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 javafx.scene.web; 27 28 import com.sun.javafx.scene.web.Debugger; 29 import javafx.animation.AnimationTimer; 30 import javafx.beans.property.ObjectProperty; 31 import javafx.beans.property.ReadOnlyBooleanProperty; 32 import javafx.beans.property.ReadOnlyDoubleProperty; 33 import javafx.beans.property.ReadOnlyObjectProperty; 34 import javafx.beans.property.ReadOnlyStringProperty; 35 import javafx.beans.property.ReadOnlyBooleanWrapper; 36 import javafx.beans.property.ReadOnlyDoubleWrapper; 37 import javafx.beans.property.ReadOnlyObjectWrapper; 38 import javafx.beans.property.ReadOnlyStringWrapper; 39 import javafx.beans.property.SimpleObjectProperty; 40 import javafx.concurrent.Worker; 41 import javafx.event.EventHandler; 42 import javafx.util.Callback; 43 import javax.xml.parsers.DocumentBuilder; 44 import javax.xml.parsers.DocumentBuilderFactory; 45 46 import org.w3c.dom.Document; 47 import org.xml.sax.InputSource; 48 49 import com.sun.javafx.tk.TKPulseListener; 50 import com.sun.javafx.tk.Toolkit; 51 import java.io.StringReader; 52 import java.security.AccessControlContext; 53 import java.security.AccessController; 54 import java.security.PrivilegedAction; 55 import javafx.beans.property.*; 56 import javafx.geometry.Rectangle2D; 57 58 /** 59 * {@code WebEngine} is a non-visual object capable of managing one Web page 60 * at a time. One can load Web pages into an engine, track loading progress, 61 * access document model of a loaded page, and execute JavaScript on the page. 62 * 63 * <p>Loading is always asynchronous. Methods that initiate loading return 64 * immediately after scheduling a job, so one must not assume loading is 65 * complete by that time. {@link #getLoadWorker} method can be used to track 66 * loading status. 67 * 68 * <p>A number of JavaScript handlers and callbacks may be registered with a 69 * {@code WebEngine}. These are invoked when a script running on the page 70 * accesses user interface elements that lie beyond the control of the 71 * {@code WebEngine}, such as browser window, toolbar or status line. 72 * 73 * <p>{@code WebEngine} objects must be created and accessed solely from the 74 * FXthread. 75 * @since JavaFX 2.0 76 */ 77 final public class WebEngine { 78 79 /** 80 * The node associated with this engine. There is a one-to-one correspondance 81 * between the WebView and its WebEngine (although not all WebEngines have 82 * a WebView, every WebView has one and only one WebEngine). 83 */ 84 private final ObjectProperty<WebView> view = new SimpleObjectProperty<WebView>(this, "view"); 85 86 /** 87 * The Worker which shows progress of the web engine as it loads pages. 88 */ 89 private final LoadWorker loadWorker = new LoadWorker(); 90 91 /** 92 * Returns a {@link javafx.concurrent.Worker} object that can be used to 93 * track loading progress. 94 */ 95 public final Worker<Void> getLoadWorker() { 96 return loadWorker; 97 } 98 99 private void updateProgress(double p) { 100 LoadWorker lw = (LoadWorker) getLoadWorker(); 101 if (lw != null) { 102 lw.updateProgress(p); 103 } 104 } 105 106 private void updateState(Worker.State s) { 107 LoadWorker lw = (LoadWorker) getLoadWorker(); 108 if (lw != null) { 109 lw.updateState(s); 110 } 111 } 112 113 /** 114 * The final document. This may be null if no document has been loaded. 115 */ 116 private final DocumentProperty document = new DocumentProperty(); 117 118 /** 119 * Returns the document object for the current Web page. If the Web page 120 * failed to load, returns {@code null}. 121 */ 122 public final Document getDocument() { return document.getValue(); } 123 124 /** 125 * Document object for the current Web page. The value is {@code null} 126 * if the Web page failed to load. 127 */ 128 public final ReadOnlyObjectProperty<Document> documentProperty() { 129 return document; 130 } 131 132 /** 133 * The location of the current page. This may return null. 134 */ 135 private final ReadOnlyStringWrapper location = new ReadOnlyStringWrapper(this, "location"); 136 137 /** 138 * Returns URL of the current Web page. If the current page has no URL, 139 * returns an empty String. 140 */ 141 public final String getLocation() { return location.getValue(); } 142 143 /** 144 * URL of the current Web page. If the current page has no URL, 145 * the value is an empty String. 146 */ 147 public final ReadOnlyStringProperty locationProperty() { return location.getReadOnlyProperty(); } 148 149 /** 150 * The page title. 151 */ 152 private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title"); 153 154 /** 155 * Returns title of the current Web page. If the current page has no title, 156 * returns {@code null}. 157 */ 158 public final String getTitle() { return title.getValue(); } 159 160 /** 161 * Title of the current Web page. If the current page has no title, 162 * the value is {@code null}. 163 */ 164 public final ReadOnlyStringProperty titleProperty() { return title.getReadOnlyProperty(); } 165 166 // 167 // Settings 168 169 /** 170 * Specifies whether JavaScript execution is enabled. 171 * 172 * @defaultValue true 173 * @since JavaFX 2.2 174 */ 175 private BooleanProperty javaScriptEnabled; 176 177 public final void setJavaScriptEnabled(boolean value) { 178 javaScriptEnabledProperty().set(value); 179 } 180 181 public final boolean isJavaScriptEnabled() { 182 return javaScriptEnabled == null ? true : javaScriptEnabled.get(); 183 } 184 185 private Debugger debugger = new DebuggerImpl(); 186 187 Debugger getDebugger(){ 188 return debugger; 189 } 190 191 public final BooleanProperty javaScriptEnabledProperty() { 192 if (javaScriptEnabled == null) { 193 javaScriptEnabled = new BooleanPropertyBase(true) { 194 @Override public void invalidated() { 195 checkThread(); 196 } 197 198 @Override public Object getBean() { 199 return WebEngine.this; 200 } 201 202 @Override public String getName() { 203 return "javaScriptEnabled"; 204 } 205 }; 206 } 207 return javaScriptEnabled; 208 } 209 210 /** 211 * Location of the user stylesheet as a string URL. 212 * 213 * <p>This should be a local URL, i.e. either {@code 'data:'}, 214 * {@code 'file:'}, or {@code 'jar:'}. Remote URLs are not allowed 215 * for security reasons. 216 * 217 * @defaultValue null 218 * @since JavaFX 2.2 219 */ 220 private StringProperty userStyleSheetLocation; 221 222 public final void setUserStyleSheetLocation(String value) { 223 userStyleSheetLocationProperty().set(value); 224 } 225 226 public final String getUserStyleSheetLocation() { 227 return userStyleSheetLocation == null ? null : userStyleSheetLocation.get(); 228 } 229 230 public final StringProperty userStyleSheetLocationProperty() { 231 if (userStyleSheetLocation == null) { 232 userStyleSheetLocation = new StringPropertyBase(null) { 233 private final static String DATA_PREFIX = "data:text/css;charset=utf-8;base64,"; 234 235 @Override public void invalidated() { 236 checkThread(); 237 String url = get(); 238 String dataUrl; 239 if (url == null || url.length() <= 0) { 240 dataUrl = null; 241 } else if (url.startsWith(DATA_PREFIX)) { 242 dataUrl = url; 243 } else if (url.startsWith("file:") || 244 url.startsWith("jar:") || 245 url.startsWith("data:")) { 246 247 } else { 248 throw new IllegalArgumentException("Invalid stylesheet URL"); 249 } 250 } 251 252 @Override public Object getBean() { 253 return WebEngine.this; 254 } 255 256 @Override public String getName() { 257 return "userStyleSheetLocation"; 258 } 259 }; 260 } 261 return userStyleSheetLocation; 262 } 263 264 /** 265 * Specifies user agent ID string. This string is the value of the 266 * {@code User-Agent} HTTP header. 267 * 268 * @defaultValue system dependent 269 * @since JavaFX 8.0 270 */ 271 private StringProperty userAgent; 272 273 public final void setUserAgent(String value) { 274 userAgentProperty().set(value); 275 } 276 277 public final String getUserAgent() { 278 return userAgent == null ? null : userAgent.get(); 279 } 280 281 public final StringProperty userAgentProperty() { 282 if (userAgent == null) { 283 userAgent = new StringPropertyBase() { 284 @Override public void invalidated() { 285 checkThread(); 286 } 287 288 @Override public Object getBean() { 289 return WebEngine.this; 290 } 291 292 @Override public String getName() { 293 return "userAgent"; 294 } 295 }; 296 } 297 return userAgent; 298 } 299 300 private final ObjectProperty<EventHandler<WebEvent<String>>> onAlert 301 = new SimpleObjectProperty<EventHandler<WebEvent<String>>>(this, "onAlert"); 302 303 /** 304 * Returns the JavaScript {@code alert} handler. 305 * @see #onAlertProperty 306 * @see #setOnAlert 307 */ 308 public final EventHandler<WebEvent<String>> getOnAlert() { return onAlert.get(); } 309 310 /** 311 * Sets the JavaScript {@code alert} handler. 312 * @see #onAlertProperty 313 * @see #getOnAlert 314 */ 315 public final void setOnAlert(EventHandler<WebEvent<String>> handler) { onAlert.set(handler); } 316 317 /** 318 * JavaScript {@code alert} handler property. This handler is invoked 319 * when a script running on the Web page calls the {@code alert} function. 320 */ 321 public final ObjectProperty<EventHandler<WebEvent<String>>> onAlertProperty() { return onAlert; } 322 323 324 private final ObjectProperty<EventHandler<WebEvent<String>>> onStatusChanged 325 = new SimpleObjectProperty<EventHandler<WebEvent<String>>>(this, "onStatusChanged"); 326 327 /** 328 * Returns the JavaScript status handler. 329 * @see #onStatusChangedProperty 330 * @see #setOnStatusChanged 331 */ 332 public final EventHandler<WebEvent<String>> getOnStatusChanged() { return onStatusChanged.get(); } 333 334 /** 335 * Sets the JavaScript status handler. 336 * @see #onStatusChangedProperty 337 * @see #getOnStatusChanged 338 */ 339 public final void setOnStatusChanged(EventHandler<WebEvent<String>> handler) { onStatusChanged.set(handler); } 340 341 /** 342 * JavaScript status handler property. This handler is invoked when 343 * a script running on the Web page sets {@code window.status} property. 344 */ 345 public final ObjectProperty<EventHandler<WebEvent<String>>> onStatusChangedProperty() { return onStatusChanged; } 346 347 348 private final ObjectProperty<EventHandler<WebEvent<Rectangle2D>>> onResized 349 = new SimpleObjectProperty<EventHandler<WebEvent<Rectangle2D>>>(this, "onResized"); 350 351 /** 352 * Returns the JavaScript window resize handler. 353 * @see #onResizedProperty 354 * @see #setOnResized 355 */ 356 public final EventHandler<WebEvent<Rectangle2D>> getOnResized() { return onResized.get(); } 357 358 /** 359 * Sets the JavaScript window resize handler. 360 * @see #onResizedProperty 361 * @see #getOnResized 362 */ 363 public final void setOnResized(EventHandler<WebEvent<Rectangle2D>> handler) { onResized.set(handler); } 364 365 /** 366 * JavaScript window resize handler property. This handler is invoked 367 * when a script running on the Web page moves or resizes the 368 * {@code window} object. 369 */ 370 public final ObjectProperty<EventHandler<WebEvent<Rectangle2D>>> onResizedProperty() { return onResized; } 371 372 373 private final ObjectProperty<EventHandler<WebEvent<Boolean>>> onVisibilityChanged 374 = new SimpleObjectProperty<EventHandler<WebEvent<Boolean>>>(this, "onVisibilityChanged"); 375 376 /** 377 * Returns the JavaScript window visibility handler. 378 * @see #onVisibilityChangedProperty 379 * @see #setOnVisibilityChanged 380 */ 381 public final EventHandler<WebEvent<Boolean>> getOnVisibilityChanged() { return onVisibilityChanged.get(); } 382 383 /** 384 * Sets the JavaScript window visibility handler. 385 * @see #onVisibilityChangedProperty 386 * @see #getOnVisibilityChanged 387 */ 388 public final void setOnVisibilityChanged(EventHandler<WebEvent<Boolean>> handler) { onVisibilityChanged.set(handler); } 389 390 /** 391 * JavaScript window visibility handler property. This handler is invoked 392 * when a script running on the Web page changes visibility of the 393 * {@code window} object. 394 */ 395 public final ObjectProperty<EventHandler<WebEvent<Boolean>>> onVisibilityChangedProperty() { return onVisibilityChanged; } 396 397 398 private final ObjectProperty<Callback<PopupFeatures, WebEngine>> createPopupHandler 399 = new SimpleObjectProperty<Callback<PopupFeatures, WebEngine>>(this, "createPopupHandler", 400 p -> WebEngine.this); 401 402 /** 403 * Returns the JavaScript popup handler. 404 * @see #createPopupHandlerProperty 405 * @see #setCreatePopupHandler 406 */ 407 public final Callback<PopupFeatures, WebEngine> getCreatePopupHandler() { return createPopupHandler.get(); } 408 409 /** 410 * Sets the JavaScript popup handler. 411 * @see #createPopupHandlerProperty 412 * @see #getCreatePopupHandler 413 * @see PopupFeatures 414 */ 415 public final void setCreatePopupHandler(Callback<PopupFeatures, WebEngine> handler) { createPopupHandler.set(handler); } 416 417 /** 418 * JavaScript popup handler property. This handler is invoked when a script 419 * running on the Web page requests a popup to be created. 420 * <p>To satisfy this request a handler may create a new {@code WebEngine}, 421 * attach a visibility handler and optionally a resize handler, and return 422 * the newly created engine. To block the popup, a handler should return 423 * {@code null}. 424 * <p>By default, a popup handler is installed that opens popups in this 425 * {@code WebEngine}. 426 * 427 * @see PopupFeatures 428 */ 429 public final ObjectProperty<Callback<PopupFeatures, WebEngine>> createPopupHandlerProperty() { return createPopupHandler; } 430 431 432 private final ObjectProperty<Callback<String, Boolean>> confirmHandler 433 = new SimpleObjectProperty<Callback<String, Boolean>>(this, "confirmHandler"); 434 435 /** 436 * Returns the JavaScript {@code confirm} handler. 437 * @see #confirmHandlerProperty 438 * @see #setConfirmHandler 439 */ 440 public final Callback<String, Boolean> getConfirmHandler() { return confirmHandler.get(); } 441 442 /** 443 * Sets the JavaScript {@code confirm} handler. 444 * @see #confirmHandlerProperty 445 * @see #getConfirmHandler 446 */ 447 public final void setConfirmHandler(Callback<String, Boolean> handler) { confirmHandler.set(handler); } 448 449 /** 450 * JavaScript {@code confirm} handler property. This handler is invoked 451 * when a script running on the Web page calls the {@code confirm} function. 452 * <p>An implementation may display a dialog box with Yes and No options, 453 * and return the user's choice. 454 */ 455 public final ObjectProperty<Callback<String, Boolean>> confirmHandlerProperty() { return confirmHandler; } 456 457 458 private final ObjectProperty<Callback<PromptData, String>> promptHandler 459 = new SimpleObjectProperty<Callback<PromptData, String>>(this, "promptHandler"); 460 461 /** 462 * Returns the JavaScript {@code prompt} handler. 463 * @see #promptHandlerProperty 464 * @see #setPromptHandler 465 * @see PromptData 466 */ 467 public final Callback<PromptData, String> getPromptHandler() { return promptHandler.get(); } 468 469 /** 470 * Sets the JavaScript {@code prompt} handler. 471 * @see #promptHandlerProperty 472 * @see #getPromptHandler 473 * @see PromptData 474 */ 475 public final void setPromptHandler(Callback<PromptData, String> handler) { promptHandler.set(handler); } 476 477 /** 478 * JavaScript {@code prompt} handler property. This handler is invoked 479 * when a script running on the Web page calls the {@code prompt} function. 480 * <p>An implementation may display a dialog box with an text field, 481 * and return the user's input. 482 * 483 * @see PromptData 484 */ 485 public final ObjectProperty<Callback<PromptData, String>> promptHandlerProperty() { return promptHandler; } 486 487 /** 488 * The event handler called when an error occurs. 489 * 490 * @defaultValue {@code null} 491 * @since JavaFX 8.0 492 */ 493 private final ObjectProperty<EventHandler<WebErrorEvent>> onError = 494 new SimpleObjectProperty<>(this, "onError"); 495 496 public final EventHandler<WebErrorEvent> getOnError() { 497 return onError.get(); 498 } 499 500 public final void setOnError(EventHandler<WebErrorEvent> handler) { 501 onError.set(handler); 502 } 503 504 public final ObjectProperty<EventHandler<WebErrorEvent>> onErrorProperty() { 505 return onError; 506 } 507 508 509 /** 510 * Creates a new engine. 511 */ 512 public WebEngine() { 513 this(null); 514 } 515 516 /** 517 * Creates a new engine and loads a Web page into it. 518 */ 519 public WebEngine(String url) { 520 accessControlContext = AccessController.getContext(); 521 js2javaBridge = new JS2JavaBridge(this); 522 load(url); 523 } 524 525 /** 526 * Loads a Web page into this engine. This method starts asynchronous 527 * loading and returns immediately. 528 * @param url URL of the web page to load 529 */ 530 public void load(String url) { 531 checkThread(); 532 533 if (url == null) { 534 url = ""; 535 } 536 537 if (view.get() != null) { 538 _loadUrl(view.get().getNativeHandle(), url); 539 } 540 } 541 542 /* Loads a web page */ 543 private native void _loadUrl(long handle, String url); 544 545 /** 546 * Loads the given HTML content directly. This method is useful when you have an HTML 547 * String composed in memory, or loaded from some system which cannot be reached via 548 * a URL (for example, the HTML text may have come from a database). As with 549 * {@link #load(String)}, this method is asynchronous. 550 */ 551 public void loadContent(String content) { 552 loadContent(content, "text/html"); 553 } 554 555 /** 556 * Loads the given content directly. This method is useful when you have content 557 * composed in memory, or loaded from some system which cannot be reached via 558 * a URL (for example, the SVG text may have come from a database). As with 559 * {@link #load(String)}, this method is asynchronous. This method also allows you to 560 * specify the content type of the string being loaded, and so may optionally support 561 * other types besides just HTML. 562 */ 563 public void loadContent(String content, String contentType) { 564 checkThread(); 565 _loadContent(view.get().getNativeHandle(), content); 566 } 567 568 /* Loads the given content directly */ 569 private native void _loadContent(long handle, String content); 570 571 /** 572 * Reloads the current page, whether loaded from URL or directly from a String in 573 * one of the {@code loadContent} methods. 574 */ 575 public void reload() { 576 checkThread(); 577 } 578 579 /** 580 * Executes a script in the context of the current page. 581 * @return execution result, converted to a Java object using the following 582 * rules: 583 * <ul> 584 * <li>JavaScript Int32 is converted to {@code java.lang.Integer} 585 * <li>Other JavaScript numbers to {@code java.lang.Double} 586 * <li>JavaScript string to {@code java.lang.String} 587 * <li>JavaScript boolean to {@code java.lang.Boolean} 588 * <li>JavaScript {@code null} to {@code null} 589 * <li>Most JavaScript objects get wrapped as 590 * {@code netscape.javascript.JSObject} 591 * <li>JavaScript JSNode objects get mapped to instances of 592 * {@code netscape.javascript.JSObject}, that also implement 593 * {@code org.w3c.dom.Node} 594 * <li>A special case is the JavaScript class {@code JavaRuntimeObject} 595 * which is used to wrap a Java object as a JavaScript value - in this 596 * case we just extract the original Java value. 597 * </ul> 598 */ 599 public Object executeScript(String script) { 600 checkThread(); 601 602 StringBuilder b = new StringBuilder(js2javaBridge.getJavaBridge()).append(".fxEvaluate('"); 603 b.append(escapeScript(script)); 604 b.append("')"); 605 String retVal = _executeScript(view.get().getNativeHandle(), b.toString()); 606 607 try { 608 return js2javaBridge.decode(retVal); 609 } catch (Exception ex) { 610 System.err.println("Couldn't parse arguments. " + ex); 611 } 612 return null; 613 } 614 615 void executeScriptDirect(String script) { 616 _executeScript(view.get().getNativeHandle(), script); 617 } 618 619 /* Executes a script in the context of the current page */ 620 private native String _executeScript(long handle, String script); 621 622 void setView(WebView view) { 623 this.view.setValue(view); 624 } 625 626 private void stop() { 627 checkThread(); 628 } 629 630 private String escapeScript(String script) { 631 final int len = script.length(); 632 StringBuilder sb = new StringBuilder((int) (len * 1.2)); 633 for (int i = 0; i < len; i++) { 634 char ch = script.charAt(i); 635 switch (ch) { 636 case '\\': sb.append("\\\\"); break; 637 case '\'': sb.append("\\'"); break; 638 case '"': sb.append("\\\""); break; 639 case '\n': sb.append("\\n"); break; 640 case '\r': sb.append("\\r"); break; 641 case '\t': sb.append("\\t"); break; 642 default: sb.append(ch); 643 } 644 } 645 return sb.toString(); 646 } 647 648 /** 649 * Drives the {@code Timer} when {@code Timer.Mode.PLATFORM_TICKS} is set. 650 */ 651 private static final class PulseTimer { 652 653 // Used just to guarantee constant pulse activity. See RT-14433. 654 private static final AnimationTimer animation = 655 new AnimationTimer() { 656 @Override public void handle(long l) {} 657 }; 658 659 private static final TKPulseListener listener = 660 () -> { 661 // Note, the timer event is executed right in the notifyTick(), 662 // that is during the pulse event. This makes the timer more 663 // repsonsive, though prolongs the pulse. So far it causes no 664 // problems but nevertheless it should be kept in mind. 665 //Timer.getTimer().notifyTick(); 666 }; 667 668 private static void start(){ 669 Toolkit.getToolkit().addSceneTkPulseListener(listener); 670 animation.start(); 671 } 672 673 private static void stop() { 674 Toolkit.getToolkit().removeSceneTkPulseListener(listener); 675 animation.stop(); 676 } 677 } 678 679 static void checkThread() { 680 Toolkit.getToolkit().checkFxUserThread(); 681 } 682 683 private final class LoadWorker implements Worker<Void> { 684 685 private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<State>(this, "state", State.READY); 686 @Override public final State getState() { checkThread(); return state.get(); } 687 @Override public final ReadOnlyObjectProperty<State> stateProperty() { checkThread(); return state.getReadOnlyProperty(); } 688 private void updateState(State value) { 689 checkThread(); 690 this.state.set(value); 691 running.set(value == State.SCHEDULED || value == State.RUNNING); 692 } 693 694 /** 695 * @InheritDoc 696 */ 697 private final ReadOnlyObjectWrapper<Void> value = new ReadOnlyObjectWrapper<Void>(this, "value", null); 698 @Override public final Void getValue() { checkThread(); return value.get(); } 699 @Override public final ReadOnlyObjectProperty<Void> valueProperty() { checkThread(); return value.getReadOnlyProperty(); } 700 701 /** 702 * @InheritDoc 703 */ 704 private final ReadOnlyObjectWrapper<Throwable> exception = new ReadOnlyObjectWrapper<Throwable>(this, "exception"); 705 @Override public final Throwable getException() { checkThread(); return exception.get(); } 706 @Override public final ReadOnlyObjectProperty<Throwable> exceptionProperty() { checkThread(); return exception.getReadOnlyProperty(); } 707 708 /** 709 * @InheritDoc 710 */ 711 private final ReadOnlyDoubleWrapper workDone = new ReadOnlyDoubleWrapper(this, "workDone", -1); 712 @Override public final double getWorkDone() { checkThread(); return workDone.get(); } 713 @Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone.getReadOnlyProperty(); } 714 715 /** 716 * @InheritDoc 717 */ 718 private final ReadOnlyDoubleWrapper totalWorkToBeDone = new ReadOnlyDoubleWrapper(this, "totalWork", -1); 719 @Override public final double getTotalWork() { checkThread(); return totalWorkToBeDone.get(); } 720 @Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWorkToBeDone.getReadOnlyProperty(); } 721 722 /** 723 * @InheritDoc 724 */ 725 private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1); 726 @Override public final double getProgress() { checkThread(); return progress.get(); } 727 @Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress.getReadOnlyProperty(); } 728 private void updateProgress(double p) { 729 totalWorkToBeDone.set(100.0); 730 workDone.set(p * 100.0); 731 progress.set(p); 732 } 733 734 /** 735 * @InheritDoc 736 */ 737 private final ReadOnlyBooleanWrapper running = new ReadOnlyBooleanWrapper(this, "running", false); 738 @Override public final boolean isRunning() { checkThread(); return running.get(); } 739 @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running.getReadOnlyProperty(); } 740 741 /** 742 * @InheritDoc 743 */ 744 private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", ""); 745 @Override public final String getMessage() { return message.get(); } 746 @Override public final ReadOnlyStringProperty messageProperty() { return message.getReadOnlyProperty(); } 747 748 /** 749 * @InheritDoc 750 */ 751 private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", "WebEngine Loader"); 752 @Override public final String getTitle() { return title.get(); } 753 @Override public final ReadOnlyStringProperty titleProperty() { return title.getReadOnlyProperty(); } 754 755 /** 756 * Cancels the loading of the page. If called after the page has already 757 * been loaded, then this call takes no effect. 758 */ 759 @Override public boolean cancel() { 760 if (isRunning()) { 761 stop(); // this call indirectly sets state 762 return true; 763 } else { 764 return false; 765 } 766 } 767 768 private void cancelAndReset() { 769 cancel(); 770 exception.set(null); 771 message.set(""); 772 totalWorkToBeDone.set(-1); 773 workDone.set(-1); 774 progress.set(-1); 775 updateState(State.READY); 776 running.set(false); 777 } 778 779 private void dispatchLoadEvent(long frame, int state, 780 String url, String contentType, double workDone, int errorCode) 781 { 782 } 783 784 Throwable describeError(int errorCode) { 785 String reason = "Unknown error"; 786 787 return new Throwable(reason); 788 } 789 } 790 791 792 private final class DocumentProperty 793 extends ReadOnlyObjectPropertyBase<Document> { 794 795 private boolean available; 796 private Document document; 797 798 private void invalidate(boolean available) { 799 if (this.available || available) { 800 this.available = available; 801 this.document = null; 802 fireValueChangedEvent(); 803 } 804 } 805 806 public Document get() { 807 if (!this.available) { 808 return null; 809 } 810 this.document = getCurrentDocument(); 811 // if (this.document == null) { 812 // if (this.document == null) { 813 // this.available = false; 814 // } 815 // } 816 return this.document; 817 } 818 819 public Object getBean() { 820 return WebEngine.this; 821 } 822 823 public String getName() { 824 return "document"; 825 } 826 } 827 828 /////////////////////////////////////////////// 829 // JavaScript to Java bridge 830 /////////////////////////////////////////////// 831 832 private JS2JavaBridge js2javaBridge = null; 833 834 public void exportObject(String jsName, Object object) { 835 synchronized (loadedLock) { 836 if (js2javaBridge == null) { 837 js2javaBridge = new JS2JavaBridge(this); 838 } 839 js2javaBridge.exportObject(jsName, object); 840 } 841 } 842 843 844 interface PageListener { 845 void onLoadStarted(); 846 void onLoadFinished(); 847 void onLoadFailed(); 848 void onJavaCall(String args); 849 } 850 851 private PageListener pageListener = null; 852 private boolean loaded = false; 853 private final Object loadedLock = new Object(); 854 855 void setPageListener(PageListener listener) { 856 synchronized (loadedLock) { 857 pageListener = listener; 858 if (loaded) { 859 updateProgress(0.0); 860 updateState(Worker.State.SCHEDULED); 861 updateState(Worker.State.RUNNING); 862 pageListener.onLoadStarted(); 863 updateProgress(1.0); 864 updateState(Worker.State.SUCCEEDED); 865 pageListener.onLoadFinished(); 866 } 867 } 868 } 869 870 boolean isLoaded() { 871 return loaded; 872 } 873 874 // notifications are called from WebView 875 void notifyLoadStarted() { 876 synchronized (loadedLock) { 877 loaded = false; 878 updateProgress(0.0); 879 updateState(Worker.State.SCHEDULED); 880 updateState(Worker.State.RUNNING); 881 if (pageListener != null) { 882 pageListener.onLoadStarted(); 883 } 884 } 885 } 886 887 private String pageContent; 888 889 Document getCurrentDocument () { 890 Document document = null; 891 try { 892 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 893 DocumentBuilder builder = factory.newDocumentBuilder(); 894 document = builder.parse(new InputSource(new StringReader(pageContent))); 895 } 896 catch (Exception e) { 897 e.printStackTrace(); 898 } 899 return document; 900 } 901 902 void notifyLoadFinished(String loc, String content) { 903 synchronized (loadedLock) { 904 this.pageContent = "<html>"+content+"</html>"; 905 loaded = true; 906 updateProgress(1.0); 907 updateState(Worker.State.SUCCEEDED); 908 location.set(loc); 909 document.invalidate(true); 910 if (pageListener != null) { 911 pageListener.onLoadFinished(); 912 } 913 } 914 } 915 916 void notifyLoadFailed() { 917 synchronized (loadedLock) { 918 loaded = false; 919 updateProgress(0.0); 920 updateState(Worker.State.FAILED); 921 if (pageListener != null) { 922 pageListener.onLoadFailed(); 923 } 924 } 925 } 926 927 void notifyJavaCall(String arg) { 928 if (pageListener != null) { 929 pageListener.onJavaCall(arg); 930 } 931 } 932 933 void onAlertNotify(String text) { 934 if (getOnAlert() != null) { 935 dispatchWebEvent( 936 getOnAlert(), 937 new WebEvent<String>(this, WebEvent.ALERT, text)); 938 } 939 } 940 941 final private AccessControlContext accessControlContext; 942 943 AccessControlContext getAccessControlContext() { 944 return accessControlContext; 945 } 946 947 private void dispatchWebEvent(final EventHandler handler, final WebEvent ev) { 948 AccessController.doPrivileged(new PrivilegedAction<Void>() { 949 @Override 950 public Void run() { 951 handler.handle(ev); 952 return null; 953 } 954 }, getAccessControlContext()); 955 } 956 957 private class DebuggerImpl implements Debugger { 958 private Callback<String, Void> callback; 959 960 @Override 961 public boolean isEnabled() { 962 return false; 963 } 964 965 @Override 966 public void setEnabled(boolean enabled) { 967 968 } 969 970 @Override 971 public void sendMessage(String message) { 972 } 973 974 @Override 975 public Callback<String, Void> getMessageCallback() { 976 return callback; 977 } 978 979 @Override 980 public void setMessageCallback(Callback<String, Void> callback) { 981 this.callback = callback; 982 } 983 }; 984 }