You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2017/09/03 12:48:56 UTC

[23/24] incubator-netbeans-html4j git commit: [INFRA-15006] Initial donation of HTML/Java NetBeans API. Equivalent to the content of html4j-donation-review.zip donated as part of ApacheNetBeansDonation1.zip with SHA256 being 7f2ca0f61953a190613c9a0fbcc1b

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
new file mode 100644
index 0000000..71c8547
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
@@ -0,0 +1,427 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.Preferences;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.concurrent.Worker;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Text;
+import javafx.scene.web.PromptData;
+import javafx.scene.web.WebEvent;
+import javafx.scene.web.WebView;
+import javafx.stage.Modality;
+import javafx.stage.Screen;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+import javafx.stage.WindowEvent;
+import javafx.util.Callback;
+
+/** This is an implementation class, to implement browser builder API. Just
+ * include this JAR on classpath and the browser builder API will find
+ * this implementation automatically.
+ */
+public class FXBrwsr extends Application {
+    private static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName());
+    private static FXBrwsr INSTANCE;
+    private static final CountDownLatch FINISHED = new CountDownLatch(1);
+    private BorderPane root;
+
+    public static synchronized WebView findWebView(final URL url, final FXPresenter onLoad) {
+        if (INSTANCE == null) {
+            final String callee = findCalleeClassName();
+            Executors.newFixedThreadPool(1).submit(new Runnable() {
+                @Override
+                public void run() {
+                    if (!Platform.isFxApplicationThread()) {
+                        try {
+                            Platform.runLater(this);
+                        } catch (IllegalStateException ex) {
+                            try {
+                                FXBrwsr.launch(FXBrwsr.class, callee);
+                            } catch (Throwable t) {
+                                t.printStackTrace();
+                            } finally {
+                                FINISHED.countDown();
+                            }
+                        }
+                    } else {
+                        FXBrwsr brwsr = new FXBrwsr();
+                        brwsr.start(new Stage(), callee);
+                        INSTANCE = brwsr;
+                        FINISHED.countDown();
+                    }
+                }
+            });
+        }
+        while (INSTANCE == null) {
+            try {
+                FXBrwsr.class.wait();
+            } catch (InterruptedException ex) {
+                // wait more
+            }
+        }
+        if (!Platform.isFxApplicationThread()) {
+            final WebView[] arr = {null};
+            final CountDownLatch waitForResult = new CountDownLatch(1);
+            Platform.runLater(new Runnable() {
+                @Override
+                public void run() {
+                    arr[0] = INSTANCE.newView(url, onLoad);
+                    waitForResult.countDown();
+                }
+            });
+            for (;;) {
+                try {
+                    waitForResult.await();
+                    break;
+                } catch (InterruptedException ex) {
+                    LOG.log(Level.INFO, null, ex);
+                }
+            }
+            return arr[0];
+        } else {
+            return INSTANCE.newView(url, onLoad);
+        }
+    }
+
+    static synchronized Stage findStage() throws InterruptedException {
+        while (INSTANCE == null) {
+            FXBrwsr.class.wait();
+        }
+        return INSTANCE.stage;
+    }
+
+    private Stage stage;
+
+    @Override
+    public void start(Stage primaryStage) throws Exception {
+        start(primaryStage, this.getParameters().getRaw().get(0));
+    }
+
+    final void start(Stage primaryStage, String callee) {
+        BorderPane r = new BorderPane();
+        Object[] arr = findInitialSize(callee);
+        Scene scene = new Scene(r, (Double)arr[2], (Double)arr[3]);
+        primaryStage.setScene(scene);
+        this.root = r;
+        this.stage = primaryStage;
+        synchronized (FXBrwsr.class) {
+            INSTANCE = this;
+            FXBrwsr.class.notifyAll();
+        }
+        primaryStage.setX((Double)arr[0]);
+        primaryStage.setY((Double)arr[1]);
+        if (arr[4] != null) {
+            scene.getWindow().setOnCloseRequest((EventHandler<WindowEvent>) arr[4]);
+        }
+        if (Boolean.getBoolean("fxpresenter.headless")) {
+            return;
+        }
+        primaryStage.show();
+    }
+
+    static String findCalleeClassName() {
+        StackTraceElement[] frames = new Exception().getStackTrace();
+        for (StackTraceElement e : frames) {
+            String cn = e.getClassName();
+            if (cn.startsWith("org.netbeans.html.")) { // NOI18N
+                continue;
+            }
+            if (cn.startsWith("net.java.html.")) { // NOI18N
+                continue;
+            }
+            if (cn.startsWith("java.")) { // NOI18N
+                continue;
+            }
+            if (cn.startsWith("javafx.")) { // NOI18N
+                continue;
+            }
+            if (cn.startsWith("com.sun.")) { // NOI18N
+                continue;
+            }
+            return cn;
+        }
+        return "org.netbeans.html"; // NOI18N
+    }
+
+    private static Object[] findInitialSize(String callee) {
+        final Preferences prefs = Preferences.userRoot().node(callee.replace('.', '/'));
+        Rectangle2D screen = Screen.getPrimary().getBounds();
+        double x = prefs.getDouble("x", screen.getWidth() * 0.05); // NOI18N
+        double y = prefs.getDouble("y", screen.getHeight() * 0.05); // NOI18N
+        double width = prefs.getDouble("width", screen.getWidth() * 0.9); // NOI18N
+        double height = prefs.getDouble("height", screen.getHeight() * 0.9); // NOI18N
+
+        Object[] arr = {
+            x, y, width, height, null
+        };
+
+        if (!callee.equals("org.netbeans.html")) { // NOI18N
+            arr[4] = new EventHandler<WindowEvent>() {
+                @Override
+                public void handle(WindowEvent event) {
+                    Window window = (Window) event.getSource();
+                    prefs.putDouble("x", window.getX()); // NOI18N
+                    prefs.putDouble("y", window.getY()); // NOI18N
+                    prefs.putDouble("width", window.getWidth()); // NOI18N
+                    prefs.putDouble("height", window.getHeight()); // NOI18N
+                }
+            };
+        }
+
+        return arr;
+    }
+
+    private WebView newView(final URL url, final FXPresenter onLoad) {
+        final WebView view = new WebView();
+        view.setContextMenuEnabled(false);
+        Stage newStage;
+        BorderPane bp;
+        if (root == null) {
+            newStage = new Stage();
+            newStage.initOwner(stage);
+            bp = new BorderPane();
+            newStage.setScene(new Scene(bp));
+            newStage.show();
+        } else {
+            bp = root;
+            newStage = stage;
+            root = null;
+        }
+
+        attachHandlers(view, newStage);
+        bp.setCenter(view);
+        final Worker<Void> w = view.getEngine().getLoadWorker();
+        w.stateProperty().addListener(new ChangeListener<Worker.State>() {
+            private String previous;
+
+            @Override
+            public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
+                if (newState.equals(Worker.State.SUCCEEDED)) {
+                    if (checkValid()) {
+                        FXConsole.register(view.getEngine());
+                        onLoad.onPageLoad();
+                    }
+                }
+                if (newState.equals(Worker.State.FAILED)) {
+                    throw new IllegalStateException("Failed to load " + url);
+                }
+            }
+            private boolean checkValid() {
+                final String crnt = view.getEngine().getLocation();
+                if (previous != null && !previous.equals(crnt)) {
+                    w.stateProperty().removeListener(this);
+                    return false;
+                }
+                previous = crnt;
+                return true;
+            }
+
+        });
+        class Title implements ChangeListener<String> {
+
+            private String title;
+
+            public Title() {
+                super();
+            }
+
+            @Override
+            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
+                title = view.getEngine().getTitle();
+                if (title != null) {
+                    stage.setTitle(title);
+                }
+            }
+        }
+        final Title x = new Title();
+        view.getEngine().titleProperty().addListener(x);
+        x.changed(null, null, null);
+        return view;
+    }
+
+    private static void attachHandlers(final WebView view, final Stage owner) {
+        view.getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
+            @Override
+            public void handle(WebEvent<String> t) {
+                final Stage dialogStage = new Stage();
+                dialogStage.initModality(Modality.WINDOW_MODAL);
+                dialogStage.initOwner(owner);
+                ResourceBundle r = ResourceBundle.getBundle("org/netbeans/html/boot/fx/Bundle"); // NOI18N
+                dialogStage.setTitle(r.getString("AlertTitle")); // NOI18N
+                final Button button = new Button(r.getString("AlertCloseButton")); // NOI18N
+                final Text text = new Text(t.getData());
+                VBox box = new VBox();
+                box.setAlignment(Pos.CENTER);
+                box.setSpacing(10);
+                box.setPadding(new Insets(10));
+                box.getChildren().addAll(text, button);
+                dialogStage.setScene(new Scene(box));
+                button.setCancelButton(true);
+                button.setOnAction(new CloseDialogHandler(dialogStage, null));
+                dialogStage.centerOnScreen();
+                dialogStage.showAndWait();
+            }
+        });
+        view.getEngine().setConfirmHandler(new Callback<String, Boolean>() {
+            @Override
+            public Boolean call(String question) {
+                final Stage dialogStage = new Stage();
+                dialogStage.initModality(Modality.WINDOW_MODAL);
+                dialogStage.initOwner(owner);
+                ResourceBundle r = ResourceBundle.getBundle("org/netbeans/html/boot/fx/Bundle"); // NOI18N
+                dialogStage.setTitle(r.getString("ConfirmTitle")); // NOI18N
+                final Button ok = new Button(r.getString("ConfirmOKButton")); // NOI18N
+                final Button cancel = new Button(r.getString("ConfirmCancelButton")); // NOI18N
+                final Text text = new Text(question);
+                final Insets ins = new Insets(10);
+                final VBox box = new VBox();
+                box.setAlignment(Pos.CENTER);
+                box.setSpacing(10);
+                box.setPadding(ins);
+                final HBox buttons = new HBox(10);
+                buttons.getChildren().addAll(ok, cancel);
+                buttons.setAlignment(Pos.CENTER);
+                buttons.setPadding(ins);
+                box.getChildren().addAll(text, buttons);
+                dialogStage.setScene(new Scene(box));
+                ok.setCancelButton(false);
+
+                final boolean[] res = new boolean[1];
+                ok.setOnAction(new CloseDialogHandler(dialogStage, res));
+                cancel.setCancelButton(true);
+                cancel.setOnAction(new CloseDialogHandler(dialogStage, null));
+                dialogStage.centerOnScreen();
+                dialogStage.showAndWait();
+                return res[0];
+            }
+        });
+        view.getEngine().setPromptHandler(new Callback<PromptData, String>() {
+            @Override
+            public String call(PromptData prompt) {
+                final Stage dialogStage = new Stage();
+                dialogStage.initModality(Modality.WINDOW_MODAL);
+                dialogStage.initOwner(owner);
+                ResourceBundle r = ResourceBundle.getBundle("org/netbeans/html/boot/fx/Bundle"); // NOI18N
+                dialogStage.setTitle(r.getString("PromptTitle")); // NOI18N
+                final Button ok = new Button(r.getString("PromptOKButton")); // NOI18N
+                final Button cancel = new Button(r.getString("PromptCancelButton")); // NOI18N
+                final Text text = new Text(prompt.getMessage());
+                final TextField line = new TextField();
+                if (prompt.getDefaultValue() != null) {
+                    line.setText(prompt.getDefaultValue());
+                }
+                final Insets ins = new Insets(10);
+                final VBox box = new VBox();
+                box.setAlignment(Pos.CENTER);
+                box.setSpacing(10);
+                box.setPadding(ins);
+                final HBox buttons = new HBox(10);
+                buttons.getChildren().addAll(ok, cancel);
+                buttons.setAlignment(Pos.CENTER);
+                buttons.setPadding(ins);
+                box.getChildren().addAll(text, line, buttons);
+                dialogStage.setScene(new Scene(box));
+                ok.setCancelButton(false);
+
+                final boolean[] res = new boolean[1];
+                ok.setOnAction(new CloseDialogHandler(dialogStage, res));
+                cancel.setCancelButton(true);
+                cancel.setOnAction(new CloseDialogHandler(dialogStage, null));
+                dialogStage.centerOnScreen();
+                dialogStage.showAndWait();
+                return res[0] ? line.getText() : null;
+            }
+        });
+    }
+
+    static void waitFinished() {
+        for (;;) {
+            try {
+                FINISHED.await();
+                break;
+            } catch (InterruptedException ex) {
+                LOG.log(Level.INFO, null, ex);
+            }
+        }
+    }
+
+    private static final class CloseDialogHandler implements EventHandler<ActionEvent> {
+        private final Stage dialogStage;
+        private final boolean[] res;
+
+        public CloseDialogHandler(Stage dialogStage, boolean[] res) {
+            this.dialogStage = dialogStage;
+            this.res = res;
+        }
+
+        @Override
+        public void handle(ActionEvent t) {
+            dialogStage.close();
+            if (res != null) {
+                res[0] = true;
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXConsole.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXConsole.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXConsole.java
new file mode 100644
index 0000000..d176ede
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXConsole.java
@@ -0,0 +1,84 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javafx.scene.web.WebEngine;
+import netscape.javascript.JSObject;
+
+/** This is an implementation package - just
+ * include its JAR on classpath and use official browser builder API
+ * to access the functionality.
+ * <p>
+ * Redirects JavaScript's messages to Java's {@link Logger}.
+ *
+ * @author Jaroslav Tulach
+ */
+public final class FXConsole {
+    static final Logger LOG = Logger.getLogger(FXConsole.class.getName());
+    
+    private FXConsole() {
+    }
+
+    static void register(WebEngine eng) {
+        JSObject fn = (JSObject) eng.executeScript(""
+            + "(function(attr, l, c) {"
+            + "  window.console[attr] = function(msg) { c.log(l, msg); };"
+            + "})"
+        );
+        FXConsole c = new FXConsole();
+        c.registerImpl(fn, "log", Level.INFO);
+        c.registerImpl(fn, "info", Level.INFO);
+        c.registerImpl(fn, "warn", Level.WARNING);
+        c.registerImpl(fn, "error", Level.SEVERE);
+    }
+    
+    private void registerImpl(JSObject eng, String attr, Level l) {
+        eng.call("call", null, attr, l, this);
+    }
+    
+    public void log(Level l, String msg) {
+        LOG.log(l, msg);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXInspect.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXInspect.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXInspect.java
new file mode 100644
index 0000000..2c8bc4f
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXInspect.java
@@ -0,0 +1,134 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javafx.application.Platform;
+import javafx.scene.web.WebEngine;
+import javafx.util.Callback;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+final class FXInspect implements Runnable {
+    static final Logger LOG = Logger.getLogger(FXInspect.class.getName());
+    
+    
+    private final WebEngine engine;
+    private final ObjectInputStream input;
+    private Dbgr dbg;
+    
+    private FXInspect(WebEngine engine, int port) throws IOException {
+        this.engine = engine;
+        
+        Socket socket = new Socket(InetAddress.getByName(null), port);
+        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
+        this.input = new ObjectInputStream(socket.getInputStream());
+        initializeDebugger(output);
+    }
+    
+    static boolean initialize(WebEngine engine) {
+        final int inspectPort = Integer.getInteger("netbeans.inspect.port", -1); // NOI18N
+        if (inspectPort != -1) {
+            try {
+                FXInspect inspector = new FXInspect(engine, inspectPort);
+                Thread t = new Thread(inspector, "FX<->NetBeans Inspector");
+                t.start();
+                return true;
+            } catch (IOException ex) {
+                LOG.log(Level.INFO, "Cannot connect to NetBeans IDE to port " + inspectPort, ex); // NOI18N
+            }
+        }
+        return false;
+    }
+    
+    private void initializeDebugger(final ObjectOutputStream output) {
+        Platform.runLater(new Runnable() {
+            @Override
+            public void run() {
+                dbg = new Dbgr(engine, new Callback<String,Void>() {
+                    @Override
+                    public Void call(String message) {
+                        try {
+                            byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
+                            output.writeInt(bytes.length);
+                            output.write(bytes);
+                            output.flush();
+                        } catch (IOException ioex) {
+                            ioex.printStackTrace();
+                        }
+                        return null;
+                    }
+                });
+            }
+        });
+    }
+
+    @Override
+    public void run() {
+        try {
+            while (true) {
+                int length = input.readInt();
+                byte[] bytes = new byte[length];
+                input.readFully(bytes);
+                final String message = new String(bytes, StandardCharsets.UTF_8);
+                Platform.runLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        dbg.sendMessage(message);
+                    }
+                });
+            }
+        } catch (IOException ex) {
+            LOG.log(Level.WARNING, null, ex);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
new file mode 100644
index 0000000..c54a1a1
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
@@ -0,0 +1,88 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import javafx.scene.web.WebView;
+import net.java.html.boot.BrowserBuilder;
+import org.netbeans.html.boot.spi.Fn;
+import org.openide.util.lookup.ServiceProvider;
+
+/** This is an implementation class, use {@link BrowserBuilder} API. Just
+ * include this JAR on classpath and the {@link BrowserBuilder} API will find
+ * this implementation automatically.
+ *
+ * @author Jaroslav Tulach
+ */
+@ServiceProvider(service = Fn.Presenter.class)
+public final class FXPresenter extends AbstractFXPresenter {
+    static {
+        try {
+            try {
+                Class<?> c = Class.forName("javafx.application.Platform");
+                // OK, on classpath
+            } catch (ClassNotFoundException classNotFoundException) {
+                Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+                m.setAccessible(true);
+                File f = new File(System.getProperty("java.home"), "lib/jfxrt.jar");
+                if (f.exists()) {
+                    URL l = f.toURI().toURL();
+                    m.invoke(ClassLoader.getSystemClassLoader(), l);
+                }
+            }
+        } catch (Exception ex) {
+            throw new LinkageError("Can't add jfxrt.jar on the classpath", ex);
+        }
+    }
+
+    protected void waitFinished() {
+        FXBrwsr.waitFinished();
+    }
+
+    protected WebView findView(final URL resource) {
+        return FXBrwsr.findWebView(resource, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java
new file mode 100644
index 0000000..20d4e10
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java
@@ -0,0 +1,437 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.prefs.Preferences;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Separator;
+import javafx.scene.control.Toggle;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.control.ToolBar;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.web.WebEngine;
+import javafx.scene.web.WebView;
+import javafx.stage.Screen;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+
+final class FXToolbar extends ToolBar {
+    private final ArrayList<ResizeBtn> resizeButtons;
+    private final WebView webView;
+    private final BorderPane container;
+    private final ToggleGroup resizeGroup = new ToggleGroup();
+    private final ComboBox<String> comboZoom = new ComboBox<String>();
+    private WatchDir watcher;
+    
+    FXToolbar(WebView wv, BorderPane container, boolean enableFirebug) {
+        this.webView = wv;
+        this.container = container;
+        
+        List<ResizeOption> options = ResizeOption.loadAll();
+        options.add( 0, ResizeOption.SIZE_TO_FIT );
+        resizeButtons = new ArrayList<ResizeBtn>( options.size() );
+
+        for( ResizeOption ro : options ) {
+            ResizeBtn button = new ResizeBtn(ro);
+            resizeButtons.add( button );
+            resizeGroup.getToggles().add( button );
+            getItems().add( button );
+        }
+        resizeButtons.get( 0 ).setSelected( true );
+        resizeGroup.selectedToggleProperty().addListener( new InvalidationListener() {
+
+            @Override
+            public void invalidated( Observable o ) {
+                resize();
+            }
+        });
+        
+        getItems().add( new Separator() );
+
+        getItems().add( comboZoom );
+        ArrayList<String> zoomModel = new ArrayList<String>( 6 );
+        zoomModel.add( "200%" ); //NOI18N
+        zoomModel.add( "150%" ); //NOI18N
+        zoomModel.add( "100%" ); //NOI18N
+        zoomModel.add( "75%" ); //NOI18N
+        zoomModel.add( "50%" ); //NOI18N
+        comboZoom.setItems( FXCollections.observableList( zoomModel ) );
+        comboZoom.setEditable( true );
+        comboZoom.setValue( "100%" ); //NOI18N
+        comboZoom.valueProperty().addListener( new ChangeListener<String>() {
+
+            @Override
+            public void changed( ObservableValue<? extends String> ov, String t, String t1 ) {
+                String newZoom = zoom( t1 );
+                comboZoom.setValue( newZoom );
+            }
+        });
+        
+        getItems().add(new Separator());
+        final CheckBox automatic = new CheckBox("Automatic");
+        final Preferences prefs = Preferences.userNodeForPackage(FXToolbar.class);
+        final String ar = "automaticReload"; // NOI18N
+        automatic.setSelected(prefs.getBoolean(ar, true));
+        getItems().add(automatic);
+        final Button reload = new Button("Reload");
+        getItems().add(reload);
+        reload.setOnAction(new EventHandler<ActionEvent>() {
+            @Override
+            public void handle(ActionEvent event) {
+                webView.getEngine().reload();
+            }
+        });
+        automatic.setOnAction(new EventHandler<ActionEvent>() {
+            @Override
+            public void handle(ActionEvent event) {
+                prefs.putBoolean(ar, automatic.isSelected());
+                listenOnChanges(automatic.isSelected());
+            }
+        });
+        webView.getEngine().locationProperty().addListener(new ChangeListener<String>() {
+            @Override
+            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                listenOnChanges(automatic.isSelected());
+            }
+        });
+        if (enableFirebug){
+        final Button firebug = new Button("Firebug");
+        getItems().add(firebug);
+        firebug.setOnAction(new EventHandler<ActionEvent>() {
+
+            @Override
+            public void handle(ActionEvent event) {
+                enableFirebug(webView.getEngine());
+                firebug.setDisable(true);
+            }
+        });}
+    }
+
+    private String zoom( String zoomFactor ) {
+        if( zoomFactor.trim().isEmpty() )
+            return null;
+
+        try {
+            zoomFactor = zoomFactor.replaceAll( "\\%", ""); //NOI18N
+            zoomFactor = zoomFactor.trim();
+            double zoom = Double.parseDouble( zoomFactor );
+            zoom = Math.abs( zoom )/100;
+            if( zoom <= 0.0 )
+                return null;
+            webView.setScaleX(zoom);
+            webView.setScaleY(zoom);
+            webView.setScaleZ(zoom);
+            return (int)(100*zoom) + "%"; //NOI18N
+        } catch( NumberFormatException nfe ) {
+            //ignore
+        }
+        return null;
+    }
+
+    private void resize() {
+        Toggle selection = resizeGroup.getSelectedToggle();
+        if( selection instanceof ResizeBtn ) {
+            ResizeOption ro = ((ResizeBtn)selection).getResizeOption();
+            if( ro == ResizeOption.SIZE_TO_FIT ) {
+                _autofit();
+            } else {
+                _resize( ro.getWidth(), ro.getHeight() );
+            }
+        }
+
+    }
+
+    private void _resize(final double width, final double height) {
+        Window window = container.getScene().getWindow();
+        // size difference between root node and window depends on OS and Decorations
+        double diffY = window.getHeight() - container.getHeight();
+        double diffX = window.getWidth() - container.getWidth();
+
+        webView.setMaxWidth(width);
+        webView.setMaxHeight(height);
+        webView.setMinWidth(width);
+        webView.setMinHeight(height);
+        javafx.geometry.Rectangle2D screenBounds = Screen.getPrimary().getBounds();
+        double scaleX = screenBounds.getWidth() / ( width + diffX );
+        double scaleY = screenBounds.getHeight() / ( height + diffY );
+        // calculate scale factor if too big for device, the .1 adds some padding
+        double scale = Math.min(Math.min(scaleX, scaleY), 1.1) - .1;
+        webView.setScaleX(scale);
+        webView.setScaleY(scale);
+        container.getScene().setRoot(new Group());
+        ((Stage)window).setScene(new Scene(container, width * scale, height * scale));
+    }
+
+    private void _autofit() {
+        if (container.getCenter() != webView) {
+            container.setCenter(webView);
+        }
+        webView.setMaxWidth( Integer.MAX_VALUE );
+        webView.setMaxHeight( Integer.MAX_VALUE );
+        webView.setMinWidth( -1 );
+        webView.setMinHeight( -1 );
+        webView.autosize();
+    }
+
+    /**
+     * Button to resize the browser window.
+     * Taken from NetBeans. Kept GPLwithCPEx license.
+     * Portions Copyright 2012 Oracle.
+     *
+     * @author S. Aubrecht
+     */
+    static final class ResizeBtn extends ToggleButton {
+
+        private final ResizeOption resizeOption;
+
+        ResizeBtn(ResizeOption resizeOption) {
+            super(null, new ImageView(toImage(resizeOption)));
+            this.resizeOption = resizeOption;
+            setTooltip(new Tooltip(resizeOption.getToolTip()));
+        }
+
+        ResizeOption getResizeOption() {
+            return resizeOption;
+        }
+
+        static Image toImage(ResizeOption ro) {
+            if (ro == ResizeOption.SIZE_TO_FIT) {
+                return ResizeOption.Type.CUSTOM.getImage();
+            }
+            return ro.getType().getImage();
+        }
+    }
+
+    /**
+     * Immutable value class describing a single button to resize web browser window.
+     * Taken from NetBeans. Kept GPLwithCPEx license.
+     * Portions Copyrighted 2012 Sun Microsystems, Inc.
+     *
+     * @author S. Aubrecht
+     */
+    static final class ResizeOption {
+
+        private final Type type;
+        private final String displayName;
+        private final int width;
+        private final int height;
+        private final boolean isDefault;
+
+        enum Type {
+            DESKTOP("desktop.png"), 
+            TABLET_PORTRAIT("tabletPortrait.png"), 
+            TABLET_LANDSCAPE("tabletLandscape.png"), 
+            SMARTPHONE_PORTRAIT("handheldPortrait.png"), 
+            SMARTPHONE_LANDSCAPE("handheldLandscape.png"), 
+            WIDESCREEN("widescreen.png"), 
+            NETBOOK("netbook.png"), 
+            CUSTOM("sizeToFit.png");
+            
+            
+            private final String resource;
+
+            private Type(String r) {
+                resource = r;
+            }
+
+            public Image getImage() {
+                return new Image(Type.class.getResourceAsStream(resource));
+            }
+        }
+
+        private ResizeOption(Type type, String displayName, int width, int height, boolean showInToolbar, boolean isDefault) {
+            super();
+            this.type = type;
+            this.displayName = displayName;
+            this.width = width;
+            this.height = height;
+            this.isDefault = isDefault;
+        }
+
+        static List<ResizeOption> loadAll() {
+            List<ResizeOption> res = new ArrayList<ResizeOption>(10);
+            res.add(ResizeOption.create(ResizeOption.Type.DESKTOP, "Desktop", 1280, 1024, true, true));
+            res.add(ResizeOption.create(ResizeOption.Type.TABLET_LANDSCAPE, "Tablet Landscape", 1024, 768, true, true));
+            res.add(ResizeOption.create(ResizeOption.Type.TABLET_PORTRAIT, "Tablet Portrait", 768, 1024, true, true));
+            res.add(ResizeOption.create(ResizeOption.Type.SMARTPHONE_LANDSCAPE, "Smartphone Landscape", 480, 320, true, true));
+            res.add(ResizeOption.create(ResizeOption.Type.SMARTPHONE_PORTRAIT, "Smartphone Portrait", 320, 480, true, true));
+            res.add(ResizeOption.create(ResizeOption.Type.WIDESCREEN, "Widescreen", 1680, 1050, false, true));
+            res.add(ResizeOption.create(ResizeOption.Type.NETBOOK, "Netbook", 1024, 600, false, true));
+            return res;
+        }
+        
+        /**
+         * Creates a new instance.
+         * @param type
+         * @param displayName Display name to show in tooltip, cannot be empty.
+         * @param width Screen width
+         * @param height Screen height
+         * @param showInToolbar True to show in web developer toolbar.
+         * @param isDefault True if this is a predefined option that cannot be removed.
+         * @return New instance.
+         */
+        public static ResizeOption create(Type type, String displayName, int width, int height, boolean showInToolbar, boolean isDefault) {
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("Invalid screen dimensions: " + width + " x " + height); //NOI18N
+            }
+            return new ResizeOption(type, displayName, width, height, showInToolbar, isDefault);
+        }
+        /**
+         * An extra option to size the browser content to fit its window.
+         */
+        public static final ResizeOption SIZE_TO_FIT = new ResizeOption(Type.CUSTOM, "Size To Fit", -1, -1, true, true);
+
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        public Type getType() {
+            return type;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+
+        public boolean isDefault() {
+            return isDefault;
+        }
+
+        @Override
+        public String toString() {
+            return displayName;
+        }
+
+        public String getToolTip() {
+            if (width < 0 || height < 0) {
+                return displayName;
+            }
+            StringBuilder sb = new StringBuilder();
+            sb.append(width);
+            sb.append(" x "); //NOI18N
+            sb.append(height);
+            sb.append(" ("); //NOI18N
+            sb.append(displayName);
+            sb.append(')'); //NOI18N
+            return sb.toString();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final ResizeOption other = (ResizeOption) obj;
+            if (this.type != other.type) {
+                return false;
+            }
+            if ((this.displayName == null) ? (other.displayName != null) : !this.displayName.equals(other.displayName)) {
+                return false;
+            }
+            if (this.width != other.width) {
+                return false;
+            }
+            if (this.height != other.height) {
+                return false;
+            }
+            if (this.isDefault != other.isDefault) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 7;
+            hash = 11 * hash + (this.type != null ? this.type.hashCode() : 0);
+            hash = 11 * hash + (this.displayName != null ? this.displayName.hashCode() : 0);
+            hash = 11 * hash + this.width;
+            hash = 11 * hash + this.height;
+            hash = 11 * hash + (this.isDefault ? 1 : 0);
+            return hash;
+        }
+    }
+    
+    private void listenOnChanges(boolean turnOn) {
+        try {
+            if (watcher != null) {
+                watcher.close();
+                watcher = null;
+            }
+            final WebEngine eng = webView.getEngine();
+            if (turnOn && eng.getLocation().startsWith("file:")) { // NOI18N
+                watcher = new WatchDir(eng);
+            }
+        } catch (Exception ex) {
+            FXInspect.LOG.log(Level.SEVERE, null, ex);
+        }
+    }
+       private static void enableFirebug(final WebEngine engine) {
+        engine.executeScript("if (!document.getElementById('FirebugLite')){E = document['createElement' + 'NS'] && document.documentElement.namespaceURI;E = E ? document['createElement' + 'NS'](E, 'script') : document['createElement']('script');E['setAttribute']('id', 'FirebugLite');E['setAttribute']('src', 'https://getfirebug.com/' + 'firebug-lite.js' + '#startOpened');E['setAttribute']('FirebugLite', '4');(document['getElementsByTagName']('head')[0] || document['getElementsByTagName']('body')[0]).appendChild(E);E = new Image;E['setAttribute']('src', 'https://getfirebug.com/' + '#startOpened');}"); 
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java
new file mode 100644
index 0000000..25cc5f2
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java
@@ -0,0 +1,117 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.ClosedWatchServiceException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javafx.application.Platform;
+import javafx.scene.web.WebEngine;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+final class WatchDir implements Runnable {
+    private final Path dir;
+    private final WatchKey key;
+    private final WatchService ws; 
+    private final Thread watcher;
+    private final WebEngine engine;
+    
+    WatchDir(WebEngine eng) throws URISyntaxException, IOException {
+        dir = Paths.get(new URI(eng.getLocation())).getParent();
+        engine = eng;
+        ws = dir.getFileSystem().newWatchService();
+        key = dir.register(ws, 
+            StandardWatchEventKinds.ENTRY_CREATE,
+            StandardWatchEventKinds.ENTRY_DELETE,
+            StandardWatchEventKinds.ENTRY_MODIFY
+        );
+        watcher = new Thread(this, "Watching files in " + dir);
+        watcher.setDaemon(true);
+        watcher.setPriority(Thread.MIN_PRIORITY);
+        watcher.start();
+    }
+
+    public void close() throws IOException {
+        key.cancel();
+        ws.close();
+        watcher.interrupt();
+    }
+
+    @Override
+    public void run() {
+        if (Platform.isFxApplicationThread()) {
+            engine.reload();
+            return;
+        }
+        try {
+            while (key.isValid()) {
+                WatchKey changed;
+                try {
+                    changed = ws.take();
+                    if (changed != key || changed.pollEvents().isEmpty()) {
+                        continue;
+                    }
+                } catch (ClosedWatchServiceException ex) {
+                    continue;
+                }
+                Platform.runLater(this);
+                if (!key.reset()) {
+                    break;
+                }
+            }
+        } catch (InterruptedException ex) {
+            FXInspect.LOG.log(Level.SEVERE, null, ex);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/Bundle.properties
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/Bundle.properties b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/Bundle.properties
new file mode 100644
index 0000000..7ebe82d
--- /dev/null
+++ b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/Bundle.properties
@@ -0,0 +1,54 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+#
+# Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+# Other names may be trademarks of their respective owners.
+#
+# The contents of this file are subject to the terms of either the GNU
+# General Public License Version 2 only ("GPL") or the Common
+# Development and Distribution License("CDDL") (collectively, the
+# "License"). You may not use this file except in compliance with the
+# License. You can obtain a copy of the License at
+# http://www.netbeans.org/cddl-gplv2.html
+# or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+# specific language governing permissions and limitations under the
+# License.  When distributing the software, include this License Header
+# Notice in each file and include the License file at
+# nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the GPL Version 2 section of the License file that
+# accompanied this code. If applicable, add the following below the
+# License Header, with the fields enclosed by brackets [] replaced by
+# your own identifying information:
+# "Portions Copyrighted [year] [name of copyright owner]"
+#
+# Contributor(s):
+#
+# The Original Software is NetBeans. The Initial Developer of the Original
+# Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+#
+# If you wish your version of this file to be governed by only the CDDL
+# or only the GPL Version 2, indicate your decision by adding
+# "[Contributor] elects to include this software in this distribution
+# under the [CDDL or GPL Version 2] license." If you do not indicate a
+# single choice of license, a recipient has the option to distribute
+# your version of this file under either the CDDL, the GPL Version 2 or
+# to extend the choice of license to its licensees as provided above.
+# However, if you add GPL Version 2 code and therefore, elected the GPL
+# Version 2 license, then the option applies only if the new code is
+# made subject to such option by the copyright holder.
+#
+
+AlertTitle=Warning
+AlertCloseButton=Close
+
+ConfirmTitle=Question
+ConfirmOKButton=OK
+ConfirmCancelButton=Cancel
+
+PromptTitle=Question
+PromptOKButton=OK
+PromptCancelButton=Cancel
+

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/desktop.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/desktop.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/desktop.png
new file mode 100644
index 0000000..b295e4b
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/desktop.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldLandscape.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldLandscape.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldLandscape.png
new file mode 100644
index 0000000..ce5d64e
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldLandscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldPortrait.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldPortrait.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldPortrait.png
new file mode 100644
index 0000000..686bea8
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/handheldPortrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/netbook.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/netbook.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/netbook.png
new file mode 100644
index 0000000..53838ba
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/netbook.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/selectionMode.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/selectionMode.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/selectionMode.png
new file mode 100644
index 0000000..d2e398c
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/selectionMode.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/sizeToFit.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/sizeToFit.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/sizeToFit.png
new file mode 100644
index 0000000..5fec5a4
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/sizeToFit.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletLandscape.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletLandscape.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletLandscape.png
new file mode 100644
index 0000000..89f7f61
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletLandscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletPortrait.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletPortrait.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletPortrait.png
new file mode 100644
index 0000000..fb94771
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/tabletPortrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/main/resources/org/netbeans/html/boot/fx/widescreen.png
----------------------------------------------------------------------
diff --git a/boot-fx/src/main/resources/org/netbeans/html/boot/fx/widescreen.png b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/widescreen.png
new file mode 100644
index 0000000..933e01f
Binary files /dev/null and b/boot-fx/src/main/resources/org/netbeans/html/boot/fx/widescreen.png differ

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersOnResourceTest.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersOnResourceTest.java b/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersOnResourceTest.java
new file mode 100644
index 0000000..1400ad8
--- /dev/null
+++ b/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersOnResourceTest.java
@@ -0,0 +1,209 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.boot.fx;
+
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.scene.Scene;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.web.WebView;
+import javafx.stage.Stage;
+import net.java.html.js.JavaScriptBody;
+import net.java.html.js.JavaScriptResource;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class FXBrowsersOnResourceTest {
+    
+    public FXBrowsersOnResourceTest() {
+    }
+    
+    @BeforeClass public void initFX() throws Throwable {
+        new Thread("initFX") {
+            @Override
+            public void run() {
+                if (Platform.isFxApplicationThread()) {
+                    new App().start(new Stage());
+                } else {
+                    try {
+                        App.launch(App.class);
+                    } catch (IllegalStateException ex) {
+                        Platform.runLater(this);
+                    }
+                }
+            }
+        }.start();
+        App.CDL.await();
+    }
+
+    @Test
+    public void behaviorOfTwoWebViewsAtOnce() throws Throwable {
+        class R implements Runnable {
+            CountDownLatch DONE = new CountDownLatch(1);
+            Throwable t;
+
+            @Override
+            public void run() {
+                try {
+                    doTest();
+                } catch (Throwable ex) {
+                    t = ex;
+                } finally {
+                    DONE.countDown();
+                }
+            }
+            
+            private void doTest() throws Throwable {
+                URL u = FXBrowsersOnResourceTest.class.getResource("/org/netbeans/html/boot/fx/empty.html");
+                assertNotNull(u, "URL found");
+                FXBrowsers.load(App.getV1(), u, OnPages.class, "first");
+                
+            }
+        }
+        R run = new R();
+        Platform.runLater(run);
+        run.DONE.await();
+        for (int i = 0; i < 100; i++) {
+            if (run.t != null) {
+                throw run.t;
+            }
+            if (System.getProperty("finalSecond") == null) {
+                Thread.sleep(100);
+            }
+        }
+        
+        
+        
+        assertEquals(Integer.getInteger("finalFirst"), Integer.valueOf(3), "Three times in view one");
+        assertEquals(Integer.getInteger("finalSecond"), Integer.valueOf(2), "Two times in view one");
+    }
+
+    @JavaScriptResource("wnd.js")
+    public static class OnPages {
+        static Class<?> first;
+        static Object firstWindow;
+        
+        public static void first() {
+            first = OnPages.class;
+            firstWindow = window();
+            assertNotNull(firstWindow, "First window found");
+            
+            assertEquals(increment(), 1, "Now it is one");
+            
+            URL u = FXBrowsersOnResourceTest.class.getResource("/org/netbeans/html/boot/fx/empty.html");
+            assertNotNull(u, "URL found");
+            FXBrowsers.load(App.getV2(), u, OnPages.class, "second", "Hello");
+            
+            assertEquals(increment(), 2, "Now it is two and not influenced by second view");
+            System.setProperty("finalFirst", "" + increment());
+        }
+        
+        public static void second(String... args) {
+            assertEquals(args.length, 1, "One string argument");
+            assertEquals(args[0], "Hello", "It is hello");
+            assertEquals(first, OnPages.class, "Both views share the same classloader");
+            
+            Object window = window();
+            assertNotNull(window, "Some window found");
+            assertNotNull(firstWindow, "First window is known");
+            assertNotSame(firstWindow, window, "The window objects should be different");
+            
+            assertEquals(increment(), 1, "Counting starts from zero");
+            System.setProperty("finalSecond", "" + increment());
+        }
+        
+        @JavaScriptBody(args = {}, body = "return wnd;")
+        private static native Object window();
+        
+        @JavaScriptBody(args = {}, body = ""
+            + "if (wnd.cnt) return ++wnd.cnt;"
+            + "return wnd.cnt = 1;"
+        )
+        private static native int increment();
+    }
+    
+    public static class App extends Application {
+        static final CountDownLatch CDL = new CountDownLatch(1);
+        private static BorderPane pane;
+
+        /**
+         * @return the v1
+         */
+        static WebView getV1() {
+            return (WebView)System.getProperties().get("v1");
+        }
+
+        /**
+         * @return the v2
+         */
+        static WebView getV2() {
+            return (WebView)System.getProperties().get("v2");
+        }
+
+        @Override
+        public void start(Stage stage) {
+            pane= new BorderPane();
+            Scene scene = new Scene(pane, 800, 600);
+            stage.setScene(scene);
+            
+            System.getProperties().put("v1", new WebView());
+            System.getProperties().put("v2", new WebView());
+
+            pane.setCenter(getV1());
+            pane.setBottom(getV2());
+
+            CDL.countDown();
+        }
+        
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersTest.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersTest.java b/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersTest.java
new file mode 100644
index 0000000..2498c56
--- /dev/null
+++ b/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersTest.java
@@ -0,0 +1,253 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.boot.fx;
+
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.scene.Scene;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.web.WebView;
+import javafx.stage.Stage;
+import net.java.html.BrwsrCtx;
+import net.java.html.js.JavaScriptBody;
+import static org.testng.Assert.*;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class FXBrowsersTest {
+    private static CountDownLatch PROPERTY_SET;
+
+    public FXBrowsersTest() {
+    }
+    
+    @BeforeClass public void initFX() throws Throwable {
+        new Thread("initFX") {
+            @Override
+            public void run() {
+                if (Platform.isFxApplicationThread()) {
+                    new App().start(new Stage());
+                } else {
+                    try {
+                        App.launch(App.class);
+                    } catch (IllegalStateException ex) {
+                        Platform.runLater(this);
+                    }
+                }
+            }
+        }.start();
+        App.CDL.await();
+    }
+    
+    @JavaScriptBody(args = {  }, body = "return true;")
+    static boolean inJS() {
+        return false;
+    }
+
+    @Test
+    public void brwsrCtxExecute() throws Throwable {
+        assertFalse(inJS(), "We aren't in JS now");
+        final CountDownLatch init = new CountDownLatch(1);
+        final BrwsrCtx[] ctx = { null };
+        FXBrowsers.runInBrowser(App.getV1(), new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(inJS(), "We are in JS context now");
+                ctx[0] = BrwsrCtx.findDefault(FXBrowsersTest.class);
+                init.countDown();
+            }
+        });
+        init.await();
+
+        final CountDownLatch cdl = new CountDownLatch(1);
+        class R implements Runnable {
+            @Override
+            public void run() {
+                if (Platform.isFxApplicationThread()) {
+                    assertTrue(inJS());
+                    cdl.countDown();
+                } else {
+                    ctx[0].execute(this);
+                }
+            }
+        }
+        new Thread(new R(), "Background thread").start();
+
+        cdl.await();
+    }
+
+    @Test
+    public void behaviorOfTwoWebViewsAtOnce() throws Throwable {
+        class R implements Runnable {
+            Throwable t;
+
+            @Override
+            public void run() {
+                try {
+                    doTest();
+                } catch (Throwable ex) {
+                    t = ex;
+                }
+            }
+            
+            private void doTest() throws Throwable {
+                URL u = FXBrowsersTest.class.getResource("/org/netbeans/html/boot/fx/empty.html");
+                assertNotNull(u, "URL found");
+                FXBrowsers.load(App.getV1(), u, OnPages.class, "first");
+            }
+        }
+        R run = new R();
+        PROPERTY_SET = new CountDownLatch(2);
+        Platform.runLater(run);
+        PROPERTY_SET.await();
+        
+        assertEquals(Integer.getInteger("finalFirst"), Integer.valueOf(3), "Three times in view one");
+        assertEquals(Integer.getInteger("finalSecond"), Integer.valueOf(2), "Two times in view one");
+
+        final CountDownLatch finish = new CountDownLatch(1);
+        final Object[] three = { 0 };
+        assertFalse(Platform.isFxApplicationThread());
+        FXBrowsers.runInBrowser(App.getV1(), new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(Platform.isFxApplicationThread());
+                three[0] = App.getV1().getEngine().executeScript("window.cntBrwsr");
+                finish.countDown();
+            }
+        });
+        finish.await();
+        
+        assertEquals(three[0], Integer.valueOf(3));
+    }
+    
+    public static class OnPages {
+        static Class<?> first;
+        static Object firstWindow;
+        
+        public static void first() {
+            first = OnPages.class;
+            firstWindow = window();
+            assertNotNull(firstWindow, "First window found");
+            
+            assertEquals(increment(), 1, "Now it is one");
+            
+            URL u = FXBrowsersTest.class.getResource("/org/netbeans/html/boot/fx/empty.html");
+            assertNotNull(u, "URL found");
+            FXBrowsers.load(App.getV2(), u, new Runnable() {
+                @Override
+                public void run() {
+                    OnPages.second("Hello");
+                }
+            });
+            
+            assertEquals(increment(), 2, "Now it is two and not influenced by second view");
+            System.setProperty("finalFirst", "" + increment());
+            PROPERTY_SET.countDown();
+        }
+        
+        public static void second(String... args) {
+            assertEquals(args.length, 1, "One string argument");
+            assertEquals(args[0], "Hello", "It is hello");
+            assertEquals(first, OnPages.class, "Both views share the same classloader");
+            
+            Object window = window();
+            assertNotNull(window, "Some window found");
+            assertNotNull(firstWindow, "First window is known");
+            assertNotSame(firstWindow, window, "The window objects should be different");
+            
+            assertEquals(increment(), 1, "Counting starts from zero");
+            System.setProperty("finalSecond", "" + increment());
+            PROPERTY_SET.countDown();
+        }
+        
+        @JavaScriptBody(args = {}, body = "return window;")
+        private static native Object window();
+        
+        @JavaScriptBody(args = {}, body = ""
+            + "if (window.cntBrwsr) return ++window.cntBrwsr;"
+            + "return window.cntBrwsr = 1;"
+        )
+        private static native int increment();
+    }
+    
+    public static class App extends Application {
+        static final CountDownLatch CDL = new CountDownLatch(1);
+        private static BorderPane pane;
+
+        /**
+         * @return the v1
+         */
+        static WebView getV1() {
+            return (WebView)System.getProperties().get("v1");
+        }
+
+        /**
+         * @return the v2
+         */
+        static WebView getV2() {
+            return (WebView)System.getProperties().get("v2");
+        }
+
+        @Override
+        public void start(Stage stage) {
+            pane= new BorderPane();
+            Scene scene = new Scene(pane, 800, 600);
+            stage.setScene(scene);
+            
+            System.getProperties().put("v1", new WebView());
+            System.getProperties().put("v2", new WebView());
+
+            pane.setCenter(getV1());
+            pane.setBottom(getV2());
+
+            CDL.countDown();
+        }
+        
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot-fx/src/test/java/org/netbeans/html/boot/fx/FXBrwsrTest.java
----------------------------------------------------------------------
diff --git a/boot-fx/src/test/java/org/netbeans/html/boot/fx/FXBrwsrTest.java b/boot-fx/src/test/java/org/netbeans/html/boot/fx/FXBrwsrTest.java
new file mode 100644
index 0000000..1f52998
--- /dev/null
+++ b/boot-fx/src/test/java/org/netbeans/html/boot/fx/FXBrwsrTest.java
@@ -0,0 +1,84 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.fx;
+
+import org.sample.app.pkg.SampleApp;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.Test;
+
+public class FXBrwsrTest {
+
+    public FXBrwsrTest() {
+    }
+
+    @Test
+    public void testFindCalleeClassName() throws InterruptedException {
+        String callee = invokeMain();
+        assertEquals(callee, SampleApp.class.getName(), "Callee is found correctly");
+    }
+
+    synchronized static String invokeMain() throws InterruptedException {
+        new Thread("starting main") {
+            @Override
+            public void run() {
+                SampleApp.main();
+            }
+        }.start();
+        for (;;) {
+            String callee = System.getProperty("callee");
+            if (callee != null) {
+                return callee;
+            }
+            FXBrwsrTest.class.wait();
+        }
+    }
+
+
+    public static void computeCalleeClassName() {
+        String name = FXBrwsr.findCalleeClassName();
+        System.setProperty("callee", name);
+        synchronized (FXBrwsrTest.class) {
+            FXBrwsrTest.class.notifyAll();
+        }
+    }
+}