1 /* 2 * Copyright (c) 2011, 2016, 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.java.scene.web.WebViewHelper; 29 import javafx.beans.property.BooleanProperty; 30 import javafx.beans.property.DoubleProperty; 31 import javafx.beans.property.ObjectProperty; 32 import javafx.beans.property.ReadOnlyDoubleProperty; 33 import javafx.beans.property.ReadOnlyDoubleWrapper; 34 import javafx.collections.ObservableList; 35 import javafx.css.CssMetaData; 36 import javafx.css.Styleable; 37 import javafx.css.StyleableBooleanProperty; 38 import javafx.css.StyleableDoubleProperty; 39 import javafx.css.StyleableObjectProperty; 40 import javafx.css.StyleableProperty; 41 import javafx.event.EventHandler; 42 import javafx.event.EventType; 43 import javafx.geometry.NodeOrientation; 44 import javafx.geometry.Point2D; 45 import javafx.scene.Node; 46 import javafx.scene.Parent; 47 import javafx.scene.Scene; 48 import javafx.scene.input.DataFormat; 49 import javafx.scene.input.DragEvent; 50 import javafx.scene.input.Dragboard; 51 import javafx.scene.input.InputMethodEvent; 52 import javafx.scene.input.KeyEvent; 53 import javafx.scene.input.MouseButton; 54 import javafx.scene.input.MouseEvent; 55 import javafx.scene.input.ScrollEvent; 56 import javafx.scene.input.TransferMode; 57 import javafx.scene.text.FontSmoothingType; 58 import javafx.stage.Stage; 59 import javafx.stage.Window; 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.HashMap; 63 import java.util.LinkedList; 64 import java.util.List; 65 import java.util.Map; 66 import javafx.css.converter.BooleanConverter; 67 import javafx.css.converter.EnumConverter; 68 import javafx.css.converter.SizeConverter; 69 import com.sun.javafx.geom.BaseBounds; 70 import com.sun.javafx.geom.PickRay; 71 import com.sun.javafx.geom.transform.BaseTransform; 72 import com.sun.javafx.scene.DirtyBits; 73 import com.sun.javafx.scene.NodeHelper; 74 import com.sun.javafx.scene.SceneHelper; 75 import com.sun.javafx.scene.input.PickResultChooser; 76 import com.sun.javafx.sg.prism.NGNode; 77 import com.sun.javafx.sg.prism.web.NGWebView; 78 import com.sun.javafx.tk.TKPulseListener; 79 import com.sun.javafx.tk.Toolkit; 80 import com.sun.javafx.webkit.InputMethodClientImpl; 81 import com.sun.javafx.webkit.KeyCodeMap; 82 import com.sun.webkit.WebPage; 83 import com.sun.webkit.event.WCFocusEvent; 84 import com.sun.webkit.event.WCInputMethodEvent; 85 import com.sun.webkit.event.WCKeyEvent; 86 import com.sun.webkit.event.WCMouseEvent; 87 import com.sun.webkit.event.WCMouseWheelEvent; 88 89 /** 90 * {@code WebView} is a {@link javafx.scene.Node} that manages a 91 * {@link WebEngine} and displays its content. The associated {@code WebEngine} 92 * is created automatically at construction time and cannot be changed 93 * afterwards. {@code WebView} handles mouse and some keyboard events, and 94 * manages scrolling automatically, so there's no need to put it into a 95 * {@code ScrollPane}. 96 * 97 * <p>{@code WebView} objects must be created and accessed solely from the 98 * FX thread. 99 * @since JavaFX 2.0 100 */ 101 final public class WebView extends Parent { 102 103 private static final Map<Object, Integer> idMap = new HashMap<Object, Integer>(); 104 105 private static final boolean DEFAULT_CONTEXT_MENU_ENABLED = true; 106 private static final FontSmoothingType DEFAULT_FONT_SMOOTHING_TYPE = FontSmoothingType.LCD; 107 private static final double DEFAULT_ZOOM = 1.0; 108 private static final double DEFAULT_FONT_SCALE = 1.0; 109 private static final double DEFAULT_MIN_WIDTH = 0; 110 private static final double DEFAULT_MIN_HEIGHT = 0; 111 private static final double DEFAULT_PREF_WIDTH = 800; 112 private static final double DEFAULT_PREF_HEIGHT = 600; 113 private static final double DEFAULT_MAX_WIDTH = Double.MAX_VALUE; 114 private static final double DEFAULT_MAX_HEIGHT = Double.MAX_VALUE; 115 116 private final WebPage page; 117 private final WebEngine engine; 118 private volatile InputMethodClientImpl imClient; 119 120 /** 121 * The stage pulse listener registered with the toolkit. 122 * This field guarantees that the listener will exist throughout 123 * the whole lifetime of the WebView node. This field is necessary 124 * because the toolkit references its stage pulse listeners weakly. 125 */ 126 private final TKPulseListener stagePulseListener; 127 128 /** 129 * Returns the {@code WebEngine} object. 130 */ 131 public final WebEngine getEngine() { 132 return engine; 133 } 134 135 private final ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper(this, "width"); 136 137 /** 138 * Returns width of this {@code WebView}. 139 */ 140 public final double getWidth() { 141 return width.get(); 142 } 143 144 /** 145 * Width of this {@code WebView}. 146 */ 147 public ReadOnlyDoubleProperty widthProperty() { 148 return width.getReadOnlyProperty(); 149 } 150 151 private final ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper(this, "height"); 152 153 /** 154 * Returns height of this {@code WebView}. 155 */ 156 public final double getHeight() { 157 return height.get(); 158 } 159 160 /** 161 * Height of this {@code WebView}. 162 */ 163 public ReadOnlyDoubleProperty heightProperty() { 164 return height.getReadOnlyProperty(); 165 } 166 167 /** 168 * Zoom factor applied to the whole page contents. 169 * 170 * @defaultValue 1.0 171 */ 172 private DoubleProperty zoom; 173 174 /** 175 * Sets current zoom factor applied to the whole page contents. 176 * @param value zoom factor to be set 177 * @see #zoomProperty() 178 * @see #getZoom() 179 * @since JavaFX 8.0 180 */ 181 public final void setZoom(double value) { 182 WebEngine.checkThread(); 183 zoomProperty().set(value); 184 } 185 186 /** 187 * Returns current zoom factor applied to the whole page contents. 188 * @return current zoom factor 189 * @see #zoomProperty() 190 * @see #setZoom(double value) 191 * @since JavaFX 8.0 192 */ 193 public final double getZoom() { 194 return (this.zoom != null) 195 ? this.zoom.get() 196 : DEFAULT_ZOOM; 197 } 198 199 /** 200 * Returns zoom property object. 201 * @return zoom property object 202 * @see #getZoom() 203 * @see #setZoom(double value) 204 * @since JavaFX 8.0 205 */ 206 public final DoubleProperty zoomProperty() { 207 if (zoom == null) { 208 zoom = new StyleableDoubleProperty(DEFAULT_ZOOM) { 209 @Override public void invalidated() { 210 Toolkit.getToolkit().checkFxUserThread(); 211 page.setZoomFactor((float) get(), false); 212 } 213 214 @Override public CssMetaData<WebView, Number> getCssMetaData() { 215 return StyleableProperties.ZOOM; 216 } 217 @Override public Object getBean() { 218 return WebView.this; 219 } 220 @Override public String getName() { 221 return "zoom"; 222 } 223 }; 224 } 225 return zoom; 226 } 227 228 /** 229 * Specifies scale factor applied to font. This setting affects 230 * text content but not images and fixed size elements. 231 * 232 * @defaultValue 1.0 233 */ 234 private DoubleProperty fontScale; 235 236 public final void setFontScale(double value) { 237 WebEngine.checkThread(); 238 fontScaleProperty().set(value); 239 } 240 241 public final double getFontScale() { 242 return (this.fontScale != null) 243 ? this.fontScale.get() 244 : DEFAULT_FONT_SCALE; 245 } 246 247 public DoubleProperty fontScaleProperty() { 248 if (fontScale == null) { 249 fontScale = new StyleableDoubleProperty(DEFAULT_FONT_SCALE) { 250 @Override public void invalidated() { 251 Toolkit.getToolkit().checkFxUserThread(); 252 page.setZoomFactor((float)get(), true); 253 } 254 @Override public CssMetaData<WebView, Number> getCssMetaData() { 255 return StyleableProperties.FONT_SCALE; 256 } 257 @Override public Object getBean() { 258 return WebView.this; 259 } 260 @Override public String getName() { 261 return "fontScale"; 262 } 263 }; 264 } 265 return fontScale; 266 } 267 268 { 269 // To initialize the class helper at the begining each constructor of this class 270 WebViewHelper.initHelper(this); 271 } 272 /** 273 * Creates a {@code WebView} object. 274 */ 275 public WebView() { 276 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 277 getStyleClass().add("web-view"); 278 engine = new WebEngine(); 279 engine.setView(this); 280 page = engine.getPage(); 281 page.setFontSmoothingType(DEFAULT_FONT_SMOOTHING_TYPE.ordinal()); 282 283 registerEventHandlers(); 284 stagePulseListener = () -> { 285 handleStagePulse(); 286 }; 287 focusedProperty().addListener((ov, t, t1) -> { 288 if (page != null) { 289 // Traversal direction is not currently available in FX. 290 WCFocusEvent focusEvent = new WCFocusEvent( 291 isFocused() ? WCFocusEvent.FOCUS_GAINED 292 : WCFocusEvent.FOCUS_LOST, 293 WCFocusEvent.UNKNOWN); 294 page.dispatchFocusEvent(focusEvent); 295 } 296 }); 297 setFocusTraversable(true); 298 Toolkit.getToolkit().addStageTkPulseListener(stagePulseListener); 299 } 300 301 // Resizing support. Allows arbitrary growing and shrinking. 302 // Designed after javafx.scene.control.Control 303 304 @Override public boolean isResizable() { 305 return true; 306 } 307 308 @Override public void resize(double width, double height) { 309 if ((width != this.width.get()) || (height != this.height.get())) { 310 this.width.set(width); 311 this.height.set(height); 312 NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY); 313 NodeHelper.geomChanged(this); 314 } 315 } 316 317 /** 318 * Called during layout to determine the minimum width for this node. 319 * 320 * @return the minimum width that this node should be resized to during layout 321 */ 322 @Override public final double minWidth(double height) { 323 final double result = getMinWidth(); 324 return Double.isNaN(result) || result < 0 ? 0 : result; 325 } 326 327 /** 328 * Called during layout to determine the minimum height for this node. 329 * 330 * @return the minimum height that this node should be resized to during layout 331 */ 332 @Override public final double minHeight(double width) { 333 final double result = getMinHeight(); 334 return Double.isNaN(result) || result < 0 ? 0 : result; 335 } 336 337 338 /** 339 * Called during layout to determine the preferred width for this node. 340 * 341 * @return the preferred width that this node should be resized to during layout 342 */ 343 @Override public final double prefWidth(double height) { 344 final double result = getPrefWidth(); 345 return Double.isNaN(result) || result < 0 ? 0 : result; 346 } 347 348 /** 349 * Called during layout to determine the preferred height for this node. 350 * 351 * @return the preferred height that this node should be resized to during layout 352 */ 353 @Override public final double prefHeight(double width) { 354 final double result = getPrefHeight(); 355 return Double.isNaN(result) || result < 0 ? 0 : result; 356 } 357 /** 358 * Called during layout to determine the maximum width for this node. 359 * 360 * @return the maximum width that this node should be resized to during layout 361 */ 362 @Override public final double maxWidth(double height) { 363 final double result = getMaxWidth(); 364 return Double.isNaN(result) || result < 0 ? 0 : result; 365 } 366 367 /** 368 * Called during layout to determine the maximum height for this node. 369 * 370 * @return the maximum height that this node should be resized to during layout 371 */ 372 @Override public final double maxHeight(double width) { 373 final double result = getMaxHeight(); 374 return Double.isNaN(result) || result < 0 ? 0 : result; 375 } 376 377 /** 378 * Minimum width property. 379 */ 380 public DoubleProperty minWidthProperty() { 381 if (minWidth == null) { 382 minWidth = new StyleableDoubleProperty(DEFAULT_MIN_WIDTH) { 383 @Override 384 public void invalidated() { 385 if (getParent() != null) { 386 getParent().requestLayout(); 387 } 388 } 389 @Override 390 public CssMetaData<WebView, Number> getCssMetaData() { 391 return StyleableProperties.MIN_WIDTH; 392 } 393 @Override 394 public Object getBean() { 395 return WebView.this; 396 } 397 @Override 398 public String getName() { 399 return "minWidth"; 400 } 401 }; 402 } 403 return minWidth; 404 } 405 private DoubleProperty minWidth; 406 407 /** 408 * Sets minimum width. 409 */ 410 public final void setMinWidth(double value) { 411 minWidthProperty().set(value); 412 } 413 414 /** 415 * Returns minimum width. 416 */ 417 public final double getMinWidth() { 418 return (this.minWidth != null) 419 ? this.minWidth.get() 420 : DEFAULT_MIN_WIDTH; 421 } 422 423 /** 424 * Minimum height property. 425 */ 426 public DoubleProperty minHeightProperty() { 427 if (minHeight == null) { 428 minHeight = new StyleableDoubleProperty(DEFAULT_MIN_HEIGHT) { 429 @Override 430 public void invalidated() { 431 if (getParent() != null) { 432 getParent().requestLayout(); 433 } 434 } 435 @Override 436 public CssMetaData<WebView, Number> getCssMetaData() { 437 return StyleableProperties.MIN_HEIGHT; 438 } 439 @Override 440 public Object getBean() { 441 return WebView.this; 442 } 443 @Override 444 public String getName() { 445 return "minHeight"; 446 } 447 }; 448 } 449 return minHeight; 450 } 451 private DoubleProperty minHeight; 452 453 /** 454 * Sets minimum height. 455 */ 456 public final void setMinHeight(double value) { 457 minHeightProperty().set(value); 458 } 459 460 /** 461 * Sets minimum height. 462 */ 463 public final double getMinHeight() { 464 return (this.minHeight != null) 465 ? this.minHeight.get() 466 : DEFAULT_MIN_HEIGHT; 467 } 468 469 /** 470 * Convenience method for setting minimum width and height. 471 */ 472 public void setMinSize(double minWidth, double minHeight) { 473 setMinWidth(minWidth); 474 setMinHeight(minHeight); 475 } 476 477 /** 478 * Preferred width property. 479 */ 480 public DoubleProperty prefWidthProperty() { 481 if (prefWidth == null) { 482 prefWidth = new StyleableDoubleProperty(DEFAULT_PREF_WIDTH) { 483 @Override 484 public void invalidated() { 485 if (getParent() != null) { 486 getParent().requestLayout(); 487 } 488 } 489 @Override 490 public CssMetaData<WebView, Number> getCssMetaData() { 491 return StyleableProperties.PREF_WIDTH; 492 } 493 @Override 494 public Object getBean() { 495 return WebView.this; 496 } 497 @Override 498 public String getName() { 499 return "prefWidth"; 500 } 501 }; 502 } 503 return prefWidth; 504 } 505 private DoubleProperty prefWidth; 506 507 /** 508 * Sets preferred width. 509 */ 510 public final void setPrefWidth(double value) { 511 prefWidthProperty().set(value); 512 } 513 514 /** 515 * Returns preferred width. 516 */ 517 public final double getPrefWidth() { 518 return (this.prefWidth != null) 519 ? this.prefWidth.get() 520 : DEFAULT_PREF_WIDTH; 521 } 522 523 /** 524 * Preferred height property. 525 */ 526 public DoubleProperty prefHeightProperty() { 527 if (prefHeight == null) { 528 prefHeight = new StyleableDoubleProperty(DEFAULT_PREF_HEIGHT) { 529 @Override 530 public void invalidated() { 531 if (getParent() != null) { 532 getParent().requestLayout(); 533 } 534 } 535 @Override 536 public CssMetaData<WebView, Number> getCssMetaData() { 537 return StyleableProperties.PREF_HEIGHT; 538 } 539 @Override 540 public Object getBean() { 541 return WebView.this; 542 } 543 @Override 544 public String getName() { 545 return "prefHeight"; 546 } 547 }; 548 } 549 return prefHeight; 550 } 551 private DoubleProperty prefHeight; 552 553 /** 554 * Sets preferred height. 555 */ 556 public final void setPrefHeight(double value) { 557 prefHeightProperty().set(value); 558 } 559 560 /** 561 * Returns preferred height. 562 */ 563 public final double getPrefHeight() { 564 return (this.prefHeight != null) 565 ? this.prefHeight.get() 566 : DEFAULT_PREF_HEIGHT; 567 } 568 569 /** 570 * Convenience method for setting preferred width and height. 571 */ 572 public void setPrefSize(double prefWidth, double prefHeight) { 573 setPrefWidth(prefWidth); 574 setPrefHeight(prefHeight); 575 } 576 577 /** 578 * Maximum width property. 579 */ 580 public DoubleProperty maxWidthProperty() { 581 if (maxWidth == null) { 582 maxWidth = new StyleableDoubleProperty(DEFAULT_MAX_WIDTH) { 583 @Override 584 public void invalidated() { 585 if (getParent() != null) { 586 getParent().requestLayout(); 587 } 588 } 589 @Override 590 public CssMetaData<WebView, Number> getCssMetaData() { 591 return StyleableProperties.MAX_WIDTH; 592 } 593 @Override 594 public Object getBean() { 595 return WebView.this; 596 } 597 @Override 598 public String getName() { 599 return "maxWidth"; 600 } 601 }; 602 } 603 return maxWidth; 604 } 605 private DoubleProperty maxWidth; 606 607 /** 608 * Sets maximum width. 609 */ 610 public final void setMaxWidth(double value) { 611 maxWidthProperty().set(value); 612 } 613 614 /** 615 * Returns maximum width. 616 */ 617 public final double getMaxWidth() { 618 return (this.maxWidth != null) 619 ? this.maxWidth.get() 620 : DEFAULT_MAX_WIDTH; 621 } 622 623 /** 624 * Maximum height property. 625 */ 626 public DoubleProperty maxHeightProperty() { 627 if (maxHeight == null) { 628 maxHeight = new StyleableDoubleProperty(DEFAULT_MAX_HEIGHT) { 629 @Override 630 public void invalidated() { 631 if (getParent() != null) { 632 getParent().requestLayout(); 633 } 634 } 635 @Override 636 public CssMetaData<WebView, Number> getCssMetaData() { 637 return StyleableProperties.MAX_HEIGHT; 638 } 639 @Override 640 public Object getBean() { 641 return WebView.this; 642 } 643 @Override 644 public String getName() { 645 return "maxHeight"; 646 } 647 }; 648 } 649 return maxHeight; 650 } 651 private DoubleProperty maxHeight; 652 653 /** 654 * Sets maximum height. 655 */ 656 public final void setMaxHeight(double value) { 657 maxHeightProperty().set(value); 658 } 659 660 /** 661 * Returns maximum height. 662 */ 663 public final double getMaxHeight() { 664 return (this.maxHeight != null) 665 ? this.maxHeight.get() 666 : DEFAULT_MAX_HEIGHT; 667 } 668 669 /** 670 * Convenience method for setting maximum width and height. 671 */ 672 public void setMaxSize(double maxWidth, double maxHeight) { 673 setMaxWidth(maxWidth); 674 setMaxHeight(maxHeight); 675 } 676 677 678 /** 679 * Specifies a requested font smoothing type : gray or LCD. 680 * 681 * The width of the bounding box is defined by the widest row. 682 * 683 * Note: LCD mode doesn't apply in numerous cases, such as various 684 * compositing modes, where effects are applied and very large glyphs. 685 * 686 * @defaultValue FontSmoothingType.LCD 687 * @since JavaFX 2.2 688 */ 689 private ObjectProperty<FontSmoothingType> fontSmoothingType; 690 691 public final void setFontSmoothingType(FontSmoothingType value) { 692 fontSmoothingTypeProperty().set(value); 693 } 694 695 public final FontSmoothingType getFontSmoothingType() { 696 return (this.fontSmoothingType != null) 697 ? this.fontSmoothingType.get() 698 : DEFAULT_FONT_SMOOTHING_TYPE; 699 } 700 701 public final ObjectProperty<FontSmoothingType> fontSmoothingTypeProperty() { 702 if (this.fontSmoothingType == null) { 703 this.fontSmoothingType = new StyleableObjectProperty<FontSmoothingType>(DEFAULT_FONT_SMOOTHING_TYPE) { 704 @Override 705 public void invalidated() { 706 Toolkit.getToolkit().checkFxUserThread(); 707 page.setFontSmoothingType(get().ordinal()); 708 } 709 @Override 710 public CssMetaData<WebView, FontSmoothingType> getCssMetaData() { 711 return StyleableProperties.FONT_SMOOTHING_TYPE; 712 } 713 @Override 714 public Object getBean() { 715 return WebView.this; 716 } 717 @Override 718 public String getName() { 719 return "fontSmoothingType"; 720 } 721 }; 722 } 723 return this.fontSmoothingType; 724 } 725 726 /** 727 * Specifies whether context menu is enabled. 728 * 729 * @defaultValue true 730 * @since JavaFX 2.2 731 */ 732 private BooleanProperty contextMenuEnabled; 733 734 public final void setContextMenuEnabled(boolean value) { 735 contextMenuEnabledProperty().set(value); 736 } 737 738 public final boolean isContextMenuEnabled() { 739 return contextMenuEnabled == null 740 ? DEFAULT_CONTEXT_MENU_ENABLED 741 : contextMenuEnabled.get(); 742 } 743 744 public final BooleanProperty contextMenuEnabledProperty() { 745 if (contextMenuEnabled == null) { 746 contextMenuEnabled = new StyleableBooleanProperty(DEFAULT_CONTEXT_MENU_ENABLED) { 747 @Override public void invalidated() { 748 Toolkit.getToolkit().checkFxUserThread(); 749 page.setContextMenuEnabled(get()); 750 } 751 752 @Override public CssMetaData<WebView, Boolean> getCssMetaData() { 753 return StyleableProperties.CONTEXT_MENU_ENABLED; 754 } 755 756 @Override public Object getBean() { 757 return WebView.this; 758 } 759 760 @Override public String getName() { 761 return "contextMenuEnabled"; 762 } 763 }; 764 } 765 return contextMenuEnabled; 766 } 767 768 /** 769 * Super-lazy instantiation pattern from Bill Pugh. 770 */ 771 private static final class StyleableProperties { 772 773 private static final CssMetaData<WebView, Boolean> CONTEXT_MENU_ENABLED 774 = new CssMetaData<WebView, Boolean>( 775 "-fx-context-menu-enabled", 776 BooleanConverter.getInstance(), 777 DEFAULT_CONTEXT_MENU_ENABLED) 778 { 779 @Override public boolean isSettable(WebView view) { 780 return view.contextMenuEnabled == null || !view.contextMenuEnabled.isBound(); 781 } 782 @Override public StyleableProperty<Boolean> getStyleableProperty(WebView view) { 783 return (StyleableProperty<Boolean>)view.contextMenuEnabledProperty(); 784 } 785 }; 786 787 private static final CssMetaData<WebView, FontSmoothingType> FONT_SMOOTHING_TYPE 788 = new CssMetaData<WebView, FontSmoothingType>( 789 "-fx-font-smoothing-type", 790 new EnumConverter<FontSmoothingType>(FontSmoothingType.class), 791 DEFAULT_FONT_SMOOTHING_TYPE) { 792 @Override 793 public boolean isSettable(WebView view) { 794 return view.fontSmoothingType == null || !view.fontSmoothingType.isBound(); 795 } 796 @Override 797 public StyleableProperty<FontSmoothingType> getStyleableProperty(WebView view) { 798 return (StyleableProperty<FontSmoothingType>)view.fontSmoothingTypeProperty(); 799 } 800 }; 801 802 private static final CssMetaData<WebView, Number> ZOOM 803 = new CssMetaData<WebView, Number>( 804 "-fx-zoom", 805 SizeConverter.getInstance(), 806 DEFAULT_ZOOM) { 807 @Override public boolean isSettable(WebView view) { 808 return view.zoom == null || !view.zoom.isBound(); 809 } 810 @Override public StyleableProperty<Number> getStyleableProperty(WebView view) { 811 return (StyleableProperty<Number>)view.zoomProperty(); 812 } 813 }; 814 815 private static final CssMetaData<WebView, Number> FONT_SCALE 816 = new CssMetaData<WebView, Number>( 817 "-fx-font-scale", 818 SizeConverter.getInstance(), 819 DEFAULT_FONT_SCALE) { 820 @Override 821 public boolean isSettable(WebView view) { 822 return view.fontScale == null || !view.fontScale.isBound(); 823 } 824 @Override 825 public StyleableProperty<Number> getStyleableProperty(WebView view) { 826 return (StyleableProperty<Number>)view.fontScaleProperty(); 827 } 828 }; 829 830 private static final CssMetaData<WebView, Number> MIN_WIDTH 831 = new CssMetaData<WebView, Number>( 832 "-fx-min-width", 833 SizeConverter.getInstance(), 834 DEFAULT_MIN_WIDTH) { 835 @Override 836 public boolean isSettable(WebView view) { 837 return view.minWidth == null || !view.minWidth.isBound(); 838 } 839 @Override 840 public StyleableProperty<Number> getStyleableProperty(WebView view) { 841 return (StyleableProperty<Number>)view.minWidthProperty(); 842 } 843 }; 844 845 private static final CssMetaData<WebView, Number> MIN_HEIGHT 846 = new CssMetaData<WebView, Number>( 847 "-fx-min-height", 848 SizeConverter.getInstance(), 849 DEFAULT_MIN_HEIGHT) { 850 @Override 851 public boolean isSettable(WebView view) { 852 return view.minHeight == null || !view.minHeight.isBound(); 853 } 854 @Override 855 public StyleableProperty<Number> getStyleableProperty(WebView view) { 856 return (StyleableProperty<Number>)view.minHeightProperty(); 857 } 858 }; 859 860 private static final CssMetaData<WebView, Number> MAX_WIDTH 861 = new CssMetaData<WebView, Number>( 862 "-fx-max-width", 863 SizeConverter.getInstance(), 864 DEFAULT_MAX_WIDTH) { 865 @Override 866 public boolean isSettable(WebView view) { 867 return view.maxWidth == null || !view.maxWidth.isBound(); 868 } 869 @Override 870 public StyleableProperty<Number> getStyleableProperty(WebView view) { 871 return (StyleableProperty<Number>)view.maxWidthProperty(); 872 } 873 }; 874 875 private static final CssMetaData<WebView, Number> MAX_HEIGHT 876 = new CssMetaData<WebView, Number>( 877 "-fx-max-height", 878 SizeConverter.getInstance(), 879 DEFAULT_MAX_HEIGHT) { 880 @Override 881 public boolean isSettable(WebView view) { 882 return view.maxHeight == null || !view.maxHeight.isBound(); 883 } 884 @Override 885 public StyleableProperty<Number> getStyleableProperty(WebView view) { 886 return (StyleableProperty<Number>)view.maxHeightProperty(); 887 } 888 }; 889 890 private static final CssMetaData<WebView, Number> PREF_WIDTH 891 = new CssMetaData<WebView, Number>( 892 "-fx-pref-width", 893 SizeConverter.getInstance(), 894 DEFAULT_PREF_WIDTH) { 895 @Override 896 public boolean isSettable(WebView view) { 897 return view.prefWidth == null || !view.prefWidth.isBound(); 898 } 899 @Override 900 public StyleableProperty<Number> getStyleableProperty(WebView view) { 901 return (StyleableProperty<Number>)view.prefWidthProperty(); 902 } 903 }; 904 905 private static final CssMetaData<WebView, Number> PREF_HEIGHT 906 = new CssMetaData<WebView, Number>( 907 "-fx-pref-height", 908 SizeConverter.getInstance(), 909 DEFAULT_PREF_HEIGHT) { 910 @Override 911 public boolean isSettable(WebView view) { 912 return view.prefHeight == null || !view.prefHeight.isBound(); 913 } 914 @Override 915 public StyleableProperty<Number> getStyleableProperty(WebView view) { 916 return (StyleableProperty<Number>)view.prefHeightProperty(); 917 } 918 }; 919 920 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 921 922 static { 923 List<CssMetaData<? extends Styleable, ?>> styleables 924 = new ArrayList<CssMetaData<? extends Styleable, ?>>(Parent.getClassCssMetaData()); 925 styleables.add(CONTEXT_MENU_ENABLED); 926 styleables.add(FONT_SMOOTHING_TYPE); 927 styleables.add(ZOOM); 928 styleables.add(FONT_SCALE); 929 styleables.add(MIN_WIDTH); 930 styleables.add(PREF_WIDTH); 931 styleables.add(MAX_WIDTH); 932 styleables.add(MIN_HEIGHT); 933 styleables.add(PREF_HEIGHT); 934 styleables.add(MAX_HEIGHT); 935 STYLEABLES = Collections.unmodifiableList(styleables); 936 } 937 } 938 939 /** 940 * @return The CssMetaData associated with this class, which may include the 941 * CssMetaData of its super classes. 942 * @since JavaFX 8.0 943 */ 944 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 945 return StyleableProperties.STYLEABLES; 946 } 947 948 /** 949 * {@inheritDoc} 950 * @since JavaFX 8.0 951 */ 952 @Override 953 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 954 return getClassCssMetaData(); 955 } 956 957 // event handling 958 959 // To handle stage pulse we need to know if currently webview and 960 // tree is visible or not 961 private boolean isTreeReallyVisible() { 962 if (getScene() == null) { 963 return false; 964 } 965 966 final Window window = getScene().getWindow(); 967 968 if (window == null) { 969 return false; 970 } 971 972 boolean iconified = (window instanceof Stage) ? ((Stage)window).isIconified() : false; 973 974 return NodeHelper.isTreeShowing(this) 975 && window.getWidth() > 0 976 && window.getHeight() > 0 977 && !iconified; 978 } 979 980 private void handleStagePulse() { 981 // The stage pulse occurs before the scene pulse. 982 // Here the page content is updated before CSS/Layout/Sync pass 983 // is initiated by the scene pulse. The update may 984 // change the WebView children and, if so, the children should be 985 // processed right away during the scene pulse. 986 987 // The WebView node does not render its pending render queues 988 // while it is invisible. Therefore, we should not schedule new 989 // render queues while the WebView is invisible to prevent 990 // the list of render queues from growing infinitely. 991 // Also, if and when the WebView becomes invisible, the currently 992 // pending render queues, if any, become obsolete and should be 993 // discarded. 994 995 if (page == null) return; 996 997 boolean reallyVisible = isTreeReallyVisible(); 998 999 if (reallyVisible) { 1000 if (page.isDirty()) { 1001 SceneHelper.setAllowPGAccess(true); 1002 final NGWebView peer = NodeHelper.getPeer(this); 1003 peer.update(); // creates new render queues 1004 if (page.isRepaintPending()) { 1005 NodeHelper.markDirty(this, DirtyBits.WEBVIEW_VIEW); 1006 } 1007 SceneHelper.setAllowPGAccess(false); 1008 } 1009 } else { 1010 page.dropRenderFrames(); 1011 } 1012 } 1013 1014 private void processMouseEvent(MouseEvent ev) { 1015 if (page == null) { 1016 return; 1017 } 1018 1019 // RT-24511 1020 EventType<? extends MouseEvent> type = ev.getEventType(); 1021 double x = ev.getX(); 1022 double y = ev.getY(); 1023 double screenX = ev.getScreenX(); 1024 double screenY = ev.getScreenY(); 1025 if (type == MouseEvent.MOUSE_EXITED) { 1026 type = MouseEvent.MOUSE_MOVED; 1027 x = Short.MIN_VALUE; 1028 y = Short.MIN_VALUE; 1029 Point2D screenPoint = localToScreen(x, y); 1030 if (screenPoint == null) { 1031 return; 1032 } 1033 screenX = screenPoint.getX(); 1034 screenY = screenPoint.getY(); 1035 } 1036 1037 final Integer id = idMap.get(type); 1038 if (id == null) { 1039 // not supported by webkit 1040 return; 1041 } 1042 WCMouseEvent mouseEvent = 1043 new WCMouseEvent(id, idMap.get(ev.getButton()), 1044 ev.getClickCount(), (int) x, (int) y, 1045 (int) screenX, (int) screenY, 1046 System.currentTimeMillis(), 1047 ev.isShiftDown(), ev.isControlDown(), ev.isAltDown(), 1048 ev.isMetaDown(), ev.isPopupTrigger()); 1049 page.dispatchMouseEvent(mouseEvent); 1050 ev.consume(); 1051 } 1052 1053 private void processScrollEvent(ScrollEvent ev) { 1054 if (page == null) { 1055 return; 1056 } 1057 double dx = - ev.getDeltaX() * getFontScale() * getScaleX(); 1058 double dy = - ev.getDeltaY() * getFontScale() * getScaleY(); 1059 WCMouseWheelEvent wheelEvent = 1060 new WCMouseWheelEvent((int)ev.getX(), (int)ev.getY(), 1061 (int)ev.getScreenX(), (int)ev.getScreenY(), 1062 System.currentTimeMillis(), 1063 ev.isShiftDown(), ev.isControlDown(), ev.isAltDown(), 1064 ev.isMetaDown(), (float)dx, (float)dy); 1065 page.dispatchMouseWheelEvent(wheelEvent); 1066 ev.consume(); 1067 } 1068 1069 private void processKeyEvent(KeyEvent ev) { 1070 if (page == null) return; 1071 1072 String text = null; 1073 String keyIdentifier = null; 1074 int windowsVirtualKeyCode = 0; 1075 if(ev.getEventType() == KeyEvent.KEY_TYPED) { 1076 text = ev.getCharacter(); 1077 } else { 1078 KeyCodeMap.Entry keyCodeEntry = KeyCodeMap.lookup(ev.getCode()); 1079 keyIdentifier = keyCodeEntry.getKeyIdentifier(); 1080 windowsVirtualKeyCode = keyCodeEntry.getWindowsVirtualKeyCode(); 1081 } 1082 1083 WCKeyEvent keyEvent = new WCKeyEvent( 1084 idMap.get(ev.getEventType()), 1085 text, 1086 keyIdentifier, 1087 windowsVirtualKeyCode, 1088 ev.isShiftDown(), ev.isControlDown(), 1089 ev.isAltDown(), ev.isMetaDown(), System.currentTimeMillis()); 1090 if (page.dispatchKeyEvent(keyEvent)) { 1091 ev.consume(); 1092 } 1093 } 1094 1095 private InputMethodClientImpl getInputMethodClient() { 1096 if (imClient == null) { 1097 synchronized (this) { 1098 if (imClient == null) { 1099 imClient = new InputMethodClientImpl(this, page); 1100 } 1101 } 1102 } 1103 return imClient; 1104 } 1105 1106 private void processInputMethodEvent(InputMethodEvent ie) { 1107 if (page == null) { 1108 return; 1109 } 1110 1111 if (!getInputMethodClient().getInputMethodState()) { 1112 ie.consume(); 1113 return; 1114 } 1115 1116 WCInputMethodEvent imEvent = InputMethodClientImpl.convertToWCInputMethodEvent(ie); 1117 if (page.dispatchInputMethodEvent(imEvent)) { 1118 ie.consume(); 1119 return; 1120 } 1121 } 1122 1123 private static final int WK_DND_ACTION_NONE = 0x0; 1124 private static final int WK_DND_ACTION_COPY = 0x1; 1125 private static final int WK_DND_ACTION_MOVE = 0x2; 1126 private static final int WK_DND_ACTION_LINK = 0x40000000; 1127 1128 private static int getWKDndEventType(EventType et) { 1129 int commandId = 0; 1130 if (et == DragEvent.DRAG_ENTERED) 1131 commandId = WebPage.DND_DST_ENTER; 1132 else if (et == DragEvent.DRAG_EXITED) 1133 commandId = WebPage.DND_DST_EXIT; 1134 else if (et == DragEvent.DRAG_OVER) 1135 commandId = WebPage.DND_DST_OVER; 1136 else if (et == DragEvent.DRAG_DROPPED) 1137 commandId = WebPage.DND_DST_DROP; 1138 return commandId; 1139 } 1140 1141 private static int getWKDndAction(TransferMode... tms) { 1142 int dndActionId = WK_DND_ACTION_NONE; 1143 for (TransferMode tm : tms) { 1144 if (tm == TransferMode.COPY) 1145 dndActionId |= WK_DND_ACTION_COPY; 1146 else if (tm == TransferMode.MOVE) 1147 dndActionId |= WK_DND_ACTION_MOVE; 1148 else if (tm == TransferMode.LINK) 1149 dndActionId |= WK_DND_ACTION_LINK; 1150 } 1151 return dndActionId; 1152 } 1153 1154 private static TransferMode[] getFXDndAction(int wkDndAction) { 1155 LinkedList<TransferMode> tms = new LinkedList<TransferMode>(); 1156 if ((wkDndAction & WK_DND_ACTION_COPY) != 0) 1157 tms.add(TransferMode.COPY); 1158 if ((wkDndAction & WK_DND_ACTION_MOVE) != 0) 1159 tms.add(TransferMode.MOVE); 1160 if ((wkDndAction & WK_DND_ACTION_LINK) != 0) 1161 tms.add(TransferMode.LINK); 1162 return tms.toArray(new TransferMode[0]); 1163 } 1164 1165 private void registerEventHandlers() { 1166 addEventHandler(KeyEvent.ANY, 1167 event -> { 1168 processKeyEvent(event); 1169 }); 1170 addEventHandler(MouseEvent.ANY, 1171 event -> { 1172 processMouseEvent(event); 1173 if (event.isDragDetect() && !page.isDragConfirmed()) { 1174 //postpone drag recognition: 1175 //Webkit cannot resolve here is it a drag 1176 //or selection. 1177 event.setDragDetect(false); 1178 } 1179 }); 1180 addEventHandler(ScrollEvent.SCROLL, 1181 event -> { 1182 processScrollEvent(event); 1183 }); 1184 setOnInputMethodTextChanged( 1185 event -> { 1186 processInputMethodEvent(event); 1187 }); 1188 1189 //Drop target implementation: 1190 EventHandler<DragEvent> destHandler = event -> { 1191 try { 1192 Dragboard db = event.getDragboard(); 1193 LinkedList<String> mimes = new LinkedList<String>(); 1194 LinkedList<String> values = new LinkedList<String>(); 1195 for (DataFormat df : db.getContentTypes()) { 1196 //TODO: extend to non-string serialized values. 1197 //Please, look at the native code. 1198 Object content = db.getContent(df); 1199 if (content != null) { 1200 for (String mime : df.getIdentifiers()) { 1201 mimes.add(mime); 1202 values.add(content.toString()); 1203 } 1204 } 1205 } 1206 if (!mimes.isEmpty()) { 1207 int wkDndEventType = getWKDndEventType(event.getEventType()); 1208 int wkDndAction = page.dispatchDragOperation( 1209 wkDndEventType, 1210 mimes.toArray(new String[0]), values.toArray(new String[0]), 1211 (int)event.getX(), (int)event.getY(), 1212 (int)event.getScreenX(), (int)event.getScreenY(), 1213 getWKDndAction(db.getTransferModes().toArray(new TransferMode[0]))); 1214 1215 //we cannot accept nothing on drop (we skip FX exception) 1216 if (!(wkDndEventType == WebPage.DND_DST_DROP && wkDndAction == WK_DND_ACTION_NONE)) { 1217 event.acceptTransferModes(getFXDndAction(wkDndAction)); 1218 } 1219 event.consume(); 1220 } 1221 } catch (SecurityException ex) { 1222 // Just ignore the exception 1223 //ex.printStackTrace(); 1224 } 1225 }; 1226 setOnDragEntered(destHandler); 1227 setOnDragExited(destHandler); 1228 setOnDragOver(destHandler); 1229 setOnDragDropped(destHandler); 1230 1231 //Drag source implementation: 1232 setOnDragDetected(event -> { 1233 if (page.isDragConfirmed()) { 1234 page.confirmStartDrag(); 1235 event.consume(); 1236 } 1237 }); 1238 setOnDragDone(event -> { 1239 page.dispatchDragOperation( 1240 WebPage.DND_SRC_DROP, 1241 null, null, 1242 (int)event.getX(), (int)event.getY(), 1243 (int)event.getScreenX(), (int)event.getScreenY(), 1244 getWKDndAction(event.getAcceptedTransferMode())); 1245 event.consume(); 1246 }); 1247 1248 setInputMethodRequests(getInputMethodClient()); 1249 } 1250 1251 /* 1252 * Note: This method MUST only be called via its accessor method. 1253 */ 1254 private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) { 1255 NodeHelper.intersects(this, pickRay, result); 1256 } 1257 1258 @Override protected ObservableList<Node> getChildren() { 1259 return super.getChildren(); 1260 } 1261 1262 // Node stuff 1263 1264 /* 1265 * Note: This method MUST only be called via its accessor method. 1266 */ 1267 private NGNode doCreatePeer() { 1268 return new NGWebView(); 1269 } 1270 1271 /* 1272 * Note: This method MUST only be called via its accessor method. 1273 */ 1274 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { 1275 bounds.deriveWithNewBounds(0, 0, 0, (float) getWidth(), (float)getHeight(), 0); 1276 tx.transform(bounds, bounds); 1277 return bounds; 1278 } 1279 1280 /* 1281 * Note: This method MUST only be called via its accessor method. 1282 */ 1283 private void doTransformsChanged() { 1284 } 1285 1286 /* 1287 * Note: This method MUST only be called via its accessor method. 1288 */ 1289 private boolean doComputeContains(double localX, double localY) { 1290 // Note: Local bounds contain test is already done by the caller. (Node.contains()). 1291 return true; 1292 } 1293 1294 /* 1295 * Note: This method MUST only be called via its accessor method. 1296 */ 1297 private void doUpdatePeer() { 1298 final NGWebView peer = NodeHelper.getPeer(this); 1299 1300 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 1301 peer.setPage(page); 1302 } 1303 if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) { 1304 peer.resize((float)getWidth(), (float)getHeight()); 1305 } 1306 if (NodeHelper.isDirty(this, DirtyBits.WEBVIEW_VIEW)) { 1307 peer.requestRender(); 1308 } 1309 } 1310 1311 static { 1312 WebViewHelper.setWebViewAccessor(new WebViewHelper.WebViewAccessor() { 1313 @Override 1314 public NGNode doCreatePeer(Node node) { 1315 return ((WebView) node).doCreatePeer(); 1316 } 1317 1318 @Override 1319 public void doUpdatePeer(Node node) { 1320 ((WebView) node).doUpdatePeer(); 1321 } 1322 1323 @Override 1324 public void doTransformsChanged(Node node) { 1325 ((WebView) node).doTransformsChanged(); 1326 } 1327 1328 @Override 1329 public BaseBounds doComputeGeomBounds(Node node, 1330 BaseBounds bounds, BaseTransform tx) { 1331 return ((WebView) node).doComputeGeomBounds(bounds, tx); 1332 } 1333 1334 @Override 1335 public boolean doComputeContains(Node node, double localX, double localY) { 1336 return ((WebView) node).doComputeContains(localX, localY); 1337 } 1338 1339 @Override 1340 public void doPickNodeLocal(Node node, PickRay localPickRay, 1341 PickResultChooser result) { 1342 ((WebView) node).doPickNodeLocal(localPickRay, result); 1343 } 1344 }); 1345 1346 idMap.put(MouseButton.NONE, WCMouseEvent.NOBUTTON); 1347 idMap.put(MouseButton.PRIMARY, WCMouseEvent.BUTTON1); 1348 idMap.put(MouseButton.MIDDLE, WCMouseEvent.BUTTON2); 1349 idMap.put(MouseButton.SECONDARY, WCMouseEvent.BUTTON3); 1350 1351 idMap.put(MouseEvent.MOUSE_PRESSED, WCMouseEvent.MOUSE_PRESSED); 1352 idMap.put(MouseEvent.MOUSE_RELEASED, WCMouseEvent.MOUSE_RELEASED); 1353 idMap.put(MouseEvent.MOUSE_MOVED, WCMouseEvent.MOUSE_MOVED); 1354 idMap.put(MouseEvent.MOUSE_DRAGGED, WCMouseEvent.MOUSE_DRAGGED); 1355 1356 idMap.put(KeyEvent.KEY_PRESSED, WCKeyEvent.KEY_PRESSED); 1357 idMap.put(KeyEvent.KEY_RELEASED, WCKeyEvent.KEY_RELEASED); 1358 idMap.put(KeyEvent.KEY_TYPED, WCKeyEvent.KEY_TYPED); 1359 } 1360 }