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