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