1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package test.javafx.scene.web; 27 28 import javafx.scene.web.WebEngine; 29 import netscape.javascript.JSException; 30 import netscape.javascript.JSObject; 31 import static org.junit.Assert.*; 32 import org.junit.Test; 33 import org.w3c.dom.Document; 34 35 public class JavaScriptBridgeTest extends TestBase { 36 37 private void bind(String name, Object javaObject) { 38 JSObject parent = (JSObject) getEngine().executeScript("parent"); 39 parent.setMember(name, javaObject); 40 } 41 42 public @Test void testJSBridge1() throws InterruptedException { 43 final Document doc = getDocumentFor("src/test/resources/test/html/dom.html"); 44 final WebEngine web = getEngine(); 45 46 submit(() -> { 47 Object wino = web.executeScript("parent.parent"); 48 assertTrue(wino instanceof JSObject); 49 JSObject win = (JSObject) wino; 50 JSObject doc2 = (JSObject) win.getMember("document"); 51 assertSame(doc, doc2); 52 assertEquals("undefined", win.getMember("xyz")); 53 String xyz = "xyz"; 54 win.setMember("xyz", xyz); 55 assertEquals(xyz, win.getMember("xyz")); 56 web.executeScript("xlv = 50+5"); 57 assertEquals("55", win.getMember("xlv").toString()); 58 web.executeScript("xlv = xlv+0.5"); 59 assertEquals(Double.valueOf("55.5"), win.getMember("xlv")); 60 61 try { 62 doc2.eval("something_unknown"); 63 fail("JSException expected but not thrown"); 64 } catch (JSException ex) { 65 assertEquals("netscape.javascript.JSException: ReferenceError: Can't find variable: something_unknown", ex.toString()); 66 } 67 // FIXME This should work, but doesn't. A WebKit bug? 68 //JSObject htmlChildren = (JSObject) doc2.eval("documentElement.childNodes"); 69 JSObject htmlChildren = (JSObject) doc2.eval("document.documentElement.childNodes"); 70 assertEquals("[object NodeList]", htmlChildren.toString()); 71 // child 0 is the head element, child 1 is a text node (a newline), 72 // and child 2 is the body element. 73 assertEquals(3, htmlChildren.getMember("length")); 74 // This seems to fail occasionally for unknown reasons. FIXME 75 assertEquals("[object HTMLHeadElement]", htmlChildren.getSlot(0).toString()); 76 JSObject bodyNode = (JSObject) htmlChildren.getSlot(2); 77 assertEquals("[object HTMLBodyElement]", bodyNode.toString()); 78 assertEquals(Boolean.TRUE, bodyNode.call("hasChildNodes")); 79 // JSObject p2Node = (JSObject) doc2.call("getElementById", "p2"); 80 // assertEquals("p", p2Node.getMember("localName")); 81 82 // Test Node -> JavaScript conversion. 83 win.setMember("bd", bodyNode); 84 assertEquals("[object HTMLBodyElement]", web.executeScript("bd.toString()")); 85 Object bd2 = win.getMember("bd"); 86 assertSame(bodyNode, bd2); 87 88 // RT-14174 89 ((JSObject) web.executeScript("new String('test me')")) 90 .call("charAt", new Object[] {1.0}); 91 // RT-14175 92 try { 93 ((JSObject) web.executeScript("new String('test me')")) 94 .call("toUpperCase", (Object[]) null); 95 fail("NullPointerException expected but not thrown"); 96 } 97 catch (Throwable ex) { 98 assertTrue(ex instanceof NullPointerException); 99 } 100 try { 101 ((JSObject) web.executeScript("new String('test me')")) 102 .call(null); 103 fail("NullPointerException expected but not thrown"); 104 } 105 catch (Throwable ex) { 106 assertTrue(ex instanceof NullPointerException); 107 } 108 try { 109 win.setMember(null, "foo"); 110 fail("NullPointerException expected but not thrown"); 111 } 112 catch (Throwable ex) { 113 assertTrue(ex instanceof NullPointerException); 114 } 115 // RT-14178 116 ((JSObject) web.executeScript("new String('test me')")) 117 .setMember("iamwrong", null); 118 // RT-14241 119 ((JSObject) web.executeScript("new Array(1, 2, 3);")) 120 .setSlot(0, 155); 121 }); 122 } 123 124 public @Test void testJSBridge2() throws InterruptedException { 125 submit(() -> { 126 JSObject strO = (JSObject) 127 getEngine().executeScript("new String('test me')"); 128 String str = "I am new member, and I'm here"; 129 strO.setMember("newmember", str); 130 Object o = strO.getMember("newmember"); 131 assertEquals(str, o); 132 strO.removeMember("newmember"); 133 o = strO.getMember("newmember"); 134 assertEquals("undefined", o); 135 }); 136 } 137 138 public @Test void testJSBridge3() throws InterruptedException { 139 //final Document doc = getDocumentFor("src/test/resources/test/html/dom.html"); 140 final WebEngine web = getEngine(); 141 142 submit(() -> { 143 Object wino = web.executeScript("parent.parent"); 144 assertTrue(wino instanceof JSObject); 145 JSObject win = (JSObject) wino; 146 java.util.Stack<Object> st = new java.util.Stack<Object>(); 147 bind("myStack", st); 148 win.setMember("myStack2", st); 149 web.executeScript("myStack.push(\"abc\")"); 150 //assertEquals("abc", st.toString()); 151 st.push("def"); 152 assertEquals(2, web.executeScript("myStack.size()")); 153 assertSame(st, web.executeScript("myStack")); 154 assertSame(st, web.executeScript("myStack2")); 155 assertEquals("def", web.executeScript("myStack.get(1)").toString()); 156 assertEquals("[abc, def]", web.executeScript("myStack").toString()); 157 }); 158 } 159 160 public @Test void testJSBridge4() throws InterruptedException { 161 final WebEngine web = getEngine(); 162 163 submit(() -> { 164 // Based on RT-19205 "JavaScript2Java Bridge: float and double 165 // values can be lost when assigned to JS variables". 166 float a = (float) 15.5; 167 double b = 26.75; 168 Carry c = new Carry(0, 0); 169 bind("myA", a); 170 bind("myB", b); 171 bind("myC", c); 172 web.executeScript("var a1 = myA; var b1 = myB; myC.a = a1; myC.b = b1;"); 173 assertEquals(15.5, c.a, 0.1); 174 assertEquals(26.75, c.b, 0.1); 175 Carry d = new Carry(a, b); 176 Carry e = new Carry(0, 0); 177 bind("myD", d); 178 bind("myE", e); 179 Object str = 180 web.executeScript("var a2 = myD.a;" 181 + "var b2 = myD.b;" 182 + "myE.a = a2;" 183 + "myE.b = b2;[a2, b2].toString()"); 184 assertEquals("15.5,26.75", str); 185 assertEquals(15.5, d.a, 0.1); 186 assertEquals(15.5, e.a, 0.1); 187 assertEquals(26.75, d.b, 0.1); 188 assertEquals(26.75, e.b, 0.1); 189 190 // Based on RT-19209 "JavaScript2Java Bridge: assigning a JS 191 // object to a field of Java object produces garbage value" 192 Carry carry = new Carry(); 193 bind("carry", carry); 194 195 Object o = web.executeScript("carry.o = window; carry.o == window"); 196 assertEquals(Boolean.TRUE, o); 197 assertEquals("[object Window]", carry.o.toString()); 198 199 // Based on RT-19204 "JavaScript2Java Bridge: 200 // setting a char field of an object produces an exception" 201 char ch = 'C'; 202 carry = new Carry(); 203 bind("c", ch); 204 bind("carry", carry); 205 web.executeScript("carry.c = c;"); 206 assertEquals('C', carry.c); 207 }); 208 } 209 210 @Test public void testJSBridge5() throws InterruptedException { 211 final Document doc = getDocumentFor("src/test/resources/test/html/dom.html"); 212 final WebEngine web = getEngine(); 213 214 submit(() -> { 215 JSObject doc2 = (JSObject) doc; 216 try { 217 doc2.call("removeChild", new Object[] {doc2}); 218 fail("JSException expected but not thrown"); 219 } catch (Throwable ex) { 220 assertTrue(ex instanceof JSException); 221 assertTrue(ex.toString().indexOf("DOM Exception") > 0); 222 } 223 }); 224 } 225 226 @Test public void testJSCall1() throws InterruptedException { 227 final WebEngine web = getEngine(); 228 submit(() -> { 229 assertEquals("123.7", web.executeScript("123.67.toFixed(1)")); 230 try { 231 web.executeScript("123.67.toFixed(-1)"); 232 fail("JSException expected but not thrown"); 233 } catch (Throwable ex) { 234 String exp = "netscape.javascript.JSException: RangeError"; 235 assertEquals(exp, ex.toString().substring(0, exp.length())); 236 } 237 }); 238 } 239 240 @Test public void testNullMemberName() throws InterruptedException { 241 submit(() -> { 242 JSObject parent = (JSObject) getEngine().executeScript("parent"); 243 244 // test getMember(null) 245 try { 246 parent.getMember(null); 247 fail("JSObject.getMember(null) didn't throw NPE"); 248 } catch (NullPointerException e) { 249 // expected 250 } 251 252 // test setMember(null, obj) 253 try { 254 parent.setMember(null, ""); 255 fail("JSObject.setMember(null, obj) didn't throw NPE"); 256 } catch (NullPointerException e) { 257 // expected 258 } 259 260 // test removeMember(null) 261 try { 262 parent.removeMember(null); 263 fail("JSObject.removeMember(null) didn't throw NPE"); 264 } catch (NullPointerException e) { 265 // expected 266 } 267 268 // test call(null) 269 try { 270 parent.call(null); 271 fail("JSObject.call(null) didn't throw NPE"); 272 } catch (NullPointerException e) { 273 // expected 274 } 275 276 // test eval(null) 277 try { 278 parent.eval(null); 279 fail("JSObject.eval(null) didn't throw NPE"); 280 } catch (NullPointerException e) { 281 // expected 282 } 283 }); 284 } 285 286 public static class Carry { 287 public float a; 288 public double b; 289 public char c; 290 291 public Object o; 292 293 public Carry() { 294 } 295 296 public Carry(float a, double b) { 297 this.a = a; 298 this.b = b; 299 } 300 } 301 302 public @Test void testCallStatic() throws InterruptedException { 303 final WebEngine web = getEngine(); 304 305 submit(() -> { 306 // Test RT-19099 307 java.io.File x = new java.io.File("foo.txt1"); 308 bind("x", x); 309 try { 310 Object o2 = web.executeScript("x.listRoots()"); 311 fail("exception expected for invoking static method"); 312 } catch (JSException ex) { 313 if (ex.toString().indexOf("static") < 0) 314 fail("caught unexpected exception: "+ex); 315 } 316 }); 317 } 318 319 // JDK-8141386 320 public static class WrapperObjects { 321 public Number n0; // using setter 322 public Number n1; // direct access 323 public Double d0; // using setter 324 public Double d1; // direct access 325 public Integer i0; // using setter 326 public Integer i1; // direct access 327 public Boolean b0; // using setter 328 public Boolean b1; // direct access 329 public Character c0; // using setter 330 public Character c1; // direct access 331 332 public void setNumberVal(Number n) { 333 n0 = n; 334 } 335 336 public void setDoubleVal(Double d) { 337 d0 = d; 338 } 339 340 public void setIntegerVal(Integer i) { 341 i0 = i; 342 } 343 344 public void setBooleanVal(Boolean b) { 345 b0 = b; 346 } 347 348 public void setCharacterVal(Character c) { 349 c0 = c; 350 } 351 } 352 353 public @Test void testMethodCallWithWrapperObjects() { 354 final WebEngine web = getEngine(); 355 356 submit(() -> { 357 WrapperObjects obj = new WrapperObjects(); 358 bind("obj", obj); 359 // Test java.lang.Number 360 web.executeScript("obj.setNumberVal(1.23)"); 361 assertEquals(1.23, obj.n0.doubleValue(), 0.1); 362 web.executeScript("obj.n1 = 1.23"); 363 assertEquals(1.23, obj.n1.doubleValue(), 0.1); 364 // Test java.lang.Double 365 web.executeScript("obj.setDoubleVal(1.23)"); 366 assertEquals(1.23, obj.d0, 0.1); 367 web.executeScript("obj.d1 = 1.23"); 368 assertEquals(1.23, obj.d1, 0.1); 369 // Test java.lang.Integer 370 web.executeScript("obj.setIntegerVal(123)"); 371 assertEquals(123, obj.i0.intValue()); 372 web.executeScript("obj.i1 = 123"); 373 assertEquals(123, obj.i1.intValue()); 374 // Test java.lang.Boolean 375 web.executeScript("obj.setBooleanVal(true)"); 376 assertEquals(true, obj.b0.booleanValue()); 377 web.executeScript("obj.setBooleanVal(false)"); 378 assertEquals(false, obj.b0.booleanValue()); 379 web.executeScript("obj.b1 = true"); 380 assertEquals(true, obj.b1.booleanValue()); 381 web.executeScript("obj.b1 = false"); 382 assertEquals(false, obj.b1.booleanValue()); 383 // Test java.lang.Character 384 web.executeScript("obj.setCharacterVal('o')"); 385 assertEquals('o', obj.c0.charValue()); 386 web.executeScript("obj.c1 = '1'"); 387 assertEquals('1', obj.c1.charValue()); 388 }); 389 } 390 391 // JDK-8089842 392 public static class CharMember { 393 public char c; 394 } 395 396 public @Test void testJSStringToJavaCharSpecilization() { 397 final WebEngine web = getEngine(); 398 399 submit(() -> { 400 CharMember charTest = new CharMember(); 401 bind("charTest", charTest); 402 // ascii char 403 web.executeScript("charTest.c = 'o';"); 404 assertEquals('o', charTest.c); 405 web.executeScript("charTest.c = undefined;"); 406 assertEquals('\0', charTest.c); 407 web.executeScript("charTest.c = '11111111o';"); 408 assertEquals('1', charTest.c); 409 web.executeScript("charTest.c = null;"); 410 assertEquals('\0', charTest.c); 411 web.executeScript("charTest.c = ' ';"); 412 assertEquals(' ', charTest.c); 413 web.executeScript("charTest.c = '';"); 414 assertEquals('\0', charTest.c); 415 web.executeScript("charTest.c = 65;"); 416 assertEquals('A', charTest.c); 417 // unicode 418 web.executeScript("charTest.c = '\u03A9';"); 419 assertEquals('Ω', charTest.c); 420 }); 421 } 422 423 public @Test void testBridgeExplicitOverloading() throws InterruptedException { 424 final WebEngine web = getEngine(); 425 426 submit(() -> { 427 StringBuilder sb = new StringBuilder(); 428 bind("sb", sb); 429 web.executeScript("sb['append(int)'](123)"); 430 assertEquals("123", sb.toString()); 431 sb.append(' '); 432 web.executeScript("sb['append(int)'](5.5)"); 433 // Note 5.5 is truncated to int. 434 assertEquals("123 5", sb.toString()); 435 sb.append(' '); 436 web.executeScript("sb['append(Object)'](5.5)"); 437 assertEquals("123 5 5.5", sb.toString()); 438 sb.append(' '); 439 web.executeScript("sb['append(java.lang.String)']('abc')"); 440 assertEquals("123 5 5.5 abc", sb.toString()); 441 sb.append(' '); 442 web.executeScript("sb['append(String)'](987)"); 443 assertEquals("123 5 5.5 abc 987", sb.toString()); 444 assertEquals(sb.toString(), 445 web.executeScript("sb['toString()']()")); 446 447 char[] carr = { 'k', 'l', 'm' }; 448 bind("carr", carr); 449 sb.append(' '); 450 web.executeScript("sb['append(char[])'](carr)"); 451 web.executeScript("sb['append(char[],int,int)'](carr, 1, 2)"); 452 assertEquals("123 5 5.5 abc 987 klmlm", sb.toString()); 453 454 java.util.List<Integer> alist = new java.util.ArrayList<Integer>(); 455 alist.add(98); 456 alist.add(87); 457 alist.add(76); 458 bind("alist", alist); 459 Integer[] iarr = new Integer[4]; 460 bind("iarr", iarr); 461 Object r = web.executeScript("alist['toArray(Object[])'](iarr)"); 462 assertSame(iarr, r); 463 assertEquals("98/87/76/null", 464 iarr[0]+"/"+iarr[1]+"/"+iarr[2]+"/"+iarr[3]); 465 }); 466 } 467 468 private void executeShouldFail(WebEngine web, String expression, 469 String expected) { 470 try { 471 web.executeScript(expression); 472 fail("exception expected for "+expression); 473 } catch (JSException ex) { 474 if (ex.toString().indexOf(expected) < 0) 475 fail("caught unexpected exception: "+ex); 476 } 477 } 478 private void executeShouldFail(WebEngine web, String expression) { 479 executeShouldFail(web, expression, "undefined is not a function"); 480 } 481 482 public @Test void testThrowJava() throws InterruptedException { 483 final WebEngine web = getEngine(); 484 485 submit(() -> { 486 MyExceptionHelper test = new MyExceptionHelper(); 487 bind("test", test); 488 try { 489 web.executeScript("test.throwException()"); 490 fail("JSException expected but not thrown"); 491 } catch (JSException e) { 492 assertEquals("netscape.javascript.JSException", 493 e.getClass().getName()); 494 assertTrue(e.getCause() != null); 495 assertTrue(e.getCause() instanceof MyException); 496 } 497 }); 498 } 499 500 // RT-37859 501 public @Test void testThrowJava2() throws InterruptedException { 502 final WebEngine web = getEngine(); 503 504 submit(() -> { 505 MyExceptionHelper test = new MyExceptionHelper(); 506 bind("test", test); 507 try { 508 String script = 509 "try { " + 510 " test.throwException2(); " + 511 "} catch (e) { " + 512 " document.body.textContent = e; " + 513 "}"; 514 web.executeScript(script); 515 } catch (JSException e) { 516 fail("caught unexpected exception: " + e); 517 } 518 }); 519 } 520 521 public static class MyException extends Throwable { 522 } 523 524 public static class MyExceptionHelper { 525 public void throwException() throws MyException { 526 throw new MyException(); 527 } 528 public void throwException2() { 529 throw new RuntimeException("TheRuntimeException"); 530 } 531 } 532 533 534 public @Test void testBridgeArray1() throws InterruptedException { 535 final WebEngine web = getEngine(); 536 537 submit(() -> { 538 int []array = new int[3]; 539 array[0] = 42; 540 bind("test", array); 541 assertEquals(Integer.valueOf(42), web.executeScript("test[0]")); 542 assertEquals(Integer.valueOf(3), web.executeScript("test.length")); 543 assertSame(array, web.executeScript("test")); 544 }); 545 } 546 547 public @Test void testBridgeBadOverloading() throws InterruptedException { 548 final WebEngine web = getEngine(); 549 550 submit(() -> { 551 StringBuilder sb = new StringBuilder(); 552 bind("sb", sb); 553 executeShouldFail(web, "sb['append)int)'](123)"); 554 executeShouldFail(web, "sb['append)int)'](123)"); 555 executeShouldFail(web, "sb['(int)'](123)"); 556 executeShouldFail(web, "sb['append(,int)'](123)"); 557 executeShouldFail(web, "sb['append(int,)'](123)"); 558 executeShouldFail(web, "sb['unknownname(int)'](123)"); 559 executeShouldFail(web, "sb['bad-name(int)'](123)"); 560 executeShouldFail(web, "sb['append(BadClass)'](123)"); 561 executeShouldFail(web, "sb['append(bad-type)'](123)"); 562 executeShouldFail(web, "sb['append(char[],,int)'](1, 2)"); 563 }); 564 } 565 }