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