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 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 javafx.stage.Stage;
  58 import javafx.stage.Window;
  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 javafx.css.converter.BooleanConverter;
  66 import javafx.css.converter.EnumConverter;
  67 import javafx.css.converter.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     // To handle stage pulse we need to know if currently webview and
 953     // tree is visible or not
 954     private boolean isTreeReallyVisible() {
 955         if (getScene() == null) {
 956             return false;
 957         }
 958 
 959         final Window window = getScene().getWindow();
 960 
 961         if (window == null) {
 962             return false;
 963         }
 964 
 965         boolean iconified = (window instanceof Stage) ? ((Stage)window).isIconified() : false;
 966 
 967         return impl_isTreeVisible()
 968                && window.isShowing()
 969                && window.getWidth() > 0
 970                && window.getHeight() > 0
 971                && !iconified;
 972     }
 973 
 974     private void handleStagePulse() {
 975         // The stage pulse occurs before the scene pulse.
 976         // Here the page content is updated before CSS/Layout/Sync pass
 977         // is initiated by the scene pulse. The update may
 978         // change the WebView children and, if so, the children should be
 979         // processed right away during the scene pulse.
 980 
 981         // The WebView node does not render its pending render queues
 982         // while it is invisible. Therefore, we should not schedule new
 983         // render queues while the WebView is invisible to prevent
 984         // the list of render queues from growing infinitely.
 985         // Also, if and when the WebView becomes invisible, the currently
 986         // pending render queues, if any, become obsolete and should be
 987         // discarded.
 988 
 989         if (page == null) return;
 990 
 991         boolean reallyVisible = isTreeReallyVisible();
 992 
 993         if (reallyVisible) {
 994             if (page.isDirty()) {
 995                 Scene.impl_setAllowPGAccess(true);
 996                 final NGWebView peer = impl_getPeer();
 997                 peer.update(); // creates new render queues
 998                 if (page.isRepaintPending()) {
 999                     impl_markDirty(DirtyBits.WEBVIEW_VIEW);
1000                 }
1001                 Scene.impl_setAllowPGAccess(false);
1002             }
1003         } else {
1004             page.dropRenderFrames();
1005         }
1006     }
1007 
1008     private void processMouseEvent(MouseEvent ev) {
1009         if (page == null) {
1010             return;
1011         }
1012 
1013         // RT-24511
1014         EventType<? extends MouseEvent> type = ev.getEventType();
1015         double x = ev.getX();
1016         double y = ev.getY();
1017         double screenX = ev.getScreenX();
1018         double screenY = ev.getScreenY();
1019         if (type == MouseEvent.MOUSE_EXITED) {
1020             type = MouseEvent.MOUSE_MOVED;
1021             x = Short.MIN_VALUE;
1022             y = Short.MIN_VALUE;
1023             Point2D screenPoint = localToScreen(x, y);
1024             if (screenPoint == null) {
1025                 return;
1026             }
1027             screenX = screenPoint.getX();
1028             screenY = screenPoint.getY();
1029         }
1030 
1031         final Integer id = idMap.get(type);
1032         if (id == null) {
1033             // not supported by webkit
1034             return;
1035         }
1036         WCMouseEvent mouseEvent =
1037                 new WCMouseEvent(id, idMap.get(ev.getButton()),
1038                     ev.getClickCount(), (int) x, (int) y,
1039                     (int) screenX, (int) screenY,
1040                     System.currentTimeMillis(),
1041                     ev.isShiftDown(), ev.isControlDown(), ev.isAltDown(),
1042                     ev.isMetaDown(), ev.isPopupTrigger());
1043         page.dispatchMouseEvent(mouseEvent);
1044         ev.consume();
1045     }
1046 
1047     private void processScrollEvent(ScrollEvent ev) {
1048         if (page == null) {
1049             return;
1050         }
1051         double dx = - ev.getDeltaX() * getFontScale() * getScaleX();
1052         double dy = - ev.getDeltaY() * getFontScale() * getScaleY();
1053         WCMouseWheelEvent wheelEvent =
1054                 new WCMouseWheelEvent((int)ev.getX(), (int)ev.getY(),
1055                     (int)ev.getScreenX(), (int)ev.getScreenY(),
1056                     System.currentTimeMillis(),
1057                     ev.isShiftDown(), ev.isControlDown(), ev.isAltDown(),
1058                     ev.isMetaDown(), (float)dx, (float)dy);
1059         page.dispatchMouseWheelEvent(wheelEvent);
1060         ev.consume();
1061     }
1062 
1063     private void processKeyEvent(KeyEvent ev) {
1064         if (page == null) return;
1065 
1066         String text = null;
1067         String keyIdentifier = null;
1068         int windowsVirtualKeyCode = 0;
1069         if(ev.getEventType() == KeyEvent.KEY_TYPED) {
1070             text = ev.getCharacter();
1071         } else {
1072             KeyCodeMap.Entry keyCodeEntry = KeyCodeMap.lookup(ev.getCode());
1073             keyIdentifier = keyCodeEntry.getKeyIdentifier();
1074             windowsVirtualKeyCode = keyCodeEntry.getWindowsVirtualKeyCode();
1075         }
1076 
1077         WCKeyEvent keyEvent = new WCKeyEvent(
1078                 idMap.get(ev.getEventType()),
1079                 text,
1080                 keyIdentifier,
1081                 windowsVirtualKeyCode,
1082                 ev.isShiftDown(), ev.isControlDown(),
1083                 ev.isAltDown(), ev.isMetaDown());
1084         if (page.dispatchKeyEvent(keyEvent)) {
1085             ev.consume();
1086         }
1087     }
1088 
1089     private InputMethodClientImpl getInputMethodClient() {
1090          if (imClient == null) {
1091              synchronized (this) {
1092                  if (imClient == null) {
1093                      imClient = new InputMethodClientImpl(this, page);
1094                  }
1095              }
1096          }
1097          return imClient;
1098     }
1099 
1100     private void processInputMethodEvent(InputMethodEvent ie) {
1101         if (page == null) {
1102             return;
1103         }
1104 
1105         if (!getInputMethodClient().getInputMethodState()) {
1106             ie.consume();
1107             return;
1108         }
1109 
1110         WCInputMethodEvent imEvent = InputMethodClientImpl.convertToWCInputMethodEvent(ie);
1111         if (page.dispatchInputMethodEvent(imEvent)) {
1112             ie.consume();
1113             return;
1114         }
1115     }
1116 
1117     private static final int WK_DND_ACTION_NONE = 0x0;
1118     private static final int WK_DND_ACTION_COPY = 0x1;
1119     private static final int WK_DND_ACTION_MOVE = 0x2;
1120     private static final int WK_DND_ACTION_LINK = 0x40000000;
1121 
1122     private static int getWKDndEventType(EventType  et) {
1123         int commandId = 0;
1124         if (et == DragEvent.DRAG_ENTERED)
1125             commandId = WebPage.DND_DST_ENTER;
1126         else if (et == DragEvent.DRAG_EXITED)
1127             commandId = WebPage.DND_DST_EXIT;
1128         else if (et == DragEvent.DRAG_OVER)
1129             commandId = WebPage.DND_DST_OVER;
1130         else if (et == DragEvent.DRAG_DROPPED)
1131             commandId = WebPage.DND_DST_DROP;
1132         return commandId;
1133     }
1134 
1135     private static int getWKDndAction(TransferMode... tms) {
1136         int dndActionId = WK_DND_ACTION_NONE;
1137         for (TransferMode tm : tms) {
1138            if (tm == TransferMode.COPY)
1139                dndActionId |=  WK_DND_ACTION_COPY;
1140            else if (tm == TransferMode.MOVE)
1141                dndActionId |=  WK_DND_ACTION_MOVE;
1142            else if (tm == TransferMode.LINK)
1143                dndActionId |=  WK_DND_ACTION_LINK;
1144         }
1145         return dndActionId;
1146     }
1147 
1148     private static TransferMode[] getFXDndAction(int wkDndAction) {
1149         LinkedList<TransferMode> tms = new LinkedList<TransferMode>();
1150         if ((wkDndAction & WK_DND_ACTION_COPY) != 0)
1151             tms.add(TransferMode.COPY);
1152         if ((wkDndAction & WK_DND_ACTION_MOVE) != 0)
1153             tms.add(TransferMode.MOVE);
1154         if ((wkDndAction & WK_DND_ACTION_LINK) != 0)
1155             tms.add(TransferMode.LINK);
1156         return tms.toArray(new TransferMode[0]);
1157     }
1158 
1159     private void registerEventHandlers() {
1160         addEventHandler(KeyEvent.ANY,
1161                 event -> {
1162                     processKeyEvent(event);
1163                 });
1164         addEventHandler(MouseEvent.ANY,
1165                 event -> {
1166                     processMouseEvent(event);
1167                     if (event.isDragDetect() && !page.isDragConfirmed()) {
1168                         //postpone drag recognition:
1169                         //Webkit cannot resolve here is it a drag
1170                         //or selection.
1171                         event.setDragDetect(false);
1172                     }
1173                 });
1174         addEventHandler(ScrollEvent.SCROLL,
1175                 event -> {
1176                     processScrollEvent(event);
1177                 });
1178         setOnInputMethodTextChanged(
1179                 event -> {
1180                     processInputMethodEvent(event);
1181                 });
1182 
1183         //Drop target implementation:
1184         EventHandler<DragEvent> destHandler = event -> {
1185             try {
1186                 Dragboard db = event.getDragboard();
1187                 LinkedList<String> mimes = new LinkedList<String>();
1188                 LinkedList<String> values = new LinkedList<String>();
1189                 for (DataFormat df : db.getContentTypes()) {
1190                     //TODO: extend to non-string serialized values.
1191                     //Please, look at the native code.
1192                     Object content = db.getContent(df);
1193                     if (content != null) {
1194                         for (String mime : df.getIdentifiers()) {
1195                             mimes.add(mime);
1196                             values.add(content.toString());
1197                         }
1198                     }
1199                 }
1200                 if (!mimes.isEmpty()) {
1201                     int wkDndEventType = getWKDndEventType(event.getEventType());
1202                     int wkDndAction = page.dispatchDragOperation(
1203                         wkDndEventType,
1204                         mimes.toArray(new String[0]), values.toArray(new String[0]),
1205                         (int)event.getX(), (int)event.getY(),
1206                         (int)event.getScreenX(), (int)event.getScreenY(),
1207                         getWKDndAction(db.getTransferModes().toArray(new TransferMode[0])));
1208 
1209                     //we cannot accept nothing on drop (we skip FX exception)
1210                     if (!(wkDndEventType == WebPage.DND_DST_DROP && wkDndAction == WK_DND_ACTION_NONE)) {
1211                         event.acceptTransferModes(getFXDndAction(wkDndAction));
1212                     }
1213                     event.consume();
1214                 }
1215             } catch (SecurityException ex) {
1216                 // Just ignore the exception
1217                 //ex.printStackTrace();
1218             }
1219         };
1220         setOnDragEntered(destHandler);
1221         setOnDragExited(destHandler);
1222         setOnDragOver(destHandler);
1223         setOnDragDropped(destHandler);
1224 
1225         //Drag source implementation:
1226         setOnDragDetected(event -> {
1227                if (page.isDragConfirmed()) {
1228                    page.confirmStartDrag();
1229                    event.consume();
1230                }
1231            });
1232         setOnDragDone(event -> {
1233                 page.dispatchDragOperation(
1234                     WebPage.DND_SRC_DROP,
1235                     null, null,
1236                     (int)event.getX(), (int)event.getY(),
1237                     (int)event.getScreenX(), (int)event.getScreenY(),
1238                     getWKDndAction(event.getAcceptedTransferMode()));
1239                 event.consume();
1240             });
1241 
1242         setInputMethodRequests(getInputMethodClient());
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 protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) {
1251         impl_intersects(pickRay, result);
1252     }
1253 
1254     @Override protected ObservableList<Node> getChildren() {
1255         return super.getChildren();
1256     }
1257 
1258     // Node stuff
1259 
1260     /**
1261      * @treatAsPrivate implementation detail
1262      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1263      */
1264     @Deprecated
1265     @Override protected NGNode impl_createPeer() {
1266         return new NGWebView();
1267     }
1268 
1269     /**
1270      * @treatAsPrivate implementation detail
1271      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1272      */
1273     @Deprecated
1274     @Override public BaseBounds impl_computeGeomBounds(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      * @treatAsPrivate implementation detail
1282      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1283      */
1284     @Deprecated
1285     @Override protected boolean impl_computeContains(double localX, double localY) {
1286         // Note: Local bounds contain test is already done by the caller. (Node.contains()).
1287         return true;
1288     }
1289 
1290     /**
1291      * @treatAsPrivate implementation detail
1292      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1293      */
1294     @Deprecated
1295     @Override public void impl_updatePeer() {
1296         super.impl_updatePeer();
1297         final NGWebView peer = impl_getPeer();
1298 
1299         if (impl_isDirty(DirtyBits.NODE_CONTENTS)) {
1300             peer.setPage(page);
1301         }
1302         if (impl_isDirty(DirtyBits.NODE_GEOMETRY)) {
1303             peer.resize((float)getWidth(), (float)getHeight());
1304         }
1305         if (impl_isDirty(DirtyBits.WEBVIEW_VIEW)) {
1306             peer.requestRender();
1307         }
1308     }
1309 
1310     static {
1311         idMap.put(MouseButton.NONE, WCMouseEvent.NOBUTTON);
1312         idMap.put(MouseButton.PRIMARY, WCMouseEvent.BUTTON1);
1313         idMap.put(MouseButton.MIDDLE, WCMouseEvent.BUTTON2);
1314         idMap.put(MouseButton.SECONDARY, WCMouseEvent.BUTTON3);
1315 
1316         idMap.put(MouseEvent.MOUSE_PRESSED, WCMouseEvent.MOUSE_PRESSED);
1317         idMap.put(MouseEvent.MOUSE_RELEASED, WCMouseEvent.MOUSE_RELEASED);
1318         idMap.put(MouseEvent.MOUSE_MOVED, WCMouseEvent.MOUSE_MOVED);
1319         idMap.put(MouseEvent.MOUSE_DRAGGED, WCMouseEvent.MOUSE_DRAGGED);
1320 
1321         idMap.put(KeyEvent.KEY_PRESSED, WCKeyEvent.KEY_PRESSED);
1322         idMap.put(KeyEvent.KEY_RELEASED, WCKeyEvent.KEY_RELEASED);
1323         idMap.put(KeyEvent.KEY_TYPED, WCKeyEvent.KEY_TYPED);
1324     }
1325 }