1 /*
   2  * Copyright (c) 2011, 2013, 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 javafx.scene.web;
  27 
  28 import com.sun.javafx.scene.web.Debugger;
  29 import com.sun.javafx.tk.TKPulseListener;
  30 import com.sun.javafx.tk.Toolkit;
  31 import com.sun.javafx.webkit.Accessor;
  32 import com.sun.webkit.Disposer;
  33 import com.sun.webkit.DisposerRecord;
  34 import com.sun.webkit.InspectorClient;
  35 import com.sun.webkit.Invoker;
  36 import com.sun.javafx.webkit.prism.PrismInvoker;
  37 import static com.sun.webkit.LoadListenerClient.*;
  38 import com.sun.webkit.LoadListenerClient;
  39 import com.sun.webkit.Timer;
  40 import com.sun.webkit.WebPage;
  41 import com.sun.webkit.network.URLs;
  42 import com.sun.webkit.network.Util;
  43 import java.io.BufferedInputStream;
  44 import java.io.ByteArrayOutputStream;
  45 import java.io.IOException;
  46 import java.lang.ref.WeakReference;
  47 import java.net.MalformedURLException;
  48 import java.net.URLConnection;
  49 import java.security.AccessController;
  50 import java.security.PrivilegedAction;
  51 import javafx.animation.AnimationTimer;
  52 import javafx.beans.InvalidationListener;
  53 import javafx.beans.property.BooleanProperty;
  54 import javafx.beans.property.BooleanPropertyBase;
  55 import javafx.beans.property.ObjectProperty;
  56 import javafx.beans.property.ReadOnlyBooleanProperty;
  57 import javafx.beans.property.ReadOnlyBooleanWrapper;
  58 import javafx.beans.property.ReadOnlyDoubleProperty;
  59 import javafx.beans.property.ReadOnlyDoubleWrapper;
  60 import javafx.beans.property.ReadOnlyObjectProperty;
  61 import javafx.beans.property.ReadOnlyObjectPropertyBase;
  62 import javafx.beans.property.ReadOnlyObjectWrapper;
  63 import javafx.beans.property.ReadOnlyStringProperty;
  64 import javafx.beans.property.ReadOnlyStringWrapper;
  65 import javafx.beans.property.SimpleObjectProperty;
  66 import javafx.beans.property.StringProperty;
  67 import javafx.beans.property.StringPropertyBase;
  68 import javafx.concurrent.Worker;
  69 import javafx.event.EventHandler;
  70 import javafx.geometry.Rectangle2D;
  71 import javafx.scene.Node;
  72 import javafx.util.Callback;
  73 import org.w3c.dom.Document;
  74 
  75 /**
  76  * {@code WebEngine} is a non-visual object capable of managing one Web page
  77  * at a time. It loads Web pages, creates their document models, applies
  78  * styles as necessary, and runs JavaScript on pages. It provides access
  79  * to the document model of the current page, and enables two-way
  80  * communication between a Java application and JavaScript code of the page.
  81  *
  82  * <h4>Loading Web Pages</h4>
  83  * <p>The {@code WebEngine} class provides two ways to load content into a
  84  * {@code WebEngine} object:
  85  * <ul>
  86  * <li>From an arbitrary URL using the {@link #load} method. This method uses
  87  *     the {@code java.net} package for network access and protocol handling.
  88  * <li>From an in-memory String using the
  89  *     {@link #loadContent(java.lang.String, java.lang.String)} and
  90  *     {@link #loadContent(java.lang.String)} methods.
  91  * </ul>
  92  * <p>Loading always happens on a background thread. Methods that initiate
  93  * loading return immediately after scheduling a background job. To track
  94  * progress and/or cancel a job, use the {@link javafx.concurrent.Worker}
  95  * instance available from the {@link #getLoadWorker} method.
  96  *
  97  * <p>The following example changes the stage title when loading completes
  98  * successfully:
  99  * <pre><code>
 100 import javafx.concurrent.Worker.State;
 101 final Stage stage;
 102 webEngine.getLoadWorker().stateProperty().addListener(
 103         new ChangeListener&lt;State&gt;() {
 104             public void changed(ObservableValue ov, State oldState, State newState) {
 105                 if (newState == State.SUCCEEDED) {
 106                     stage.setTitle(webEngine.getLocation());
 107                 }
 108             }
 109         });
 110 webEngine.load("http://javafx.com");
 111  * </code></pre>
 112  *
 113  * <h4>User Interface Callbacks</h4>
 114  * <p>A number of user interface callbacks may be registered with a
 115  * {@code WebEngine} object. These callbacks are invoked when a script running
 116  * on the page requests a user interface operation to be performed, for
 117  * example, opens a popup window or changes status text. A {@code WebEngine}
 118  * object cannot handle such requests internally, so it passes the request to
 119  * the corresponding callbacks. If no callback is defined for a specific
 120  * operation, the request is silently ignored.
 121  *
 122  * <p>The table below shows JavaScript user interface methods and properties
 123  * with their corresponding {@code WebEngine} callbacks:
 124  * <table border="1" width="400">
 125  *     <th>JavaScript method/property
 126  *     <th>WebEngine callback
 127  * <tr><td>{@code window.alert()}<td>{@code onAlert}
 128  * <tr><td>{@code window.confirm()}<td>{@code confirmHandler}
 129  * <tr><td>{@code window.open()}<td>{@code createPopupHandler}
 130  * <tr><td>{@code window.open()} and<br>
 131  *         {@code window.close()}<td>{@code onVisibilityChanged}
 132  * <tr><td>{@code window.prompt()}<td>{@code promptHandler}
 133  * <tr><td>Setting {@code window.status}<td>{@code onStatusChanged}
 134  * <tr><td>Setting any of the following:<br>
 135  *         {@code window.innerWidth}, {@code window.innerHeight},<br>
 136  *         {@code window.outerWidth}, {@code window.outerHeight},<br>
 137  *         {@code window.screenX}, {@code window.screenY},<br>
 138  *         {@code window.screenLeft}, {@code window.screenTop}
 139  *         <td>{@code onResized}
 140  * </table>
 141  *
 142  * <p>The following example shows a callback that resizes a browser window:
 143  * <pre><code>
 144 Stage stage;
 145 webEngine.setOnResized(
 146         new EventHandler&lt;WebEvent&lt;Rectangle2D&gt;&gt;() {
 147             public void handle(WebEvent&lt;Rectangle2D&gt; ev) {
 148                 Rectangle2D r = ev.getData();
 149                 stage.setWidth(r.getWidth());
 150                 stage.setHeight(r.getHeight());
 151             }
 152         });
 153  * </code></pre>
 154  *
 155  * <h4>Access to Document Model</h4>
 156  * <p>The {@code WebEngine} objects create and manage a Document Object Model
 157  * (DOM) for their Web pages. The model can be accessed and modified using
 158  * Java DOM Core classes. The {@link #getDocument()} method provides access
 159  * to the root of the model. Additionally DOM Event specification is supported
 160  * to define event handlers in Java code.
 161  *
 162  * <p>The following example attaches a Java event listener to an element of
 163  * a Web page. Clicking on the element causes the application to exit:
 164  * <pre><code>
 165 EventListener listener = new EventListener() {
 166     public void handleEvent(Event ev) {
 167         Platform.exit();
 168     }
 169 };
 170 
 171 Document doc = webEngine.getDocument();
 172 Element el = doc.getElementById("exit-app");
 173 ((EventTarget) el).addEventListener("click", listener, false);
 174  * </code></pre>
 175  *
 176  * <h4>Evaluating JavaScript expressions</h4>
 177  * <p>It is possible to execute arbitrary JavaScript code in the context of
 178  * the current page using the {@link #executeScript} method. For example:
 179  * <pre><code>
 180 webEngine.executeScript("history.back()");
 181  * </code></pre>
 182  *
 183  * <p>The execution result is returned to the caller,
 184  * as described in the next section.
 185  *
 186  * <h4>Mapping JavaScript values to Java objects</h4>
 187  *
 188  * JavaScript values are represented using the obvious Java classes:
 189  * null becomes Java null; a boolean becomes a {@code java.lang.Boolean};
 190  * and a string becomes a {@code java.lang.String}.
 191  * A number can be {@code java.lang.Double} or a {@code java.lang.Integer},
 192  * depending.
 193  * The undefined value maps to a specific unique String
 194  * object whose value is {@code "undefined"}.
 195  * <p>
 196  * If the result is a
 197  * JavaScript object, it is wrapped as an instance of the
 198  * {@link netscape.javascript.JSObject} class.
 199  * (As a special case, if the JavaScript object is
 200  * a {@code JavaRuntimeObject} as discussed in the next section,
 201  * then the original Java object is extracted instead.)
 202  * The {@code JSObject} class is a proxy that provides access to
 203  * methods and properties of its underlying JavaScript object.
 204  * The most commonly used {@code JSObject} methods are
 205  * {@link netscape.javascript.JSObject#getMember getMember}
 206  * (to read a named property),
 207  * {@link netscape.javascript.JSObject#setMember setMember}
 208  * (to set or define a property),
 209  * and {@link netscape.javascript.JSObject#call call}
 210  * (to call a function-valued property).
 211  * <p>
 212  * A DOM {@code Node} is mapped to an object that both extends
 213  * {@code JSObject} and implements the appropriate DOM interfaces.
 214  * To get a {@code JSObject} object for a {@code Node} just do a cast:
 215  * <pre>
 216  * JSObject jdoc = (JSObject) webEngine.getDocument();
 217  * </pre>
 218  * <p>
 219  * In some cases the context provides a specific Java type that guides
 220  * the conversion.
 221  * For example if setting a Java {@code String} field from a JavaScript
 222  * expression, then the JavaScript value is converted to a string.
 223  *
 224  * <h4>Mapping Java objects to JavaScript values</h4>
 225  *
 226  * The arguments of the {@code JSObject} methods {@code setMember} and
 227  * {@code call} pass Java objects to the JavaScript environment.
 228  * This is roughly the inverse of the JavaScript-to-Java mapping
 229  * described above:
 230  * Java {@code String},  {@code Number}, or {@code Boolean} objects
 231  * are converted to the obvious JavaScript values. A  {@code JSObject}
 232  * object is converted to the original wrapped JavaScript object.
 233  * Otherwise a {@code JavaRuntimeObject} is created.  This is
 234  * a JavaScript object that acts as a proxy for the Java object,
 235  * in that accessing properties of the {@code JavaRuntimeObject}
 236  * causes the Java field or method with the same name to be accessed.
 237  *
 238  * <h4>Calling back to Java from JavaScript</h4>
 239  *
 240  * <p>The {@link netscape.javascript.JSObject#setMember JSObject.setMember}
 241  * method is useful to enable upcalls from JavaScript
 242  * into Java code, as illustrated by the following example. The Java code
 243  * establishes a new JavaScript object named {@code app}. This object has one
 244  * public member, the method {@code exit}.
 245  * <pre><code>
 246 public class JavaApplication {
 247     public void exit() {
 248         Platform.exit();
 249     }
 250 }
 251 ...
 252 JSObject window = (JSObject) webEngine.executeScript("window");
 253 window.setMember("app", new JavaApplication());
 254  * </code></pre>
 255  * You can then refer to the object and the method from your HTML page:
 256  * <pre><code>
 257 &lt;a href="" onclick="app.exit()"&gt;Click here to exit application&lt;/a&gt;
 258  * </code></pre>
 259  * <p>When a user clicks the link the application is closed.
 260  * <p>
 261  * If there are multiple Java methods with the given name,
 262  * then the engine selects one matching the number of parameters
 263  * in the call.  (Varargs are not handled.) An unspecified one is
 264  * chosen if there are multiple ones with the correct number of parameters.
 265  * <p>
 266  * You can pick a specific overloaded method by listing the
 267  * parameter types in an <q>extended method name</q>, which has the
 268  * form <code>"<var>method_name</var>(<var>param_type1</var>,...,<var>param_typen</var>)"</code>.  Typically you'd write the JavaScript expression:
 269  * <pre>
 270  * <var>receiver</var>["<var>method_name</var>(<var>param_type1</var>,...,<var>param_typeN</var>)"](<var>arg1</var>,...,<var>argN</var>)</code>
 271  * </pre>
 272  *
 273  * <h4>Threading</h4>
 274  * <p>{@code WebEngine} objects must be created and accessed solely from the
 275  * JavaFX Application thread. This rule also applies to any DOM and JavaScript
 276  * objects obtained from the {@code WebEngine} object.
 277  */
 278 final public class WebEngine {
 279     static {
 280         Accessor.setPageAccessor(new Accessor.PageAccessor() {
 281             @Override public WebPage getPage(WebEngine w) {
 282                 return w == null ? null : w.getPage();
 283             }
 284         });
 285 
 286         Invoker.setInvoker(new PrismInvoker());
 287 //        Renderer.setRenderer(new PrismRenderer());
 288 //        WCGraphicsManager.setGraphicsManager(new PrismGraphicsManager());
 289 //        CursorManager.setCursorManager(new CursorManagerImpl());
 290 //        com.sun.webkit.EventLoop.setEventLoop(new EventLoopImpl());
 291 //        ThemeClient.setDefaultRenderTheme(new RenderThemeImpl());
 292 //        Utilities.setUtilities(new UtilitiesImpl());
 293     }
 294 
 295     /**
 296      * The number of instances of this class.
 297      * Used to start and stop the pulse timer.
 298      */
 299     private static int instanceCount = 0;
 300 
 301     /**
 302      * The node associated with this engine. There is a one-to-one correspondence
 303      * between the WebView and its WebEngine (although not all WebEngines have
 304      * a WebView, every WebView has one and only one WebEngine).
 305      */
 306     private final ObjectProperty<WebView> view = new SimpleObjectProperty<WebView>(this, "view");
 307 
 308     /**
 309      * The Worker which shows progress of the web engine as it loads pages.
 310      */
 311     private final LoadWorker loadWorker = new LoadWorker();
 312 
 313     /**
 314      * The object that provides interaction with the native webkit core.
 315      */
 316     private final WebPage page;
 317 
 318     /**
 319      * The debugger associated with this web engine.
 320      */
 321     private final DebuggerImpl debugger = new DebuggerImpl();
 322 
 323     /**
 324      * Returns a {@link javafx.concurrent.Worker} object that can be used to
 325      * track loading progress.
 326      */
 327     public final Worker<Void> getLoadWorker() {
 328         return loadWorker;
 329     }
 330 
 331 
 332     /**
 333      * The final document. This may be null if no document has been loaded.
 334      */
 335     private final DocumentProperty document = new DocumentProperty();
 336 
 337     /**
 338      * Returns the document object for the current Web page. If the Web page
 339      * failed to load, returns {@code null}.
 340      */
 341     public final Document getDocument() { return document.getValue(); }
 342 
 343     /**
 344      * Document object for the current Web page. The value is {@code null}
 345      * if the Web page failed to load.
 346      */
 347     public final ReadOnlyObjectProperty<Document> documentProperty() {
 348         return document;
 349     }
 350 
 351 
 352     /**
 353      * The location of the current page. This may return null.
 354      */
 355     private final ReadOnlyStringWrapper location = new ReadOnlyStringWrapper(this, "location");
 356 
 357     /**
 358      * Returns URL of the current Web page. If the current page has no URL,
 359      * returns an empty String.
 360      */
 361     public final String getLocation() { return location.getValue(); }
 362 
 363     /**
 364      * URL of the current Web page. If the current page has no URL,
 365      * the value is an empty String.
 366      */
 367     public final ReadOnlyStringProperty locationProperty() { return location.getReadOnlyProperty(); }
 368 
 369     private void updateLocation(String value) {
 370         this.location.set(value);
 371         this.document.invalidate(false);
 372         this.title.set(null);
 373     }
 374 
 375 
 376     /**
 377      * The page title.
 378      */
 379     private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title");
 380 
 381     /**
 382      * Returns title of the current Web page. If the current page has no title,
 383      * returns {@code null}.
 384      */
 385     public final String getTitle() { return title.getValue(); }
 386 
 387     /**
 388      * Title of the current Web page. If the current page has no title,
 389      * the value is {@code null}.
 390      */
 391     public final ReadOnlyStringProperty titleProperty() { return title.getReadOnlyProperty(); }
 392 
 393     private void updateTitle() {
 394         title.set(page.getTitle(page.getMainFrame()));
 395     }
 396 
 397     //
 398     // Settings
 399 
 400     /**
 401      * Specifies whether JavaScript execution is enabled.
 402      *
 403      * @defaultValue true
 404      * @since 2.2
 405      */
 406     private BooleanProperty javaScriptEnabled;
 407 
 408     public final void setJavaScriptEnabled(boolean value) {
 409         javaScriptEnabledProperty().set(value);
 410     }
 411 
 412     public final boolean isJavaScriptEnabled() {
 413         return javaScriptEnabled == null ? true : javaScriptEnabled.get();
 414     }
 415 
 416     public final BooleanProperty javaScriptEnabledProperty() {
 417         if (javaScriptEnabled == null) {
 418             javaScriptEnabled = new BooleanPropertyBase(true) {
 419                 @Override public void invalidated() {
 420                     checkThread();
 421                     page.setJavaScriptEnabled(get());
 422                 }
 423 
 424                 @Override public Object getBean() {
 425                     return WebEngine.this;
 426                 }
 427 
 428                 @Override public String getName() {
 429                     return "javaScriptEnabled";
 430                 }
 431             };
 432         }
 433         return javaScriptEnabled;
 434     }
 435 
 436     /**
 437      * Location of the user stylesheet as a string URL.
 438      *
 439      * <p>This should be a local URL, i.e. either {@code 'data:'},
 440      * {@code 'file:'}, or {@code 'jar:'}. Remote URLs are not allowed
 441      * for security reasons.
 442      *
 443      * @defaultValue null
 444      * @since 2.2
 445      */
 446     private StringProperty userStyleSheetLocation;
 447 
 448     public final void setUserStyleSheetLocation(String value) {
 449         userStyleSheetLocationProperty().set(value);
 450     }
 451 
 452     public final String getUserStyleSheetLocation() {
 453         return userStyleSheetLocation == null ? null : userStyleSheetLocation.get();
 454     }
 455 
 456     public final StringProperty userStyleSheetLocationProperty() {
 457         if (userStyleSheetLocation == null) {
 458             userStyleSheetLocation = new StringPropertyBase(null) {
 459                 private final static String DATA_PREFIX = "data:text/css;charset=utf-8;base64,";
 460 
 461                 @Override public void invalidated() {
 462                     checkThread();
 463                     String url = get();
 464                     String dataUrl;
 465                     if (url == null || url.length() <= 0) {
 466                         dataUrl = null;
 467                     } else if (url.startsWith(DATA_PREFIX)) {
 468                         dataUrl = url;
 469                     } else if (url.startsWith("file:") ||
 470                                url.startsWith("jar:")  ||
 471                                url.startsWith("data:"))
 472                     {
 473                         try {
 474                             URLConnection conn = URLs.newURL(url).openConnection();
 475                             conn.connect();
 476 
 477                             BufferedInputStream in =
 478                                     new BufferedInputStream(conn.getInputStream());
 479                             ByteArrayOutputStream out = new ByteArrayOutputStream();
 480                             new sun.misc.BASE64Encoder().encodeBuffer(in, out);
 481                             dataUrl = DATA_PREFIX + out.toString();
 482                         } catch (IOException e) {
 483                             throw new RuntimeException(e);
 484                         }
 485                     } else {
 486                         throw new IllegalArgumentException("Invalid stylesheet URL");
 487                     }
 488                     page.setUserStyleSheetLocation(dataUrl);
 489                 }
 490 
 491                 @Override public Object getBean() {
 492                     return WebEngine.this;
 493                 }
 494 
 495                 @Override public String getName() {
 496                     return "userStyleSheetLocation";
 497                 }
 498             };
 499         }
 500         return userStyleSheetLocation;
 501     }
 502 
 503     /**
 504      * Specifies user agent ID string. This string is the value of the
 505      * {@code User-Agent} HTTP header.
 506      *
 507      * @defaultValue system dependent
 508      * @since 8.0
 509      */
 510     private StringProperty userAgent;
 511 
 512     public final void setUserAgent(String value) {
 513         userAgentProperty().set(value);
 514     }
 515 
 516     public final String getUserAgent() {
 517         return userAgent == null ? page.getUserAgent() : userAgent.get();
 518     }
 519 
 520     public final StringProperty userAgentProperty() {
 521         if (userAgent == null) {
 522             userAgent = new StringPropertyBase(page.getUserAgent()) {
 523                 @Override public void invalidated() {
 524                     checkThread();
 525                     page.setUserAgent(get());
 526                 }
 527 
 528                 @Override public Object getBean() {
 529                     return WebEngine.this;
 530                 }
 531 
 532                 @Override public String getName() {
 533                     return "userAgent";
 534                 }
 535             };
 536         }
 537         return userAgent;
 538     }
 539 
 540     private final ObjectProperty<EventHandler<WebEvent<String>>> onAlert
 541             = new SimpleObjectProperty<EventHandler<WebEvent<String>>>(this, "onAlert");
 542 
 543     /**
 544      * Returns the JavaScript {@code alert} handler.
 545      * @see #onAlertProperty
 546      * @see #setOnAlert
 547      */
 548     public final EventHandler<WebEvent<String>> getOnAlert() { return onAlert.get(); }
 549 
 550     /**
 551      * Sets the JavaScript {@code alert} handler.
 552      * @see #onAlertProperty
 553      * @see #getOnAlert
 554      */
 555     public final void setOnAlert(EventHandler<WebEvent<String>> handler) { onAlert.set(handler); }
 556 
 557     /**
 558      * JavaScript {@code alert} handler property. This handler is invoked
 559      * when a script running on the Web page calls the {@code alert} function.
 560      */
 561     public final ObjectProperty<EventHandler<WebEvent<String>>> onAlertProperty() { return onAlert; }
 562 
 563 
 564     private final ObjectProperty<EventHandler<WebEvent<String>>> onStatusChanged
 565             = new SimpleObjectProperty<EventHandler<WebEvent<String>>>(this, "onStatusChanged");
 566 
 567     /**
 568      * Returns the JavaScript status handler.
 569      * @see #onStatusChangedProperty
 570      * @see #setOnStatusChanged
 571      */
 572     public final EventHandler<WebEvent<String>> getOnStatusChanged() { return onStatusChanged.get(); }
 573 
 574     /**
 575      * Sets the JavaScript status handler.
 576      * @see #onStatusChangedProperty
 577      * @see #getOnStatusChanged
 578      */
 579     public final void setOnStatusChanged(EventHandler<WebEvent<String>> handler) { onStatusChanged.set(handler); }
 580 
 581     /**
 582      * JavaScript status handler property. This handler is invoked when
 583      * a script running on the Web page sets {@code window.status} property.
 584      */
 585     public final ObjectProperty<EventHandler<WebEvent<String>>> onStatusChangedProperty() { return onStatusChanged; }
 586 
 587 
 588     private final ObjectProperty<EventHandler<WebEvent<Rectangle2D>>> onResized
 589             = new SimpleObjectProperty<EventHandler<WebEvent<Rectangle2D>>>(this, "onResized");
 590 
 591     /**
 592      * Returns the JavaScript window resize handler.
 593      * @see #onResizedProperty
 594      * @see #setOnResized
 595      */
 596     public final EventHandler<WebEvent<Rectangle2D>> getOnResized() { return onResized.get(); }
 597 
 598     /**
 599      * Sets the JavaScript window resize handler.
 600      * @see #onResizedProperty
 601      * @see #getOnResized
 602      */
 603     public final void setOnResized(EventHandler<WebEvent<Rectangle2D>> handler) { onResized.set(handler); }
 604 
 605     /**
 606      * JavaScript window resize handler property. This handler is invoked
 607      * when a script running on the Web page moves or resizes the
 608      * {@code window} object.
 609      */
 610     public final ObjectProperty<EventHandler<WebEvent<Rectangle2D>>> onResizedProperty() { return onResized; }
 611 
 612 
 613     private final ObjectProperty<EventHandler<WebEvent<Boolean>>> onVisibilityChanged
 614             = new SimpleObjectProperty<EventHandler<WebEvent<Boolean>>>(this, "onVisibilityChanged");
 615 
 616     /**
 617      * Returns the JavaScript window visibility handler.
 618      * @see #onVisibilityChangedProperty
 619      * @see #setOnVisibilityChanged
 620      */
 621     public final EventHandler<WebEvent<Boolean>> getOnVisibilityChanged() { return onVisibilityChanged.get(); }
 622 
 623     /**
 624      * Sets the JavaScript window visibility handler.
 625      * @see #onVisibilityChangedProperty
 626      * @see #getOnVisibilityChanged
 627      */
 628     public final void setOnVisibilityChanged(EventHandler<WebEvent<Boolean>> handler) { onVisibilityChanged.set(handler); }
 629 
 630     /**
 631      * JavaScript window visibility handler property. This handler is invoked
 632      * when a script running on the Web page changes visibility of the
 633      * {@code window} object.
 634      */
 635     public final ObjectProperty<EventHandler<WebEvent<Boolean>>> onVisibilityChangedProperty() { return onVisibilityChanged; }
 636 
 637 
 638     private final ObjectProperty<Callback<PopupFeatures, WebEngine>> createPopupHandler
 639             = new SimpleObjectProperty<Callback<PopupFeatures, WebEngine>>(this, "createPopupHandler",
 640                 new Callback<PopupFeatures, WebEngine>() {
 641                     public WebEngine call(PopupFeatures p) {
 642                         return WebEngine.this;
 643                     }
 644                 });
 645 
 646     /**
 647      * Returns the JavaScript popup handler.
 648      * @see #createPopupHandlerProperty
 649      * @see #setCreatePopupHandler
 650      */
 651     public final Callback<PopupFeatures, WebEngine> getCreatePopupHandler() { return createPopupHandler.get(); }
 652 
 653     /**
 654      * Sets the JavaScript popup handler.
 655      * @see #createPopupHandlerProperty
 656      * @see #getCreatePopupHandler
 657      * @see PopupFeatures
 658      */
 659     public final void setCreatePopupHandler(Callback<PopupFeatures, WebEngine> handler) { createPopupHandler.set(handler); }
 660 
 661     /**
 662      * JavaScript popup handler property. This handler is invoked when a script
 663      * running on the Web page requests a popup to be created.
 664      * <p>To satisfy this request a handler may create a new {@code WebEngine},
 665      * attach a visibility handler and optionally a resize handler, and return
 666      * the newly created engine. To block the popup, a handler should return
 667      * {@code null}.
 668      * <p>By default, a popup handler is installed that opens popups in this
 669      * {@code WebEngine}.
 670      *
 671      * @see PopupFeatures
 672      */
 673     public final ObjectProperty<Callback<PopupFeatures, WebEngine>> createPopupHandlerProperty() { return createPopupHandler; }
 674 
 675 
 676     private final ObjectProperty<Callback<String, Boolean>> confirmHandler
 677             = new SimpleObjectProperty<Callback<String, Boolean>>(this, "confirmHandler");
 678 
 679     /**
 680      * Returns the JavaScript {@code confirm} handler.
 681      * @see #confirmHandlerProperty
 682      * @see #setConfirmHandler
 683      */
 684     public final Callback<String, Boolean> getConfirmHandler() { return confirmHandler.get(); }
 685 
 686     /**
 687      * Sets the JavaScript {@code confirm} handler.
 688      * @see #confirmHandlerProperty
 689      * @see #getConfirmHandler
 690      */
 691     public final void setConfirmHandler(Callback<String, Boolean> handler) { confirmHandler.set(handler); }
 692 
 693     /**
 694      * JavaScript {@code confirm} handler property. This handler is invoked
 695      * when a script running on the Web page calls the {@code confirm} function.
 696      * <p>An implementation may display a dialog box with Yes and No options,
 697      * and return the user's choice.
 698      */
 699     public final ObjectProperty<Callback<String, Boolean>> confirmHandlerProperty() { return confirmHandler; }
 700 
 701 
 702     private final ObjectProperty<Callback<PromptData, String>> promptHandler
 703             = new SimpleObjectProperty<Callback<PromptData, String>>(this, "promptHandler");
 704 
 705     /**
 706      * Returns the JavaScript {@code prompt} handler.
 707      * @see #promptHandlerProperty
 708      * @see #setPromptHandler
 709      * @see PromptData
 710      */
 711     public final Callback<PromptData, String> getPromptHandler() { return promptHandler.get(); }
 712 
 713     /**
 714      * Sets the JavaScript {@code prompt} handler.
 715      * @see #promptHandlerProperty
 716      * @see #getPromptHandler
 717      * @see PromptData
 718      */
 719     public final void setPromptHandler(Callback<PromptData, String> handler) { promptHandler.set(handler); }
 720 
 721     /**
 722      * JavaScript {@code prompt} handler property. This handler is invoked
 723      * when a script running on the Web page calls the {@code prompt} function.
 724      * <p>An implementation may display a dialog box with an text field,
 725      * and return the user's input.
 726      *
 727      * @see PromptData
 728      */
 729     public final ObjectProperty<Callback<PromptData, String>> promptHandlerProperty() { return promptHandler; }
 730 
 731     /**
 732      * Creates a new engine.
 733      */
 734     public WebEngine() {
 735         this(null);
 736     }
 737 
 738     /**
 739      * Creates a new engine and loads a Web page into it.
 740      */
 741     public WebEngine(String url) {
 742         if (instanceCount == 0 &&
 743             Timer.getMode() == Timer.Mode.PLATFORM_TICKS)
 744         {
 745             PulseTimer.start();
 746         }
 747         Accessor accessor = new AccessorImpl(this);
 748         page = new WebPage(this //            new WebPageClientImpl(accessor),
 749                 //            new UIClientImpl(accessor),
 750                 //            new PolicyClientImpl(),
 751                 //            new InspectorClientImpl(this),
 752                 //            new ThemeClientImpl(accessor),
 753                 //            false);
 754                 );
 755         page.addLoadListenerClient(new PageLoadListener(this));
 756 
 757         history = new WebHistory(page);
 758 
 759         Disposer.addRecord(this, new SelfDisposer(page));
 760 
 761         load(url);
 762 
 763         instanceCount++;
 764     }
 765 
 766     /**
 767      * Loads a Web page into this engine. This method starts asynchronous
 768      * loading and returns immediately.
 769      * @param url URL of the web page to load
 770      */
 771     public void load(String url) {
 772         checkThread();
 773         loadWorker.cancelAndReset();
 774 
 775         if (url == null) {
 776             url = "";
 777         } else {
 778             // verify and, if possible, adjust the url on the Java
 779             // side, otherwise it may crash native code
 780             try {
 781                 url = Util.adjustUrlForWebKit(url);
 782             } catch (MalformedURLException e) {
 783                 loadWorker.dispatchLoadEvent(getMainFrame(),
 784                         PAGE_STARTED, url, null, 0.0, 0);
 785                 loadWorker.dispatchLoadEvent(getMainFrame(),
 786                         LOAD_FAILED, url, null, 0.0, MALFORMED_URL);
 787             }
 788         }
 789         page.open(page.getMainFrame(), url);
 790     }
 791 
 792     /**
 793      * Loads the given HTML content directly. This method is useful when you have an HTML
 794      * String composed in memory, or loaded from some system which cannot be reached via
 795      * a URL (for example, the HTML text may have come from a database). As with
 796      * {@link #load(String)}, this method is asynchronous.
 797      */
 798     public void loadContent(String content) {
 799         loadContent(content, "text/html");
 800     }
 801 
 802     /**
 803      * Loads the given content directly. This method is useful when you have content
 804      * composed in memory, or loaded from some system which cannot be reached via
 805      * a URL (for example, the SVG text may have come from a database). As with
 806      * {@link #load(String)}, this method is asynchronous. This method also allows you to
 807      * specify the content type of the string being loaded, and so may optionally support
 808      * other types besides just HTML.
 809      */
 810     public void loadContent(String content, String contentType) {
 811         checkThread();
 812         loadWorker.cancelAndReset();
 813         page.load(page.getMainFrame(), content, contentType);
 814     }
 815 
 816     /**
 817      * Reloads the current page, whether loaded from URL or directly from a String in
 818      * one of the {@code loadContent} methods.
 819      */
 820     public void reload() {
 821         // TODO what happens if this is called while currently loading a page?
 822         checkThread();
 823         page.refresh(page.getMainFrame());
 824     }
 825 
 826     private final WebHistory history;
 827 
 828     /**
 829      * Returns the session history object.
 830      *
 831      * @return history object
 832      * @since 2.2
 833      */
 834     public WebHistory getHistory() {
 835         return history;
 836     }
 837 
 838     /**
 839      * Executes a script in the context of the current page.
 840      * @return execution result, converted to a Java object using the following
 841      * rules:
 842      * <ul>
 843      * <li>JavaScript Int32 is converted to {@code java.lang.Integer}
 844      * <li>Other JavaScript numbers to {@code java.lang.Double}
 845      * <li>JavaScript string to {@code java.lang.String}
 846      * <li>JavaScript boolean to {@code java.lang.Boolean}
 847      * <li>JavaScript {@code null} to {@code null}
 848      * <li>Most JavaScript objects get wrapped as
 849      *     {@code netscape.javascript.JSObject}
 850      * <li>JavaScript JSNode objects get mapped to instances of
 851      *     {@code netscape.javascript.JSObject}, that also implement
 852      *     {@code org.w3c.dom.Node}
 853      * <li>A special case is the JavaScript class {@code JavaRuntimeObject}
 854      *     which is used to wrap a Java object as a JavaScript value - in this
 855      *     case we just extract the original Java value.
 856      * </ul>
 857      */
 858     public Object executeScript(String script) {
 859         checkThread();
 860         return page.executeScript(page.getMainFrame(), script);
 861     }
 862 
 863     private long getMainFrame() {
 864         return page.getMainFrame();
 865     }
 866 
 867     WebPage getPage() {
 868         return page;
 869     }
 870 
 871     void setView(WebView view) {
 872         this.view.setValue(view);
 873     }
 874 
 875     public WebView getView() {
 876         return this.view.getValue();
 877     }
 878 
 879     private void stop() {
 880         checkThread();
 881         page.stop(page.getMainFrame());
 882     }
 883 
 884     private static final class SelfDisposer implements DisposerRecord {
 885         private final WebPage page;
 886 
 887         private SelfDisposer(WebPage page) {
 888             this.page = page;
 889         }
 890 
 891         @Override public void dispose() {
 892             page.dispose();
 893             instanceCount--;
 894             if (instanceCount == 0 &&
 895                 Timer.getMode() == Timer.Mode.PLATFORM_TICKS)
 896             {
 897                 PulseTimer.stop();
 898             }
 899         }
 900     }
 901 
 902     private static final class AccessorImpl extends Accessor {
 903         private final WeakReference<WebEngine> engine;
 904 
 905         private AccessorImpl(WebEngine w) {
 906             this.engine = new WeakReference<WebEngine>(w);
 907         }
 908 
 909         @Override public WebEngine getEngine() {
 910             return engine.get();
 911         }
 912 
 913         @Override public WebPage getPage() {
 914             WebEngine w = getEngine();
 915             return w == null ? null : w.page;
 916         }
 917 
 918         @Override public WebView getView() {
 919             WebEngine w = getEngine();
 920             return w == null ? null : w.view.get();
 921         }
 922 
 923         @Override public void addChild(Node child) {
 924             WebView view = getView();
 925             if (view != null) {
 926                 view.getChildren().add(child);
 927             }
 928         }
 929 
 930         @Override public void removeChild(Node child) {
 931             WebView view = getView();
 932             if (view != null) {
 933                 view.getChildren().remove(child);
 934             }
 935         }
 936 
 937         @Override public void addViewListener(InvalidationListener l) {
 938             WebEngine w = getEngine();
 939             if (w != null) {
 940                 w.view.addListener(l);
 941             }
 942         }
 943     }
 944 
 945     /**
 946      * Drives the {@code Timer} when {@code Timer.Mode.PLATFORM_TICKS} is set.
 947      */
 948     private static final class PulseTimer {
 949 
 950         // Used just to guarantee constant pulse activity. See RT-14433.
 951         private static final AnimationTimer animation =
 952             new AnimationTimer() {
 953                 @Override public void handle(long l) {}
 954             };
 955 
 956         private static final TKPulseListener listener =
 957             new TKPulseListener() {
 958                 public void pulse() {
 959                     // Note, the timer event is executed right in the notifyTick(),
 960                     // that is during the pulse event. This makes the timer more
 961                     // repsonsive, though prolongs the pulse. So far it causes no
 962                     // problems but nevertheless it should be kept in mind.
 963 //                    Timer.getTimer().notifyTick();
 964                 }
 965             };
 966 
 967         private static void start(){
 968             Toolkit.getToolkit().addSceneTkPulseListener(listener);
 969 //            animation.start();
 970         }
 971 
 972         private static void stop() {
 973             Toolkit.getToolkit().removeSceneTkPulseListener(listener);
 974 //            animation.stop();
 975         }
 976     }
 977 
 978     static void checkThread() {
 979         Toolkit.getToolkit().checkFxUserThread();
 980     }
 981 
 982 
 983     /**
 984      * The page load event listener. This object references the owner
 985      * WebEngine weakly so as to avoid referencing WebEngine from WebPage
 986      * strongly.
 987      */
 988     private static final class PageLoadListener implements LoadListenerClient {
 989 
 990         private final WeakReference<WebEngine> engine;
 991 
 992 
 993         private PageLoadListener(WebEngine engine) {
 994             this.engine = new WeakReference<WebEngine>(engine);
 995         }
 996 
 997 
 998         @Override public void dispatchLoadEvent(long frame, int state,
 999                 String url, String contentType, double progress, int errorCode)
1000         {
1001             WebEngine w = engine.get();
1002             if (w != null) {
1003                 w.loadWorker.dispatchLoadEvent(frame, state, url,
1004                         contentType, progress, errorCode);
1005             }
1006         }
1007 
1008         @Override public void dispatchResourceLoadEvent(long frame,
1009                 int state, String url, String contentType, double progress,
1010                 int errorCode)
1011         {
1012         }
1013     }
1014 
1015 
1016     private final class LoadWorker implements Worker<Void> {
1017 
1018         private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<State>(this, "state", State.READY);
1019         @Override public final State getState() { checkThread(); return state.get(); }
1020         @Override public final ReadOnlyObjectProperty<State> stateProperty() { checkThread(); return state.getReadOnlyProperty(); }
1021         private void updateState(State value) {
1022             checkThread();
1023             this.state.set(value);
1024             running.set(value == State.SCHEDULED || value == State.RUNNING);
1025         }
1026 
1027         /**
1028          * @InheritDoc
1029          */
1030         private final ReadOnlyObjectWrapper<Void> value = new ReadOnlyObjectWrapper<Void>(this, "value", null);
1031         @Override public final Void getValue() { checkThread(); return value.get(); }
1032         @Override public final ReadOnlyObjectProperty<Void> valueProperty() { checkThread(); return value.getReadOnlyProperty(); }
1033 
1034         /**
1035          * @InheritDoc
1036          */
1037         private final ReadOnlyObjectWrapper<Throwable> exception = new ReadOnlyObjectWrapper<Throwable>(this, "exception");
1038         @Override public final Throwable getException() { checkThread(); return exception.get(); }
1039         @Override public final ReadOnlyObjectProperty<Throwable> exceptionProperty() { checkThread(); return exception.getReadOnlyProperty(); }
1040 
1041         /**
1042          * @InheritDoc
1043          */
1044         private final ReadOnlyDoubleWrapper workDone = new ReadOnlyDoubleWrapper(this, "workDone", -1);
1045         @Override public final double getWorkDone() { checkThread(); return workDone.get(); }
1046         @Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone.getReadOnlyProperty(); }
1047 
1048         /**
1049          * @InheritDoc
1050          */
1051         private final ReadOnlyDoubleWrapper totalWorkToBeDone = new ReadOnlyDoubleWrapper(this, "totalWork", -1);
1052         @Override public final double getTotalWork() { checkThread(); return totalWorkToBeDone.get(); }
1053         @Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWorkToBeDone.getReadOnlyProperty(); }
1054 
1055         /**
1056          * @InheritDoc
1057          */
1058         private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1);
1059         @Override public final double getProgress() { checkThread(); return progress.get(); }
1060         @Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress.getReadOnlyProperty(); }
1061         private void updateProgress(double p) {
1062             totalWorkToBeDone.set(100.0);
1063             workDone.set(p * 100.0);
1064             progress.set(p);
1065         }
1066 
1067         /**
1068          * @InheritDoc
1069          */
1070         private final ReadOnlyBooleanWrapper running = new ReadOnlyBooleanWrapper(this, "running", false);
1071         @Override public final boolean isRunning() { checkThread(); return running.get(); }
1072         @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running.getReadOnlyProperty(); }
1073 
1074         /**
1075          * @InheritDoc
1076          */
1077         private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", "");
1078         @Override public final String getMessage() { return message.get(); }
1079         @Override public final ReadOnlyStringProperty messageProperty() { return message.getReadOnlyProperty(); }
1080 
1081         /**
1082          * @InheritDoc
1083          */
1084         private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", "WebEngine Loader");
1085         @Override public final String getTitle() { return title.get(); }
1086         @Override public final ReadOnlyStringProperty titleProperty() { return title.getReadOnlyProperty(); }
1087 
1088         /**
1089          * Cancels the loading of the page. If called after the page has already
1090          * been loaded, then this call takes no effect.
1091          */
1092         @Override public boolean cancel() {
1093             if (isRunning()) {
1094                 stop(); // this call indirectly sets state
1095                 return true;
1096             } else {
1097                 return false;
1098             }
1099         }
1100 
1101         private void cancelAndReset() {
1102             cancel();
1103             exception.set(null);
1104             message.set("");
1105             totalWorkToBeDone.set(-1);
1106             workDone.set(-1);
1107             progress.set(-1);
1108             updateState(State.READY);
1109             running.set(false);
1110         }
1111 
1112         private void dispatchLoadEvent(long frame, int state,
1113                 String url, String contentType, double workDone, int errorCode)
1114         {
1115             if (frame != getMainFrame()) {
1116                 return;
1117             }
1118 
1119             switch (state) {
1120                 case PAGE_STARTED:
1121                     message.set("Loading " + url);
1122                     updateLocation(url);
1123                     updateProgress(0.0);
1124                     updateState(State.SCHEDULED);
1125                     updateState(State.RUNNING);
1126                     break;
1127                 case PAGE_REDIRECTED:
1128                     message.set("Loading " + url);
1129                     updateLocation(url);
1130                     break;
1131                 case PAGE_FINISHED:
1132                     message.set("Loading complete");
1133                     updateProgress(1.0);
1134                     updateState(State.SUCCEEDED);
1135                     break;
1136                 case LOAD_FAILED:
1137                     message.set("Loading failed");
1138                     exception.set(describeError(errorCode));
1139                     updateState(State.FAILED);
1140                     break;
1141                 case LOAD_STOPPED:
1142                     message.set("Loading stopped");
1143                     updateState(State.CANCELLED);
1144                     break;
1145                 case PROGRESS_CHANGED:
1146                     updateProgress(workDone);
1147                     break;
1148                 case TITLE_RECEIVED:
1149                     updateTitle();
1150                     break;
1151                 case DOCUMENT_AVAILABLE:
1152                     document.invalidate(true);
1153                     break;
1154             }
1155         }
1156 
1157         private Throwable describeError(int errorCode) {
1158             String reason = "Unknown error";
1159 
1160             switch (errorCode) {
1161                 case UNKNOWN_HOST:
1162                     reason = "Unknown host";
1163                     break;
1164                 case MALFORMED_URL:
1165                     reason = "Malformed URL";
1166                     break;
1167                 case SSL_HANDSHAKE:
1168                     reason = "SSL handshake failed";
1169                     break;
1170                 case CONNECTION_REFUSED:
1171                     reason = "Connection refused by server";
1172                     break;
1173                 case CONNECTION_RESET:
1174                     reason = "Connection reset by server";
1175                     break;
1176                 case NO_ROUTE_TO_HOST:
1177                     reason = "No route to host";
1178                     break;
1179                 case CONNECTION_TIMED_OUT:
1180                     reason = "Connection timed out";
1181                     break;
1182                 case PERMISSION_DENIED:
1183                     reason = "Permission denied";
1184                     break;
1185                 case INVALID_RESPONSE:
1186                     reason = "Invalid response from server";
1187                     break;
1188                 case TOO_MANY_REDIRECTS:
1189                     reason = "Too many redirects";
1190                     break;
1191                 case FILE_NOT_FOUND:
1192                     reason = "File not found";
1193                     break;
1194             }
1195             return new Throwable(reason);
1196         }
1197     }
1198 
1199 
1200     private final class DocumentProperty
1201             extends ReadOnlyObjectPropertyBase<Document> {
1202 
1203         private boolean available;
1204         private Document document;
1205 
1206         private void invalidate(boolean available) {
1207             if (this.available || available) {
1208                 this.available = available;
1209                 this.document = null;
1210                 fireValueChangedEvent();
1211             }
1212         }
1213 
1214         public Document get() {
1215             if (!this.available) {
1216                 return null;
1217             }
1218             if (this.document == null) {
1219                 this.document = page.getDocument(page.getMainFrame());
1220                 if (this.document == null) {
1221                     this.available = false;
1222                 }
1223             }
1224             return this.document;
1225         }
1226 
1227         public Object getBean() {
1228             return WebEngine.this;
1229         }
1230 
1231         public String getName() {
1232             return "document";
1233         }
1234     }
1235 
1236 
1237     /**
1238      * Returns the debugger associated with this web engine.
1239      * The debugger is an object that can be used to debug
1240      * the web page currently loaded into the web engine.
1241      * <p>
1242      * All methods of the debugger must be called on
1243      * the JavaFX Application Thread.
1244      * The message callback object registered with the debugger
1245      * is always called on the JavaFX Application Thread.
1246      * @return the debugger associated with this web engine.
1247      *         The return value cannot be {@code null}.
1248      */
1249     Debugger getDebugger() {
1250         return debugger;
1251     }
1252 
1253     /**
1254      * The debugger implementation.
1255      */
1256     private final class DebuggerImpl implements Debugger {
1257 
1258         private boolean enabled;
1259         private Callback<String,Void> messageCallback;
1260 
1261 
1262         @Override
1263         public boolean isEnabled() {
1264             checkThread();
1265             return enabled;
1266         }
1267 
1268         @Override
1269         public void setEnabled(boolean enabled) {
1270             checkThread();
1271             if (enabled != this.enabled) {
1272                 if (enabled) {
1273                     page.setDeveloperExtrasEnabled(true);
1274                     page.connectInspectorFrontend();
1275                 } else {
1276                     page.disconnectInspectorFrontend();
1277                     page.setDeveloperExtrasEnabled(false);
1278                 }
1279                 this.enabled = enabled;
1280             }
1281         }
1282 
1283         @Override
1284         public void sendMessage(String message) {
1285             checkThread();
1286             if (!enabled) {
1287                 throw new IllegalStateException("Debugger is not enabled");
1288             }
1289             if (message == null) {
1290                 throw new NullPointerException("message is null");
1291             }
1292             page.dispatchInspectorMessageFromFrontend(message);
1293         }
1294 
1295         @Override
1296         public Callback<String,Void> getMessageCallback() {
1297             checkThread();
1298             return messageCallback;
1299         }
1300 
1301         @Override
1302         public void setMessageCallback(Callback<String,Void> callback) {
1303             checkThread();
1304             messageCallback = callback;
1305         }
1306     }
1307 
1308     /**
1309      * The inspector client implementation. This object references the owner
1310      * WebEngine weakly so as to avoid referencing WebEngine from WebPage
1311      * strongly.
1312      */
1313     private static final class InspectorClientImpl implements InspectorClient {
1314 
1315         private final WeakReference<WebEngine> engine;
1316 
1317 
1318         private InspectorClientImpl(WebEngine engine) {
1319             this.engine = new WeakReference<WebEngine>(engine);
1320         }
1321 
1322 
1323         @Override
1324         public boolean sendMessageToFrontend(final String message) {
1325             boolean result = false;
1326             WebEngine webEngine = engine.get();
1327             if (webEngine != null) {
1328                 final Callback<String,Void> messageCallback =
1329                         webEngine.debugger.messageCallback;
1330                 if (messageCallback != null) {
1331                     AccessController.doPrivileged(new PrivilegedAction<Void>() {
1332                         @Override public Void run() {
1333                             messageCallback.call(message);
1334                             return null;
1335                         }
1336                     }, webEngine.page.getAccessControlContext());
1337                     result = true;
1338                 }
1339             }
1340             return result;
1341         }
1342     }
1343 }