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 
  29 import javafx.css.CssMetaData;
  30 import javafx.css.StyleableBooleanProperty;
  31 import javafx.css.StyleableDoubleProperty;
  32 import javafx.css.StyleableObjectProperty;
  33 import javafx.css.StyleableProperty;
  34 import javafx.css.converter.BooleanConverter;
  35 import javafx.css.converter.EnumConverter;
  36 import javafx.css.converter.SizeConverter;
  37 import com.sun.javafx.geom.BaseBounds;
  38 import com.sun.javafx.geom.PickRay;
  39 import com.sun.javafx.geom.transform.Affine3D;
  40 import com.sun.javafx.geom.transform.BaseTransform;
  41 import com.sun.javafx.scene.DirtyBits;
  42 import com.sun.javafx.scene.NodeHelper;
  43 import com.sun.javafx.scene.input.PickResultChooser;
  44 import com.sun.java.scene.web.WebViewHelper;
  45 import com.sun.javafx.scene.SceneHelper;
  46 import com.sun.javafx.sg.prism.NGNode;
  47 import com.sun.javafx.tk.TKPulseListener;
  48 import com.sun.javafx.tk.Toolkit;
  49 import java.util.ArrayList;
  50 import java.util.Collections;
  51 import java.util.List;
  52 import javafx.beans.property.*;
  53 import javafx.beans.value.ChangeListener;
  54 import javafx.beans.value.ObservableValue;
  55 import javafx.collections.ObservableList;
  56 import javafx.css.Styleable;
  57 import javafx.geometry.Bounds;
  58 import javafx.scene.Node;
  59 import javafx.scene.Parent;
  60 import javafx.scene.text.FontSmoothingType;
  61 
  62 /**
  63  * {@code WebView} is a {@link javafx.scene.Node} that manages a
  64  * {@link WebEngine} and displays its content. The associated {@code WebEngine}
  65  * is created automatically at construction time and cannot be changed
  66  * afterwards. {@code WebView} handles mouse and some keyboard events, and
  67  * manages scrolling automatically, so there's no need to put it into a
  68  * {@code ScrollPane}.
  69  *
  70  * <p>{@code WebView} objects must be created and accessed solely from the
  71  * FX thread.
  72  * @since JavaFX 2.0
  73  */
  74 final public class WebView extends Parent {
  75     static {
  76         WebViewHelper.setWebViewAccessor(new WebViewHelper.WebViewAccessor() {
  77             @Override
  78                 public NGNode doCreatePeer(Node node) {
  79                 return ((WebView) node).doCreatePeer();
  80             }
  81 
  82             @Override
  83                 public void doUpdatePeer(Node node) {
  84                 ((WebView) node).doUpdatePeer();
  85             }
  86 
  87             @Override
  88             public void doTransformsChanged(Node node) {
  89                 ((WebView) node).doTransformsChanged();
  90             }
  91 
  92             @Override
  93             public BaseBounds doComputeGeomBounds(Node node,
  94             BaseBounds bounds, BaseTransform tx) {
  95                 return ((WebView) node).doComputeGeomBounds(bounds, tx);
  96             }
  97 
  98             @Override
  99             public boolean doComputeContains(Node node, double localX, double localY) {
 100                 return ((WebView) node).doComputeContains(localX, localY);
 101             }
 102 
 103             @Override
 104             public void doPickNodeLocal(Node node, PickRay localPickRay, PickResultChooser result) {
 105                 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
 106             }
 107         });
 108     }
 109 
 110     private static final boolean DEFAULT_CONTEXT_MENU_ENABLED = true;
 111     private static final FontSmoothingType DEFAULT_FONT_SMOOTHING_TYPE = FontSmoothingType.LCD;
 112     private static final double DEFAULT_ZOOM = 1.0;
 113     private static final double DEFAULT_FONT_SCALE = 1.0;
 114     private static final double DEFAULT_MIN_WIDTH = 0;
 115     private static final double DEFAULT_MIN_HEIGHT = 0;
 116     private static final double DEFAULT_PREF_WIDTH = 800;
 117     private static final double DEFAULT_PREF_HEIGHT = 600;
 118     private static final double DEFAULT_MAX_WIDTH = Double.MAX_VALUE;
 119     private static final double DEFAULT_MAX_HEIGHT = Double.MAX_VALUE;
 120 
 121     private final WebEngine engine;
 122     // pointer to native WebViewImpl
 123     private final long handle;
 124 
 125     /**
 126      * The stage pulse listener registered with the toolkit.
 127      * This field guarantees that the listener will exist throughout
 128      * the whole lifetime of the WebView node. This field is necessary
 129      * because the toolkit references its stage pulse listeners weakly.
 130      */
 131     private final TKPulseListener stagePulseListener;
 132 
 133     /**
 134      * Returns the {@code WebEngine} object.
 135      */
 136     public final WebEngine getEngine() {
 137         return engine;
 138     }
 139 
 140     private final ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper(this, "width");
 141 
 142     /**
 143      * Returns width of this {@code WebView}.
 144      */
 145     public final double getWidth() {
 146         return width.get();
 147     }
 148 
 149     /**
 150      * Width of this {@code WebView}.
 151      */
 152     public ReadOnlyDoubleProperty widthProperty() {
 153         return width.getReadOnlyProperty();
 154     }
 155 
 156     private final ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper(this, "height");
 157 
 158     /**
 159      * Returns height of this {@code WebView}.
 160      */
 161     public final double getHeight() {
 162         return height.get();
 163     }
 164 
 165     /**
 166      * Height of this {@code WebView}.
 167      */
 168     public ReadOnlyDoubleProperty heightProperty() {
 169         return height.getReadOnlyProperty();
 170     }
 171 
 172     /**
 173      * Zoom factor applied to the whole page contents.
 174      *
 175      * @defaultValue 1.0
 176      */
 177     private DoubleProperty zoom;
 178 
 179     /**
 180      * Sets current zoom factor applied to the whole page contents.
 181      * @param value zoom factor to be set
 182      * @see #zoomProperty()
 183      * @see #getZoom()
 184      * @since JavaFX 8.0
 185      */
 186     public final void setZoom(double value) {
 187         WebEngine.checkThread();
 188         zoomProperty().set(value);
 189     }
 190 
 191     /**
 192      * Returns current zoom factor applied to the whole page contents.
 193      * @return current zoom factor
 194      * @see #zoomProperty()
 195      * @see #setZoom(double value)
 196      * @since JavaFX 8.0
 197      */
 198     public final double getZoom() {
 199         return (this.zoom != null)
 200                 ? this.zoom.get()
 201                 : DEFAULT_ZOOM;
 202     }
 203 
 204     /**
 205      * Returns zoom property object.
 206      * @return zoom property object
 207      * @see #getZoom()
 208      * @see #setZoom(double value)
 209      * @since JavaFX 8.0
 210      */
 211     public final DoubleProperty zoomProperty() {
 212         if (zoom == null) {
 213             zoom = new StyleableDoubleProperty(DEFAULT_ZOOM) {
 214                 @Override public void invalidated() {
 215                     Toolkit.getToolkit().checkFxUserThread();
 216                 }
 217 
 218                 @Override public CssMetaData<WebView, Number> getCssMetaData() {
 219                     return StyleableProperties.ZOOM;
 220                 }
 221                 @Override public Object getBean() {
 222                     return WebView.this;
 223                 }
 224                 @Override public String getName() {
 225                     return "zoom";
 226                 }
 227             };
 228         }
 229         return zoom;
 230     }
 231 
 232     /**
 233      * Specifies scale factor applied to font. This setting affects
 234      * text content but not images and fixed size elements.
 235      *
 236      * @defaultValue 1.0
 237      */
 238     private DoubleProperty fontScale;
 239 
 240     public final void setFontScale(double value) {
 241         WebEngine.checkThread();
 242         fontScaleProperty().set(value);
 243     }
 244 
 245     public final double getFontScale() {
 246         return (this.fontScale != null)
 247                 ? this.fontScale.get()
 248                 : DEFAULT_FONT_SCALE;
 249     }
 250 
 251     public DoubleProperty fontScaleProperty() {
 252         if (fontScale == null) {
 253             fontScale = new StyleableDoubleProperty(DEFAULT_FONT_SCALE) {
 254                 @Override public void invalidated() {
 255                     Toolkit.getToolkit().checkFxUserThread();
 256                 }
 257                 @Override public CssMetaData<WebView, Number> getCssMetaData() {
 258                     return StyleableProperties.FONT_SCALE;
 259                 }
 260                 @Override public Object getBean() {
 261                     return WebView.this;
 262                 }
 263                 @Override public String getName() {
 264                     return "fontScale";
 265                 }
 266             };
 267         }
 268         return fontScale;
 269     }
 270 
 271     /**
 272      * Creates a {@code WebView} object.
 273      */
 274     public WebView() {
 275         long[] nativeHandle = new long[1];
 276         _initWebView(nativeHandle);
 277         getStyleClass().add("web-view");
 278         handle = nativeHandle[0];
 279         engine = new WebEngine();
 280         engine.setView(this);
 281 
 282         stagePulseListener = () -> {
 283             handleStagePulse();
 284         };
 285         focusedProperty().addListener((ov, t, t1) -> {
 286         });
 287         Toolkit.getToolkit().addStageTkPulseListener(stagePulseListener);
 288 
 289         final ChangeListener<Bounds> chListener = new ChangeListener<Bounds>() {
 290 
 291             @Override
 292             public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
 293                 NodeHelper.transformsChanged(WebView.this);
 294             }
 295         };
 296 
 297         parentProperty().addListener(new ChangeListener<Parent>(){
 298 
 299             @Override
 300             public void changed(ObservableValue<? extends Parent> observable, Parent oldValue, Parent newValue) {
 301                 if (oldValue != null && newValue == null) {
 302                     // webview has been removed from scene
 303                     _removeWebView(handle);
 304                 }
 305 
 306                 if (oldValue != null) {
 307                     do {
 308                         oldValue.boundsInParentProperty().removeListener(chListener);
 309                         oldValue = oldValue.getParent();
 310                     } while (oldValue != null);
 311                 }
 312 
 313                 if (newValue != null) {
 314                     do {
 315                         final Node n = newValue;
 316                         newValue.boundsInParentProperty().addListener(chListener);
 317                         newValue = newValue.getParent();
 318                     } while (newValue != null);
 319                 }
 320             }
 321 
 322         });
 323 
 324         layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
 325 
 326             @Override
 327             public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
 328                 Affine3D trans = calculateNodeToSceneTransform(WebView.this);
 329                 _setTransform(handle,
 330                 trans.getMxx(), trans.getMxy(), trans.getMxz(), trans.getMxt(),
 331                 trans.getMyx(), trans.getMyy(), trans.getMyz(), trans.getMyt(),
 332                 trans.getMzx(), trans.getMzy(), trans.getMzz(), trans.getMzt());
 333 
 334             }
 335 
 336         });
 337 
 338         impl_treeVisibleProperty().addListener(new ChangeListener<Boolean>() {
 339 
 340             @Override
 341             public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
 342                 _setVisible(handle, newValue);
 343             }
 344         });
 345     }
 346     // Resizing support. Allows arbitrary growing and shrinking.
 347     // Designed after javafx.scene.control.Control
 348 
 349     @Override public boolean isResizable() {
 350         return true;
 351     }
 352 
 353     @Override public void resize(double width, double height) {
 354         this.width.set(width);
 355         this.height.set(height);
 356         NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
 357         NodeHelper.geomChanged(this);
 358         _setWidth(handle, width);
 359         _setHeight(handle, height);
 360     }
 361 
 362     /**
 363      * Called during layout to determine the minimum width for this node.
 364      *
 365      * @return the minimum width that this node should be resized to during layout
 366      */
 367     @Override public final double minWidth(double height) {
 368         return getMinWidth();
 369     }
 370 
 371     /**
 372      * Called during layout to determine the minimum height for this node.
 373      *
 374      * @return the minimum height that this node should be resized to during layout
 375      */
 376     @Override public final double minHeight(double width) {
 377         return getMinHeight();
 378     }
 379 
 380 
 381     /**
 382      * Called during layout to determine the preferred width for this node.
 383      *
 384      * @return the preferred width that this node should be resized to during layout
 385      */
 386     @Override public final double prefWidth(double height) {
 387         return getPrefWidth();
 388     }
 389 
 390     /**
 391      * Called during layout to determine the preferred height for this node.
 392      *
 393      * @return the preferred height that this node should be resized to during layout
 394      */
 395     @Override public final double prefHeight(double width) {
 396         return getPrefHeight();
 397     }
 398     /**
 399      * Called during layout to determine the maximum width for this node.
 400      *
 401      * @return the maximum width that this node should be resized to during layout
 402      */
 403     @Override public final double maxWidth(double height) {
 404         return getMaxWidth();
 405     }
 406 
 407     /**
 408      * Called during layout to determine the maximum height for this node.
 409      *
 410      * @return the maximum height that this node should be resized to during layout
 411      */
 412     @Override public final double maxHeight(double width) {
 413         return getMaxHeight();
 414     }
 415 
 416     /**
 417      * Minimum width property.
 418      */
 419     public DoubleProperty minWidthProperty() {
 420         if (minWidth == null) {
 421             minWidth = new StyleableDoubleProperty(DEFAULT_MIN_WIDTH) {
 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_WIDTH;
 431                 }
 432                 @Override
 433                 public Object getBean() {
 434                     return WebView.this;
 435                 }
 436                 @Override
 437                 public String getName() {
 438                     return "minWidth";
 439                 }
 440             };
 441         }
 442         return minWidth;
 443     }
 444     private DoubleProperty minWidth;
 445 
 446     /**
 447      * Sets minimum width.
 448      */
 449     public final void setMinWidth(double value) {
 450         minWidthProperty().set(value);
 451         _setWidth(handle, value);
 452     }
 453 
 454     /**
 455      * Returns minimum width.
 456      */
 457     public final double getMinWidth() {
 458         return (this.minWidth != null)
 459                 ? this.minWidth.get()
 460                 : DEFAULT_MIN_WIDTH;
 461     }
 462 
 463     /**
 464      * Minimum height property.
 465      */
 466     public DoubleProperty minHeightProperty() {
 467         if (minHeight == null) {
 468             minHeight = new StyleableDoubleProperty(DEFAULT_MIN_HEIGHT) {
 469                 @Override
 470                 public void invalidated() {
 471                     if (getParent() != null) {
 472                         getParent().requestLayout();
 473                     }
 474                 }
 475                 @Override
 476                 public CssMetaData<WebView, Number> getCssMetaData() {
 477                     return StyleableProperties.MIN_HEIGHT;
 478                 }
 479                 @Override
 480                 public Object getBean() {
 481                     return WebView.this;
 482                 }
 483                 @Override
 484                 public String getName() {
 485                     return "minHeight";
 486                 }
 487             };
 488         }
 489         return minHeight;
 490     }
 491     private DoubleProperty minHeight;
 492 
 493     /**
 494      * Sets minimum height.
 495      */
 496     public final void setMinHeight(double value) {
 497         minHeightProperty().set(value);
 498         _setHeight(handle, value);
 499     }
 500 
 501     /**
 502      * Sets minimum height.
 503      */
 504     public final double getMinHeight() {
 505         return (this.minHeight != null)
 506                 ? this.minHeight.get()
 507                 : DEFAULT_MIN_HEIGHT;
 508     }
 509 
 510     /**
 511      * Convenience method for setting minimum width and height.
 512      */
 513     public void setMinSize(double minWidth, double minHeight) {
 514         setMinWidth(minWidth);
 515         setMinHeight(minHeight);
 516         _setWidth(handle, minWidth);
 517         _setHeight(handle, minHeight);
 518     }
 519 
 520     /**
 521      * Preferred width property.
 522      */
 523     public DoubleProperty prefWidthProperty() {
 524         if (prefWidth == null) {
 525             prefWidth = new StyleableDoubleProperty(DEFAULT_PREF_WIDTH) {
 526                 @Override
 527                 public void invalidated() {
 528                     if (getParent() != null) {
 529                         getParent().requestLayout();
 530                     }
 531                 }
 532                 @Override
 533                 public CssMetaData<WebView, Number> getCssMetaData() {
 534                     return StyleableProperties.PREF_WIDTH;
 535                 }
 536                 @Override
 537                 public Object getBean() {
 538                     return WebView.this;
 539                 }
 540                 @Override
 541                 public String getName() {
 542                     return "prefWidth";
 543                 }
 544             };
 545         }
 546         return prefWidth;
 547     }
 548     private DoubleProperty prefWidth;
 549 
 550     /**
 551      * Sets preferred width.
 552      */
 553     public final void setPrefWidth(double value) {
 554         prefWidthProperty().set(value);
 555         _setWidth(handle, value);
 556     }
 557 
 558     /**
 559      * Returns preferred width.
 560      */
 561     public final double getPrefWidth() {
 562         return (this.prefWidth != null)
 563                 ? this.prefWidth.get()
 564                 : DEFAULT_PREF_WIDTH;
 565     }
 566 
 567     /**
 568      * Preferred height property.
 569      */
 570     public DoubleProperty prefHeightProperty() {
 571         if (prefHeight == null) {
 572             prefHeight = new StyleableDoubleProperty(DEFAULT_PREF_HEIGHT) {
 573                 @Override
 574                 public void invalidated() {
 575                     if (getParent() != null) {
 576                         getParent().requestLayout();
 577                     }
 578                 }
 579                 @Override
 580                 public CssMetaData<WebView, Number> getCssMetaData() {
 581                     return StyleableProperties.PREF_HEIGHT;
 582                 }
 583                 @Override
 584                 public Object getBean() {
 585                     return WebView.this;
 586                 }
 587                 @Override
 588                 public String getName() {
 589                     return "prefHeight";
 590                 }
 591             };
 592         }
 593         return prefHeight;
 594     }
 595     private DoubleProperty prefHeight;
 596 
 597     /**
 598      * Sets preferred height.
 599      */
 600     public final void setPrefHeight(double value) {
 601         prefHeightProperty().set(value);
 602         _setHeight(handle, value);
 603     }
 604 
 605     /**
 606      * Returns preferred height.
 607      */
 608     public final double getPrefHeight() {
 609         return (this.prefHeight != null)
 610                 ? this.prefHeight.get()
 611                 : DEFAULT_PREF_HEIGHT;
 612     }
 613 
 614     /**
 615      * Convenience method for setting preferred width and height.
 616      */
 617     public void setPrefSize(double prefWidth, double prefHeight) {
 618         setPrefWidth(prefWidth);
 619         setPrefHeight(prefHeight);
 620         _setWidth(handle, prefWidth);
 621         _setHeight(handle, prefHeight);
 622     }
 623 
 624     /**
 625      * Maximum width property.
 626      */
 627     public DoubleProperty maxWidthProperty() {
 628         if (maxWidth == null) {
 629             maxWidth = new StyleableDoubleProperty(DEFAULT_MAX_WIDTH) {
 630                 @Override
 631                 public void invalidated() {
 632                     if (getParent() != null) {
 633                         getParent().requestLayout();
 634                     }
 635                 }
 636                 @Override
 637                 public CssMetaData<WebView, Number> getCssMetaData() {
 638                     return StyleableProperties.MAX_WIDTH;
 639                 }
 640                 @Override
 641                 public Object getBean() {
 642                     return WebView.this;
 643                 }
 644                 @Override
 645                 public String getName() {
 646                     return "maxWidth";
 647                 }
 648             };
 649         }
 650         return maxWidth;
 651     }
 652     private DoubleProperty maxWidth;
 653 
 654     /**
 655      * Sets maximum width.
 656      */
 657     public final void setMaxWidth(double value) {
 658         maxWidthProperty().set(value);
 659         _setWidth(handle, value);
 660     }
 661 
 662     /**
 663      * Returns maximum width.
 664      */
 665     public final double getMaxWidth() {
 666         return (this.maxWidth != null)
 667                 ? this.maxWidth.get()
 668                 : DEFAULT_MAX_WIDTH;
 669     }
 670 
 671     /**
 672      * Maximum height property.
 673      */
 674     public DoubleProperty maxHeightProperty() {
 675         if (maxHeight == null) {
 676             maxHeight = new StyleableDoubleProperty(DEFAULT_MAX_HEIGHT) {
 677                 @Override
 678                 public void invalidated() {
 679                     if (getParent() != null) {
 680                         getParent().requestLayout();
 681                     }
 682                 }
 683                 @Override
 684                 public CssMetaData<WebView, Number> getCssMetaData() {
 685                     return StyleableProperties.MAX_HEIGHT;
 686                 }
 687                 @Override
 688                 public Object getBean() {
 689                     return WebView.this;
 690                 }
 691                 @Override
 692                 public String getName() {
 693                     return "maxHeight";
 694                 }
 695             };
 696         }
 697         return maxHeight;
 698     }
 699     private DoubleProperty maxHeight;
 700 
 701     /**
 702      * Sets maximum height.
 703      */
 704     public final void setMaxHeight(double value) {
 705         maxHeightProperty().set(value);
 706         _setHeight(handle, value);
 707     }
 708 
 709     /**
 710      * Returns maximum height.
 711      */
 712     public final double getMaxHeight() {
 713         return (this.maxHeight != null)
 714                 ? this.maxHeight.get()
 715                 : DEFAULT_MAX_HEIGHT;
 716     }
 717 
 718     /**
 719      * Convenience method for setting maximum width and height.
 720      */
 721     public void setMaxSize(double maxWidth, double maxHeight) {
 722         setMaxWidth(maxWidth);
 723         setMaxHeight(maxHeight);
 724         _setWidth(handle, maxWidth);
 725         _setHeight(handle, maxHeight);
 726     }
 727 
 728 
 729     /**
 730      * Specifies a requested font smoothing type : gray or LCD.
 731      *
 732      * The width of the bounding box is defined by the widest row.
 733      *
 734      * Note: LCD mode doesn't apply in numerous cases, such as various
 735      * compositing modes, where effects are applied and very large glyphs.
 736      *
 737      * @defaultValue FontSmoothingType.LCD
 738      * @since JavaFX 2.2
 739      */
 740     private ObjectProperty<FontSmoothingType> fontSmoothingType;
 741 
 742     public final void setFontSmoothingType(FontSmoothingType value) {
 743         fontSmoothingTypeProperty().set(value);
 744     }
 745 
 746     public final FontSmoothingType getFontSmoothingType() {
 747         return (this.fontSmoothingType != null)
 748                 ? this.fontSmoothingType.get()
 749                 : DEFAULT_FONT_SMOOTHING_TYPE;
 750     }
 751 
 752     public final ObjectProperty<FontSmoothingType> fontSmoothingTypeProperty() {
 753         if (this.fontSmoothingType == null) {
 754             this.fontSmoothingType = new StyleableObjectProperty<FontSmoothingType>(DEFAULT_FONT_SMOOTHING_TYPE) {
 755                 @Override
 756                 public void invalidated() {
 757                     Toolkit.getToolkit().checkFxUserThread();
 758                 }
 759                 @Override
 760                 public CssMetaData<WebView, FontSmoothingType> getCssMetaData() {
 761                     return StyleableProperties.FONT_SMOOTHING_TYPE;
 762                 }
 763                 @Override
 764                 public Object getBean() {
 765                     return WebView.this;
 766                 }
 767                 @Override
 768                 public String getName() {
 769                     return "fontSmoothingType";
 770                 }
 771             };
 772         }
 773         return this.fontSmoothingType;
 774     }
 775 
 776     /**
 777      * Specifies whether context menu is enabled.
 778      *
 779      * @defaultValue true
 780      * @since JavaFX 2.2
 781      */
 782     private BooleanProperty contextMenuEnabled;
 783 
 784     public final void setContextMenuEnabled(boolean value) {
 785         contextMenuEnabledProperty().set(value);
 786     }
 787 
 788     public final boolean isContextMenuEnabled() {
 789         return contextMenuEnabled == null
 790                 ? DEFAULT_CONTEXT_MENU_ENABLED
 791                 : contextMenuEnabled.get();
 792     }
 793 
 794     public final BooleanProperty contextMenuEnabledProperty() {
 795         if (contextMenuEnabled == null) {
 796             contextMenuEnabled = new StyleableBooleanProperty(DEFAULT_CONTEXT_MENU_ENABLED) {
 797                 @Override public void invalidated() {
 798                     Toolkit.getToolkit().checkFxUserThread();
 799                 }
 800 
 801                 @Override public CssMetaData<WebView, Boolean> getCssMetaData() {
 802                     return StyleableProperties.CONTEXT_MENU_ENABLED;
 803                 }
 804 
 805                 @Override public Object getBean() {
 806                     return WebView.this;
 807                 }
 808 
 809                 @Override public String getName() {
 810                     return "contextMenuEnabled";
 811                 }
 812             };
 813         }
 814         return contextMenuEnabled;
 815     }
 816 
 817     /**
 818      * Super-lazy instantiation pattern from Bill Pugh.
 819      */
 820     private static final class StyleableProperties {
 821 
 822         private static final CssMetaData<WebView, Boolean> CONTEXT_MENU_ENABLED
 823                 = new CssMetaData<WebView, Boolean>(
 824                 "-fx-context-menu-enabled",
 825                 BooleanConverter.getInstance(),
 826                 DEFAULT_CONTEXT_MENU_ENABLED)
 827         {
 828             @Override public boolean isSettable(WebView view) {
 829                 return view.contextMenuEnabled == null || !view.contextMenuEnabled.isBound();
 830             }
 831             @Override public StyleableProperty<Boolean> getStyleableProperty(WebView view) {
 832                 return (StyleableProperty<Boolean>)view.contextMenuEnabledProperty();
 833             }
 834         };
 835 
 836         private static final CssMetaData<WebView, FontSmoothingType> FONT_SMOOTHING_TYPE
 837                 = new CssMetaData<WebView, FontSmoothingType>(
 838                 "-fx-font-smoothing-type",
 839                 new EnumConverter<FontSmoothingType>(FontSmoothingType.class),
 840                 DEFAULT_FONT_SMOOTHING_TYPE) {
 841             @Override
 842             public boolean isSettable(WebView view) {
 843                 return view.fontSmoothingType == null || !view.fontSmoothingType.isBound();
 844             }
 845             @Override
 846             public StyleableProperty<FontSmoothingType> getStyleableProperty(WebView view) {
 847                 return (StyleableProperty<FontSmoothingType>)view.fontSmoothingTypeProperty();
 848             }
 849         };
 850 
 851         private static final CssMetaData<WebView, Number> ZOOM
 852                 = new CssMetaData<WebView, Number>(
 853                 "-fx-zoom",
 854                 SizeConverter.getInstance(),
 855                 DEFAULT_ZOOM) {
 856             @Override public boolean isSettable(WebView view) {
 857                 return view.zoom == null || !view.zoom.isBound();
 858             }
 859             @Override public StyleableProperty<Number> getStyleableProperty(WebView view) {
 860                 return (StyleableProperty<Number>)view.zoomProperty();
 861             }
 862         };
 863 
 864         private static final CssMetaData<WebView, Number> FONT_SCALE
 865                 = new CssMetaData<WebView, Number>(
 866                 "-fx-font-scale",
 867                 SizeConverter.getInstance(),
 868                 DEFAULT_FONT_SCALE) {
 869             @Override
 870             public boolean isSettable(WebView view) {
 871                 return view.fontScale == null || !view.fontScale.isBound();
 872             }
 873             @Override
 874             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 875                 return (StyleableProperty<Number>)view.fontScaleProperty();
 876             }
 877         };
 878 
 879         private static final CssMetaData<WebView, Number> MIN_WIDTH
 880                 = new CssMetaData<WebView, Number>(
 881                 "-fx-min-width",
 882                 SizeConverter.getInstance(),
 883                 DEFAULT_MIN_WIDTH) {
 884             @Override
 885             public boolean isSettable(WebView view) {
 886                 return view.minWidth == null || !view.minWidth.isBound();
 887             }
 888             @Override
 889             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 890                 return (StyleableProperty<Number>)view.minWidthProperty();
 891             }
 892         };
 893 
 894         private static final CssMetaData<WebView, Number> MIN_HEIGHT
 895                 = new CssMetaData<WebView, Number>(
 896                 "-fx-min-height",
 897                 SizeConverter.getInstance(),
 898                 DEFAULT_MIN_HEIGHT) {
 899             @Override
 900             public boolean isSettable(WebView view) {
 901                 return view.minHeight == null || !view.minHeight.isBound();
 902             }
 903             @Override
 904             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 905                 return (StyleableProperty<Number>)view.minHeightProperty();
 906             }
 907         };
 908 
 909         private static final CssMetaData<WebView, Number> MAX_WIDTH
 910                 = new CssMetaData<WebView, Number>(
 911                 "-fx-max-width",
 912                 SizeConverter.getInstance(),
 913                 DEFAULT_MAX_WIDTH) {
 914             @Override
 915             public boolean isSettable(WebView view) {
 916                 return view.maxWidth == null || !view.maxWidth.isBound();
 917             }
 918             @Override
 919             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 920                 return (StyleableProperty<Number>)view.maxWidthProperty();
 921             }
 922         };
 923 
 924         private static final CssMetaData<WebView, Number> MAX_HEIGHT
 925                 = new CssMetaData<WebView, Number>(
 926                 "-fx-max-height",
 927                 SizeConverter.getInstance(),
 928                 DEFAULT_MAX_HEIGHT) {
 929             @Override
 930             public boolean isSettable(WebView view) {
 931                 return view.maxHeight == null || !view.maxHeight.isBound();
 932             }
 933             @Override
 934             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 935                 return (StyleableProperty<Number>)view.maxHeightProperty();
 936             }
 937         };
 938 
 939         private static final CssMetaData<WebView, Number> PREF_WIDTH
 940                 = new CssMetaData<WebView, Number>(
 941                 "-fx-pref-width",
 942                 SizeConverter.getInstance(),
 943                 DEFAULT_PREF_WIDTH) {
 944             @Override
 945             public boolean isSettable(WebView view) {
 946                 return view.prefWidth == null || !view.prefWidth.isBound();
 947             }
 948             @Override
 949             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 950                 return (StyleableProperty<Number>)view.prefWidthProperty();
 951             }
 952         };
 953 
 954         private static final CssMetaData<WebView, Number> PREF_HEIGHT
 955                 = new CssMetaData<WebView, Number>(
 956                 "-fx-pref-height",
 957                 SizeConverter.getInstance(),
 958                 DEFAULT_PREF_HEIGHT) {
 959             @Override
 960             public boolean isSettable(WebView view) {
 961                 return view.prefHeight == null || !view.prefHeight.isBound();
 962             }
 963             @Override
 964             public StyleableProperty<Number> getStyleableProperty(WebView view) {
 965                 return (StyleableProperty<Number>)view.prefHeightProperty();
 966             }
 967         };
 968 
 969         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 970 
 971         static {
 972             List<CssMetaData<? extends Styleable, ?>> styleables
 973                     = new ArrayList<CssMetaData<? extends Styleable, ?>>(Parent.getClassCssMetaData());
 974             styleables.add(CONTEXT_MENU_ENABLED);
 975             styleables.add(FONT_SMOOTHING_TYPE);
 976             styleables.add(ZOOM);
 977             styleables.add(FONT_SCALE);
 978             styleables.add(MIN_WIDTH);
 979             styleables.add(PREF_WIDTH);
 980             styleables.add(MAX_WIDTH);
 981             styleables.add(MIN_HEIGHT);
 982             styleables.add(PREF_HEIGHT);
 983             styleables.add(MAX_HEIGHT);
 984             STYLEABLES = Collections.unmodifiableList(styleables);
 985          }
 986     }
 987 
 988     /**
 989      * @return The CssMetaData associated with this class, which may include the
 990      * CssMetaData of its super classes.
 991      * @since JavaFX 8.0
 992      */
 993     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 994         return StyleableProperties.STYLEABLES;
 995     }
 996 
 997     /**
 998      * {@inheritDoc}
 999      * @since JavaFX 8.0
1000      */
1001     @Override
1002     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1003         return getClassCssMetaData();
1004     }
1005 
1006     // event handling
1007 
1008     private void handleStagePulse() {
1009         // The stage pulse occurs before the scene pulse.
1010         // Here the page content is updated before CSS/Layout/Sync pass
1011         // is initiated by the scene pulse. The update may
1012         // change the WebView children and, if so, the children should be
1013         // processed right away during the scene pulse.
1014 
1015         // The WebView node does not render its pending render queues
1016         // while it is invisible. Therefore, we should not schedule new
1017         // render queues while the WebView is invisible to prevent
1018         // the list of render queues from growing infinitely.
1019         // Also, if and when the WebView becomes invisible, the currently
1020         // pending render queues, if any, become obsolete and should be
1021         // discarded.
1022 
1023         boolean reallyVisible = impl_isTreeVisible()
1024                 && getScene() != null
1025                 && getScene().getWindow() != null
1026                 && getScene().getWindow().isShowing();
1027 
1028         if (reallyVisible) {
1029             if (NodeHelper.isDirty(this, DirtyBits.WEBVIEW_VIEW)) {
1030                 SceneHelper.setAllowPGAccess(true);
1031                 //getPGWebView().update(); // creates new render queues
1032                 SceneHelper.setAllowPGAccess(false);
1033             }
1034         } else {
1035             _setVisible(handle, false);
1036         }
1037     }
1038 
1039     @Override protected ObservableList<Node> getChildren() {
1040         return super.getChildren();
1041     }
1042 
1043     // Node stuff
1044 
1045     /*
1046      * Note: This method MUST only be called via its accessor method.
1047      */
1048     private NGNode doCreatePeer() {
1049         // return new NGWebView();
1050         return null; // iOS doesn't need this method.
1051     }
1052 
1053     /*
1054      * Note: This method MUST only be called via its accessor method.
1055      */
1056     private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
1057         bounds.deriveWithNewBounds(0, 0, 0, (float) getWidth(), (float)getHeight(), 0);
1058         tx.transform(bounds, bounds);
1059         return bounds;
1060     }
1061 
1062     /*
1063      * Note: This method MUST only be called via its accessor method.
1064      */
1065     private boolean doComputeContains(double localX, double localY) {
1066         // Note: Local bounds contain test is already done by the caller. (Node.contains()).
1067         return true;
1068     }
1069 
1070     /*
1071      * Note: This method MUST only be called via its accessor method.
1072      */
1073     private void doUpdatePeer() {
1074         //PGWebView peer = getPGWebView();
1075 
1076         if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) {
1077             //peer.resize((float)getWidth(), (float)getHeight());
1078         }
1079         if (NodeHelper.isDirty(this, DirtyBits.WEBVIEW_VIEW)) {
1080             //peer.requestRender();
1081         }
1082     }
1083 
1084     private static Affine3D calculateNodeToSceneTransform(Node node) {
1085         final Affine3D transform = new Affine3D();
1086         do {
1087             transform.preConcatenate(NodeHelper.getLeafTransform(node));
1088             node = node.getParent();
1089         } while (node != null);
1090 
1091         return transform;
1092     }
1093 
1094     /*
1095      * Note: This method MUST only be called via its accessor method.
1096      */
1097     private void doTransformsChanged() {
1098         Affine3D trans = calculateNodeToSceneTransform(this);
1099         _setTransform(handle,
1100                 trans.getMxx(), trans.getMxy(), trans.getMxz(), trans.getMxt(),
1101                 trans.getMyx(), trans.getMyy(), trans.getMyz(), trans.getMyt(),
1102                 trans.getMzx(), trans.getMzy(), trans.getMzz(), trans.getMzt());
1103     }
1104 
1105     long getNativeHandle() {
1106         return handle;
1107     }
1108 
1109 
1110     // native callbacks
1111     private void notifyLoadStarted() {
1112         engine.notifyLoadStarted();
1113     }
1114     private void notifyLoadFinished(String loc, String content) {
1115         engine.notifyLoadFinished(loc, content);
1116     }
1117     private void notifyLoadFailed() {
1118         engine.notifyLoadFailed();
1119     }
1120     private void notifyJavaCall(String arg) {
1121         engine.notifyJavaCall(arg);
1122     }
1123 
1124 
1125     /* Inits native WebView and returns its pointer in the given array */
1126     private native void _initWebView(long[] nativeHandle);
1127 
1128     /* Sets width of the native WebView  */
1129     private native void _setWidth(long handle, double w);
1130 
1131     /* Sets height of the native WebView  */
1132     private native void _setHeight(long handle, double h);
1133 
1134     /* Sets visibility of the native WebView  */
1135     private native void _setVisible(long handle, boolean v);
1136 
1137     /* Removes the native WebView from scene */
1138     private native void _removeWebView(long handle);
1139 
1140     /* Applies transform on the native WebView  */
1141     private native void _setTransform(long handle,
1142             double mxx, double mxy, double mxz, double mxt,
1143             double myx, double myy, double myz, double myt,
1144             double mzx, double mzy, double mzz, double mzt);
1145 }