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