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