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 }