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 }