Support for multiple FX WebViews running at once in isolation
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 12 Sep 2013 09:33:50 +0200
changeset 2888c5b40231d26
parent 287 20e0763e6cac
child 289 5fe8b52d74f9
Support for multiple FX WebViews running at once in isolation
boot-fx/pom.xml
boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
boot-fx/src/main/java/org/apidesign/html/boot/fx/AbstractFXPresenter.java
boot-fx/src/main/java/org/apidesign/html/boot/fx/FXBrwsr.java
boot-fx/src/main/java/org/apidesign/html/boot/fx/FXPresenter.java
boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersTest.java
boot-fx/src/test/java/org/apidesign/html/boot/fx/BootstrapTest.java
boot-fx/src/test/java/org/apidesign/html/boot/fx/KOFx.java
boot/src/main/java/net/java/html/boot/BrowserBuilder.java
boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java
boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java
boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java
boot/src/main/java/org/apidesign/html/boot/spi/Fn.java
boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java
boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java
ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java
ko-fx/src/test/java/org/apidesign/html/kofx/KOFx.java
ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java
ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusFX.java
ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusKnockoutTest.java
     1.1 --- a/boot-fx/pom.xml	Wed Sep 11 14:49:49 2013 +0200
     1.2 +++ b/boot-fx/pom.xml	Thu Sep 12 09:33:50 2013 +0200
     1.3 @@ -23,6 +23,13 @@
     1.4                    <skip>false</skip>
     1.5                </configuration>
     1.6            </plugin>
     1.7 +          <plugin>
     1.8 +              <groupId>org.apache.maven.plugins</groupId>
     1.9 +              <artifactId>maven-surefire-plugin</artifactId>
    1.10 +              <configuration>
    1.11 +                  <forkMode>always</forkMode>
    1.12 +              </configuration>
    1.13 +          </plugin>
    1.14        </plugins>
    1.15    </build>
    1.16    <dependencies>
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java	Thu Sep 12 09:33:50 2013 +0200
     2.3 @@ -0,0 +1,74 @@
     2.4 +/**
     2.5 + * HTML via Java(tm) Language Bindings
     2.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     2.7 + *
     2.8 + * This program is free software: you can redistribute it and/or modify
     2.9 + * it under the terms of the GNU General Public License as published by
    2.10 + * the Free Software Foundation, version 2 of the License.
    2.11 + *
    2.12 + * This program is distributed in the hope that it will be useful,
    2.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    2.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    2.15 + * GNU General Public License for more details. apidesign.org
    2.16 + * designates this particular file as subject to the
    2.17 + * "Classpath" exception as provided by apidesign.org
    2.18 + * in the License file that accompanied this code.
    2.19 + *
    2.20 + * You should have received a copy of the GNU General Public License
    2.21 + * along with this program. Look for COPYING file in the top folder.
    2.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    2.23 + */
    2.24 +package net.java.html.boot.fx;
    2.25 +
    2.26 +import java.net.URL;
    2.27 +import javafx.beans.value.ChangeListener;
    2.28 +import javafx.beans.value.ObservableValue;
    2.29 +import javafx.concurrent.Worker;
    2.30 +import javafx.scene.web.WebView;
    2.31 +import net.java.html.boot.BrowserBuilder;
    2.32 +import org.apidesign.html.boot.fx.AbstractFXPresenter;
    2.33 +
    2.34 +/**
    2.35 + *
    2.36 + * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    2.37 + */
    2.38 +public final class FXBrowsers {
    2.39 +    private FXBrowsers() {
    2.40 +    }
    2.41 +    
    2.42 +    public static void load(
    2.43 +        final WebView webView, final URL url, 
    2.44 +        Class<?> onPageLoad, String methodName,
    2.45 +        String... args
    2.46 +    ) {
    2.47 +        class Load extends AbstractFXPresenter {
    2.48 +            @Override
    2.49 +            protected void waitFinished() {
    2.50 +                // don't wait
    2.51 +            }
    2.52 +
    2.53 +            @Override
    2.54 +            protected WebView findView(URL resource) {
    2.55 +                final Worker<Void> w = webView.getEngine().getLoadWorker();
    2.56 +                w.stateProperty().addListener(new ChangeListener<Worker.State>() {
    2.57 +                    @Override
    2.58 +                    public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
    2.59 +                        if (newState.equals(Worker.State.SUCCEEDED)) {
    2.60 +                            onPageLoad();
    2.61 +                        }
    2.62 +                        if (newState.equals(Worker.State.FAILED)) {
    2.63 +                            throw new IllegalStateException("Failed to load " + url);
    2.64 +                        }
    2.65 +                    }
    2.66 +                });
    2.67 +                
    2.68 +                return webView;
    2.69 +            }
    2.70 +        }
    2.71 +        BrowserBuilder.newBrowser(new Load()).
    2.72 +            loadPage(url.toExternalForm()).
    2.73 +            loadClass(onPageLoad).
    2.74 +            invoke(methodName, args).
    2.75 +            showAndWait();
    2.76 +    }
    2.77 +}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/boot-fx/src/main/java/org/apidesign/html/boot/fx/AbstractFXPresenter.java	Thu Sep 12 09:33:50 2013 +0200
     3.3 @@ -0,0 +1,168 @@
     3.4 +/**
     3.5 + * HTML via Java(tm) Language Bindings
     3.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 + *
     3.8 + * This program is free software: you can redistribute it and/or modify
     3.9 + * it under the terms of the GNU General Public License as published by
    3.10 + * the Free Software Foundation, version 2 of the License.
    3.11 + *
    3.12 + * This program is distributed in the hope that it will be useful,
    3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 + * GNU General Public License for more details. apidesign.org
    3.16 + * designates this particular file as subject to the
    3.17 + * "Classpath" exception as provided by apidesign.org
    3.18 + * in the License file that accompanied this code.
    3.19 + *
    3.20 + * You should have received a copy of the GNU General Public License
    3.21 + * along with this program. Look for COPYING file in the top folder.
    3.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    3.23 + */
    3.24 +package org.apidesign.html.boot.fx;
    3.25 +
    3.26 +import java.io.BufferedReader;
    3.27 +import java.io.Reader;
    3.28 +import java.net.URL;
    3.29 +import java.util.ArrayList;
    3.30 +import java.util.Arrays;
    3.31 +import java.util.List;
    3.32 +import java.util.logging.Level;
    3.33 +import java.util.logging.Logger;
    3.34 +import javafx.application.Platform;
    3.35 +import javafx.scene.web.WebEngine;
    3.36 +import javafx.scene.web.WebView;
    3.37 +import netscape.javascript.JSObject;
    3.38 +import org.apidesign.html.boot.spi.Fn;
    3.39 +
    3.40 +/**
    3.41 + *
    3.42 + * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    3.43 + */
    3.44 +public abstract class AbstractFXPresenter implements Fn.Presenter {
    3.45 +    static final Logger LOG = Logger.getLogger(FXPresenter.class.getName());
    3.46 +    protected static int cnt;
    3.47 +    protected List<String> scripts;
    3.48 +    protected Runnable onLoad;
    3.49 +    protected WebEngine engine;
    3.50 +
    3.51 +    @Override
    3.52 +    public Fn defineFn(String code, String... names) {
    3.53 +        StringBuilder sb = new StringBuilder();
    3.54 +        sb.append("(function() {");
    3.55 +        sb.append("  return function(");
    3.56 +        String sep = "";
    3.57 +        for (String n : names) {
    3.58 +            sb.append(sep).append(n);
    3.59 +            sep = ",";
    3.60 +        }
    3.61 +        sb.append(") {\n");
    3.62 +        sb.append(code);
    3.63 +        sb.append("};");
    3.64 +        sb.append("})()");
    3.65 +        if (LOG.isLoggable(Level.FINE)) {
    3.66 +            LOG.log(Level.FINE, "defining function #{0}", ++cnt);
    3.67 +            LOG.fine("-----");
    3.68 +            LOG.fine(code);
    3.69 +            LOG.fine("-----");
    3.70 +        }
    3.71 +        JSObject x = (JSObject) engine.executeScript(sb.toString());
    3.72 +        return new JSFn(this, x, cnt);
    3.73 +    }
    3.74 +
    3.75 +    @Override
    3.76 +    public void loadScript(Reader code) throws Exception {
    3.77 +        BufferedReader r = new BufferedReader(code);
    3.78 +        StringBuilder sb = new StringBuilder();
    3.79 +        for (;;) {
    3.80 +            String l = r.readLine();
    3.81 +            if (l == null) {
    3.82 +                break;
    3.83 +            }
    3.84 +            sb.append(l).append('\n');
    3.85 +        }
    3.86 +        final String script = sb.toString();
    3.87 +        if (scripts != null) {
    3.88 +            scripts.add(script);
    3.89 +        }
    3.90 +        engine.executeScript(script);
    3.91 +    }
    3.92 +
    3.93 +    protected final void onPageLoad() {
    3.94 +        if (scripts != null) {
    3.95 +            for (String s : scripts) {
    3.96 +                engine.executeScript(s);
    3.97 +            }
    3.98 +        }
    3.99 +        onLoad.run();
   3.100 +    }
   3.101 +
   3.102 +    @Override
   3.103 +    public void displayPage(final URL resource, final Runnable onLoad) {
   3.104 +        this.onLoad = onLoad;
   3.105 +        final WebView view = findView(resource);
   3.106 +        this.engine = view.getEngine();
   3.107 +        try {
   3.108 +            if (FXInspect.initialize(engine)) {
   3.109 +                scripts = new ArrayList<String>();
   3.110 +            }
   3.111 +        } catch (Throwable ex) {
   3.112 +            ex.printStackTrace();
   3.113 +        }
   3.114 +
   3.115 +        class Run implements Runnable {
   3.116 +
   3.117 +            @Override
   3.118 +            public void run() {
   3.119 +                if (scripts != null) {
   3.120 +                    view.setContextMenuEnabled(true);
   3.121 +                }
   3.122 +                engine.load(resource.toExternalForm());
   3.123 +            }
   3.124 +        }
   3.125 +        Run run = new Run();
   3.126 +        if (Platform.isFxApplicationThread()) {
   3.127 +            run.run();
   3.128 +        } else {
   3.129 +            Platform.runLater(run);
   3.130 +        }
   3.131 +        waitFinished();
   3.132 +    }
   3.133 +
   3.134 +    protected abstract void waitFinished();
   3.135 +
   3.136 +    protected abstract WebView findView(final URL resource);
   3.137 +
   3.138 +    private static final class JSFn extends Fn {
   3.139 +
   3.140 +        private final JSObject fn;
   3.141 +        private static int call;
   3.142 +        private final int id;
   3.143 +
   3.144 +        public JSFn(AbstractFXPresenter p, JSObject fn, int id) {
   3.145 +            super(p);
   3.146 +            this.fn = fn;
   3.147 +            this.id = id;
   3.148 +        }
   3.149 +
   3.150 +        @Override
   3.151 +        public Object handleInvoke(Object thiz, Object... args) throws Exception {
   3.152 +            try {
   3.153 +                if (LOG.isLoggable(Level.FINE)) {
   3.154 +                    LOG.log(Level.FINE, "calling {0} function #{1}", new Object[]{++call, id});
   3.155 +                }
   3.156 +                List<Object> all = new ArrayList<Object>(args.length + 1);
   3.157 +                all.add(thiz == null ? fn : thiz);
   3.158 +                all.addAll(Arrays.asList(args));
   3.159 +                Object ret = fn.call("call", all.toArray()); // NOI18N
   3.160 +                return ret == fn ? null : ret;
   3.161 +            } catch (Error t) {
   3.162 +                t.printStackTrace();
   3.163 +                throw t;
   3.164 +            } catch (Exception t) {
   3.165 +                t.printStackTrace();
   3.166 +                throw t;
   3.167 +            }
   3.168 +        }
   3.169 +    }
   3.170 +    
   3.171 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/boot-fx/src/main/java/org/apidesign/html/boot/fx/FXBrwsr.java	Thu Sep 12 09:33:50 2013 +0200
     4.3 @@ -0,0 +1,169 @@
     4.4 +/**
     4.5 + * HTML via Java(tm) Language Bindings
     4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details. apidesign.org
    4.16 + * designates this particular file as subject to the
    4.17 + * "Classpath" exception as provided by apidesign.org
    4.18 + * in the License file that accompanied this code.
    4.19 + *
    4.20 + * You should have received a copy of the GNU General Public License
    4.21 + * along with this program. Look for COPYING file in the top folder.
    4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    4.23 + */
    4.24 +package org.apidesign.html.boot.fx;
    4.25 +
    4.26 +import java.net.URL;
    4.27 +import java.util.concurrent.CountDownLatch;
    4.28 +import java.util.concurrent.Executors;
    4.29 +import javafx.application.Application;
    4.30 +import javafx.application.Platform;
    4.31 +import javafx.beans.value.ChangeListener;
    4.32 +import javafx.beans.value.ObservableValue;
    4.33 +import javafx.concurrent.Worker;
    4.34 +import javafx.event.ActionEvent;
    4.35 +import javafx.event.EventHandler;
    4.36 +import javafx.geometry.Insets;
    4.37 +import javafx.geometry.Pos;
    4.38 +import javafx.scene.Scene;
    4.39 +import javafx.scene.control.Button;
    4.40 +import javafx.scene.layout.BorderPane;
    4.41 +import javafx.scene.layout.VBox;
    4.42 +import javafx.scene.text.Text;
    4.43 +import javafx.scene.web.WebEvent;
    4.44 +import javafx.scene.web.WebView;
    4.45 +import javafx.stage.Modality;
    4.46 +import javafx.stage.Stage;
    4.47 +import org.openide.util.Exceptions;
    4.48 +
    4.49 +/** This is an implementation class, use {@link BrowserBuilder} API. Just
    4.50 + * include this JAR on classpath and the {@link BrowserBuilder} API will find
    4.51 + * this implementation automatically.
    4.52 + */
    4.53 +public class FXBrwsr extends Application {
    4.54 +    private static FXBrwsr INSTANCE;
    4.55 +    private static final CountDownLatch FINISHED = new CountDownLatch(1);
    4.56 +    private BorderPane root;
    4.57 +
    4.58 +    public static synchronized WebView findWebView(final URL url, final FXPresenter onLoad) {
    4.59 +        if (INSTANCE == null) {
    4.60 +            Executors.newFixedThreadPool(1).submit(new Runnable() {
    4.61 +                @Override
    4.62 +                public void run() {
    4.63 +                    try {
    4.64 +                        FXBrwsr.launch(FXBrwsr.class);
    4.65 +                    } catch (Throwable ex) {
    4.66 +                        ex.printStackTrace();
    4.67 +                    } finally {
    4.68 +                        FINISHED.countDown();
    4.69 +                    }
    4.70 +                }
    4.71 +            });
    4.72 +        }
    4.73 +        while (INSTANCE == null) {
    4.74 +            try {
    4.75 +                FXBrwsr.class.wait();
    4.76 +            } catch (InterruptedException ex) {
    4.77 +                // wait more
    4.78 +            }
    4.79 +        }
    4.80 +        if (!Platform.isFxApplicationThread()) {
    4.81 +            final WebView[] arr = {null};
    4.82 +            final CountDownLatch waitForResult = new CountDownLatch(1);
    4.83 +            Platform.runLater(new Runnable() {
    4.84 +                @Override
    4.85 +                public void run() {
    4.86 +                    arr[0] = INSTANCE.newView(url, onLoad);
    4.87 +                    waitForResult.countDown();
    4.88 +                }
    4.89 +            });
    4.90 +            for (;;) {
    4.91 +                try {
    4.92 +                    waitForResult.await();
    4.93 +                    break;
    4.94 +                } catch (InterruptedException ex) {
    4.95 +                    Exceptions.printStackTrace(ex);
    4.96 +                }
    4.97 +            }
    4.98 +            return arr[0];
    4.99 +        } else {
   4.100 +            return INSTANCE.newView(url, onLoad);
   4.101 +        }
   4.102 +    }
   4.103 +
   4.104 +    @Override
   4.105 +    public void start(Stage primaryStage) throws Exception {
   4.106 +        synchronized (FXBrwsr.class) {
   4.107 +            INSTANCE = this;
   4.108 +            FXBrwsr.class.notifyAll();
   4.109 +        }
   4.110 +        BorderPane r = new BorderPane();
   4.111 +        Scene scene = new Scene(r, 800, 600);
   4.112 +        primaryStage.setScene(scene);
   4.113 +        primaryStage.show();
   4.114 +        this.root = r;
   4.115 +    }
   4.116 +
   4.117 +    private WebView newView(final URL url, final FXPresenter onLoad) {
   4.118 +        final WebView view = new WebView();
   4.119 +        view.setContextMenuEnabled(false);
   4.120 +        view.getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
   4.121 +            @Override
   4.122 +            public void handle(WebEvent<String> t) {
   4.123 +                final Stage dialogStage = new Stage();
   4.124 +                dialogStage.initModality(Modality.WINDOW_MODAL);
   4.125 +                dialogStage.setTitle("Warning");
   4.126 +                final Button button = new Button("Close");
   4.127 +                final Text text = new Text(t.getData());
   4.128 +                VBox box = new VBox();
   4.129 +                box.setAlignment(Pos.CENTER);
   4.130 +                box.setSpacing(10);
   4.131 +                box.setPadding(new Insets(10));
   4.132 +                box.getChildren().addAll(text, button);
   4.133 +                dialogStage.setScene(new Scene(box));
   4.134 +                button.setCancelButton(true);
   4.135 +                button.setOnAction(new EventHandler<ActionEvent>() {
   4.136 +                    @Override
   4.137 +                    public void handle(ActionEvent t) {
   4.138 +                        dialogStage.close();
   4.139 +                    }
   4.140 +                });
   4.141 +                dialogStage.centerOnScreen();
   4.142 +                dialogStage.showAndWait();
   4.143 +            }
   4.144 +        });
   4.145 +        root.setCenter(view);
   4.146 +        final Worker<Void> w = view.getEngine().getLoadWorker();
   4.147 +        w.stateProperty().addListener(new ChangeListener<Worker.State>() {
   4.148 +            @Override
   4.149 +            public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
   4.150 +                if (newState.equals(Worker.State.SUCCEEDED)) {
   4.151 +                    onLoad.onPageLoad();
   4.152 +                }
   4.153 +                if (newState.equals(Worker.State.FAILED)) {
   4.154 +                    throw new IllegalStateException("Failed to load " + url);
   4.155 +                }
   4.156 +            }
   4.157 +        });
   4.158 +        return view;
   4.159 +    }
   4.160 +
   4.161 +    static void waitFinished() {
   4.162 +        for (;;) {
   4.163 +            try {
   4.164 +                FINISHED.await();
   4.165 +                break;
   4.166 +            } catch (InterruptedException ex) {
   4.167 +                Exceptions.printStackTrace(ex);
   4.168 +            }
   4.169 +        }
   4.170 +    }
   4.171 +    
   4.172 +}
     5.1 --- a/boot-fx/src/main/java/org/apidesign/html/boot/fx/FXPresenter.java	Wed Sep 11 14:49:49 2013 +0200
     5.2 +++ b/boot-fx/src/main/java/org/apidesign/html/boot/fx/FXPresenter.java	Thu Sep 12 09:33:50 2013 +0200
     5.3 @@ -29,33 +29,14 @@
     5.4  import java.util.ArrayList;
     5.5  import java.util.Arrays;
     5.6  import java.util.List;
     5.7 -import java.util.concurrent.CountDownLatch;
     5.8 -import java.util.concurrent.Executors;
     5.9  import java.util.logging.Level;
    5.10  import java.util.logging.Logger;
    5.11 -import javafx.application.Application;
    5.12  import javafx.application.Platform;
    5.13 -import javafx.beans.value.ChangeListener;
    5.14 -import javafx.beans.value.ObservableValue;
    5.15 -import javafx.concurrent.Worker;
    5.16 -import javafx.event.ActionEvent;
    5.17 -import javafx.event.EventHandler;
    5.18 -import javafx.geometry.Insets;
    5.19 -import javafx.geometry.Pos;
    5.20 -import javafx.scene.Scene;
    5.21 -import javafx.scene.control.Button;
    5.22 -import javafx.scene.layout.BorderPane;
    5.23 -import javafx.scene.layout.VBox;
    5.24 -import javafx.scene.text.Text;
    5.25  import javafx.scene.web.WebEngine;
    5.26 -import javafx.scene.web.WebEvent;
    5.27  import javafx.scene.web.WebView;
    5.28 -import javafx.stage.Modality;
    5.29 -import javafx.stage.Stage;
    5.30  import net.java.html.boot.BrowserBuilder;
    5.31  import netscape.javascript.JSObject;
    5.32  import org.apidesign.html.boot.spi.Fn;
    5.33 -import org.openide.util.Exceptions;
    5.34  import org.openide.util.lookup.ServiceProvider;
    5.35  
    5.36  /** This is an implementation class, use {@link BrowserBuilder} API. Just
    5.37 @@ -65,9 +46,7 @@
    5.38   * @author Jaroslav Tulach <jtulach@netbeans.org>
    5.39   */
    5.40  @ServiceProvider(service = Fn.Presenter.class)
    5.41 -public final class FXPresenter implements Fn.Presenter {
    5.42 -    static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName());
    5.43 -        
    5.44 +public final class FXPresenter extends AbstractFXPresenter {
    5.45      static {
    5.46          try {
    5.47              try {
    5.48 @@ -86,249 +65,12 @@
    5.49              throw new LinkageError("Can't add jfxrt.jar on the classpath", ex);
    5.50          }
    5.51      }
    5.52 -    
    5.53 -    private List<String> scripts;
    5.54 -    private Runnable onLoad;
    5.55 -    private WebEngine engine;
    5.56  
    5.57 -    private static int cnt;
    5.58 -    
    5.59 -    @Override
    5.60 -    public Fn defineFn(String code, String... names) {
    5.61 -        StringBuilder sb = new StringBuilder();
    5.62 -        sb.append("(function() {");
    5.63 -        sb.append("  return function(");
    5.64 -        String sep = "";
    5.65 -        for (String n : names) {
    5.66 -            sb.append(sep).append(n);
    5.67 -            sep = ",";
    5.68 -        }
    5.69 -        sb.append(") {\n");
    5.70 -        sb.append(code);
    5.71 -        sb.append("};");
    5.72 -        sb.append("})()");
    5.73 -    
    5.74 -        if (LOG.isLoggable(Level.FINE)) {
    5.75 -            LOG.log(Level.FINE, "defining function #{0}", ++cnt);
    5.76 -            LOG.fine("-----");
    5.77 -            LOG.fine(code);
    5.78 -            LOG.fine("-----");
    5.79 -        }
    5.80 -
    5.81 -        JSObject x = (JSObject) engine.executeScript(sb.toString());
    5.82 -        return new JSFn(x, cnt);
    5.83 -    }
    5.84 -
    5.85 -    @Override
    5.86 -    public void loadScript(Reader code) throws Exception {
    5.87 -        BufferedReader r = new BufferedReader(code);
    5.88 -        StringBuilder sb = new StringBuilder();
    5.89 -        for (;;) {
    5.90 -            String l = r.readLine();
    5.91 -            if (l == null) {
    5.92 -                break;
    5.93 -            }
    5.94 -            sb.append(l).append('\n');
    5.95 -        }
    5.96 -        final String script = sb.toString();
    5.97 -        if (scripts != null) {
    5.98 -            scripts.add(script);
    5.99 -        }
   5.100 -        engine.executeScript(script);
   5.101 -    }
   5.102 -    
   5.103 -    final void onPageLoad() {
   5.104 -        if (scripts != null) {
   5.105 -            for (String s : scripts) {
   5.106 -                engine.executeScript(s);
   5.107 -            }
   5.108 -        }
   5.109 -        onLoad.run();
   5.110 -    }
   5.111 -
   5.112 -    @Override
   5.113 -    public void displayPage(final URL resource, final Runnable onLoad) {
   5.114 -        this.onLoad = onLoad;
   5.115 -        final WebView view = FXBrwsr.findWebView(resource, this); 
   5.116 -        this.engine = view.getEngine();
   5.117 -        try {
   5.118 -            if (FXInspect.initialize(engine)) {
   5.119 -                scripts = new ArrayList<String>();
   5.120 -            }
   5.121 -        } catch (Throwable ex) {
   5.122 -            ex.printStackTrace();
   5.123 -        }
   5.124 -        Platform.runLater(new Runnable() {
   5.125 -            @Override
   5.126 -            public void run() {
   5.127 -                if (scripts != null) {
   5.128 -                    view.setContextMenuEnabled(true);
   5.129 -                }
   5.130 -                engine.load(resource.toExternalForm());
   5.131 -            }
   5.132 -        });
   5.133 +    protected void waitFinished() {
   5.134          FXBrwsr.waitFinished();
   5.135      }
   5.136  
   5.137 -    private static final class JSFn extends Fn {
   5.138 -        private final JSObject fn;
   5.139 -        private static int call;
   5.140 -        private final int id;
   5.141 -
   5.142 -        public JSFn(JSObject fn, int id) {
   5.143 -            this.fn = fn;
   5.144 -            this.id = id;
   5.145 -        }
   5.146 -        
   5.147 -        @Override
   5.148 -        public Object invoke(Object thiz, Object... args) throws Exception {
   5.149 -            try {
   5.150 -                if (LOG.isLoggable(Level.FINE)) {
   5.151 -                    LOG.log(Level.FINE, "calling {0} function #{1}", new Object[]{++call, id});
   5.152 -                }
   5.153 -                List<Object> all = new ArrayList<Object>(args.length + 1);
   5.154 -                all.add(thiz == null ? fn : thiz);
   5.155 -                all.addAll(Arrays.asList(args));
   5.156 -                Object ret = fn.call("call", all.toArray()); // NOI18N
   5.157 -                return ret == fn ? null : ret;
   5.158 -            } catch (Error t) {
   5.159 -                t.printStackTrace();
   5.160 -                throw t;
   5.161 -            } catch (Exception t) {
   5.162 -                t.printStackTrace();
   5.163 -                throw t;
   5.164 -            }
   5.165 -        }
   5.166 +    protected WebView findView(final URL resource) {
   5.167 +        return FXBrwsr.findWebView(resource, this);
   5.168      }
   5.169 -
   5.170 -    /** This is an implementation class, use {@link BrowserBuilder} API. Just
   5.171 -     * include this JAR on classpath and the {@link BrowserBuilder} API will find
   5.172 -     * this implementation automatically.
   5.173 -     */
   5.174 -    public static class FXBrwsr extends Application {
   5.175 -        private static FXBrwsr INSTANCE;
   5.176 -        private static final CountDownLatch FINISHED = new CountDownLatch(1);
   5.177 -
   5.178 -        private BorderPane root;
   5.179 -
   5.180 -        public synchronized static WebView findWebView(final URL url, final FXPresenter onLoad) {
   5.181 -            if (INSTANCE == null) {
   5.182 -                Executors.newFixedThreadPool(1).submit(new Runnable() {
   5.183 -                    @Override
   5.184 -                    public void run() {
   5.185 -                        try {
   5.186 -                            FXBrwsr.launch(FXBrwsr.class);
   5.187 -                        } catch (Throwable ex) {
   5.188 -                            ex.printStackTrace();
   5.189 -                        } finally {
   5.190 -                            FINISHED.countDown();
   5.191 -                        }
   5.192 -                    }
   5.193 -                });
   5.194 -            }
   5.195 -            while (INSTANCE == null) {
   5.196 -                try {
   5.197 -                    FXBrwsr.class.wait();
   5.198 -                } catch (InterruptedException ex) {
   5.199 -                    // wait more
   5.200 -                }
   5.201 -            }
   5.202 -            if (!Platform.isFxApplicationThread()) {
   5.203 -                final WebView[] arr = { null };
   5.204 -                final CountDownLatch waitForResult = new CountDownLatch(1);
   5.205 -                Platform.runLater(new Runnable() {
   5.206 -                    @Override
   5.207 -                    public void run() {
   5.208 -                        arr[0] = INSTANCE.newView(url, onLoad);
   5.209 -                        waitForResult.countDown();
   5.210 -                    }
   5.211 -                });
   5.212 -                for (;;) {
   5.213 -                    try {
   5.214 -                        waitForResult.await();
   5.215 -                        break;
   5.216 -                    } catch (InterruptedException ex) {
   5.217 -                        Exceptions.printStackTrace(ex);
   5.218 -                    }
   5.219 -                }
   5.220 -                return arr[0];
   5.221 -            } else {
   5.222 -                return INSTANCE.newView(url, onLoad);
   5.223 -            }
   5.224 -        }
   5.225 -
   5.226 -        @Override
   5.227 -        public void start(Stage primaryStage) throws Exception {
   5.228 -            synchronized (FXBrwsr.class) {
   5.229 -                INSTANCE = this;
   5.230 -                FXBrwsr.class.notifyAll();
   5.231 -            }
   5.232 -            BorderPane r = new BorderPane();
   5.233 -            Scene scene = new Scene(r, 800, 600);
   5.234 -            primaryStage.setScene(scene);
   5.235 -            primaryStage.show();
   5.236 -            this.root = r;
   5.237 -        }
   5.238 -
   5.239 -        private WebView newView(final URL url, final FXPresenter onLoad) {
   5.240 -            final WebView view = new WebView();
   5.241 -            view.setContextMenuEnabled(false);
   5.242 -            view.getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
   5.243 -                @Override
   5.244 -                public void handle(WebEvent<String> t) {
   5.245 -                    final Stage dialogStage = new Stage();
   5.246 -                    dialogStage.initModality(Modality.WINDOW_MODAL);
   5.247 -                    dialogStage.setTitle("Warning");
   5.248 -                    final Button button = new Button("Close");
   5.249 -                    final Text text = new Text(t.getData());
   5.250 -
   5.251 -                    VBox box = new VBox();
   5.252 -                    box.setAlignment(Pos.CENTER);
   5.253 -                    box.setSpacing(10);
   5.254 -                    box.setPadding(new Insets(10));
   5.255 -                    box.getChildren().addAll(text, button);
   5.256 -
   5.257 -                    dialogStage.setScene(new Scene(box));
   5.258 -
   5.259 -                    button.setCancelButton(true);
   5.260 -                    button.setOnAction(new EventHandler<ActionEvent>() {
   5.261 -                        @Override
   5.262 -                        public void handle(ActionEvent t) {
   5.263 -                            dialogStage.close();
   5.264 -                        }
   5.265 -                    });
   5.266 -
   5.267 -                    dialogStage.centerOnScreen();
   5.268 -                    dialogStage.showAndWait();
   5.269 -                }
   5.270 -            });
   5.271 -            root.setCenter(view);
   5.272 -
   5.273 -            final Worker<Void> w = view.getEngine().getLoadWorker();
   5.274 -            w.stateProperty().addListener(new ChangeListener<Worker.State>() {
   5.275 -                @Override
   5.276 -                public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
   5.277 -                    if (newState.equals(Worker.State.SUCCEEDED)) {
   5.278 -                        onLoad.onPageLoad();
   5.279 -                    }
   5.280 -                    if (newState.equals(Worker.State.FAILED)) {
   5.281 -                        throw new IllegalStateException("Failed to load " + url);
   5.282 -                    }
   5.283 -                }
   5.284 -            });
   5.285 -            return view;
   5.286 -        }
   5.287 -
   5.288 -        private static void waitFinished() {
   5.289 -            for (;;) {
   5.290 -                try {
   5.291 -                    FINISHED.await();
   5.292 -                    break;
   5.293 -                } catch (InterruptedException ex) {
   5.294 -                    Exceptions.printStackTrace(ex);
   5.295 -                }
   5.296 -            }
   5.297 -        }
   5.298 -
   5.299 -    }    
   5.300  }
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/boot-fx/src/test/java/net/java/html/boot/fx/FXBrowsersTest.java	Thu Sep 12 09:33:50 2013 +0200
     6.3 @@ -0,0 +1,179 @@
     6.4 +/**
     6.5 + * HTML via Java(tm) Language Bindings
     6.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     6.7 + *
     6.8 + * This program is free software: you can redistribute it and/or modify
     6.9 + * it under the terms of the GNU General Public License as published by
    6.10 + * the Free Software Foundation, version 2 of the License.
    6.11 + *
    6.12 + * This program is distributed in the hope that it will be useful,
    6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.15 + * GNU General Public License for more details. apidesign.org
    6.16 + * designates this particular file as subject to the
    6.17 + * "Classpath" exception as provided by apidesign.org
    6.18 + * in the License file that accompanied this code.
    6.19 + *
    6.20 + * You should have received a copy of the GNU General Public License
    6.21 + * along with this program. Look for COPYING file in the top folder.
    6.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    6.23 + */
    6.24 +package net.java.html.boot.fx;
    6.25 +
    6.26 +import java.net.URL;
    6.27 +import java.util.concurrent.CountDownLatch;
    6.28 +import javafx.application.Application;
    6.29 +import javafx.application.Platform;
    6.30 +import javafx.scene.Scene;
    6.31 +import javafx.scene.layout.BorderPane;
    6.32 +import javafx.scene.web.WebView;
    6.33 +import javafx.stage.Stage;
    6.34 +import net.java.html.js.JavaScriptBody;
    6.35 +import static org.testng.Assert.assertEquals;
    6.36 +import static org.testng.Assert.assertNotEquals;
    6.37 +import static org.testng.Assert.assertNotNull;
    6.38 +import static org.testng.Assert.assertNotSame;
    6.39 +import org.testng.annotations.BeforeClass;
    6.40 +import org.testng.annotations.Test;
    6.41 +
    6.42 +/**
    6.43 + *
    6.44 + * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    6.45 + */
    6.46 +public class FXBrowsersTest {
    6.47 +    
    6.48 +    public FXBrowsersTest() {
    6.49 +    }
    6.50 +    
    6.51 +    @BeforeClass public void initFX() throws Throwable {
    6.52 +        new Thread("initFX") {
    6.53 +            @Override
    6.54 +            public void run() {
    6.55 +                App.launch(App.class);
    6.56 +            }
    6.57 +        }.start();
    6.58 +        App.CDL.await();
    6.59 +    }
    6.60 +
    6.61 +    @Test
    6.62 +    public void behaviorOfTwoWebViewsAtOnce() throws Throwable {
    6.63 +        class R implements Runnable {
    6.64 +            CountDownLatch DONE = new CountDownLatch(1);
    6.65 +            Throwable t;
    6.66 +
    6.67 +            @Override
    6.68 +            public void run() {
    6.69 +                try {
    6.70 +                    doTest();
    6.71 +                } catch (Throwable ex) {
    6.72 +                    t = ex;
    6.73 +                } finally {
    6.74 +                    DONE.countDown();
    6.75 +                }
    6.76 +            }
    6.77 +            
    6.78 +            private void doTest() throws Throwable {
    6.79 +                URL u = FXBrowsersTest.class.getResource("/org/apidesign/html/boot/fx/empty.html");
    6.80 +                assertNotNull(u, "URL found");
    6.81 +                FXBrowsers.load(App.getV1(), u, OnPages.class, "first");
    6.82 +                
    6.83 +            }
    6.84 +        }
    6.85 +        R run = new R();
    6.86 +        Platform.runLater(run);
    6.87 +        run.DONE.await();
    6.88 +        for (int i = 0; i < 100; i++) {
    6.89 +            if (run.t != null) {
    6.90 +                throw run.t;
    6.91 +            }
    6.92 +            if (System.getProperty("finalSecond") == null) {
    6.93 +                Thread.sleep(100);
    6.94 +            }
    6.95 +        }
    6.96 +        
    6.97 +        
    6.98 +        
    6.99 +        assertEquals(Integer.getInteger("finalFirst"), Integer.valueOf(3), "Three times in view one");
   6.100 +        assertEquals(Integer.getInteger("finalSecond"), Integer.valueOf(2), "Two times in view one");
   6.101 +    }
   6.102 +    
   6.103 +    public static class OnPages {
   6.104 +        static Class<?> first;
   6.105 +        static Object firstWindow;
   6.106 +        
   6.107 +        public static void first() {
   6.108 +            first = OnPages.class;
   6.109 +            firstWindow = window();
   6.110 +            assertNotNull(firstWindow, "First window found");
   6.111 +            
   6.112 +            assertEquals(increment(), 1, "Now it is one");
   6.113 +            
   6.114 +            URL u = FXBrowsersTest.class.getResource("/org/apidesign/html/boot/fx/empty.html");
   6.115 +            assertNotNull(u, "URL found");
   6.116 +            FXBrowsers.load(App.getV2(), u, OnPages.class, "second", "Hello");
   6.117 +            
   6.118 +            assertEquals(increment(), 2, "Now it is two and not influenced by second view");
   6.119 +            System.setProperty("finalFirst", "" + increment());
   6.120 +        }
   6.121 +        
   6.122 +        public static void second(String... args) {
   6.123 +            assertEquals(args.length, 1, "One string argument");
   6.124 +            assertEquals(args[0], "Hello", "It is hello");
   6.125 +            assertEquals(first, OnPages.class, "Both views share the same classloader");
   6.126 +            
   6.127 +            Object window = window();
   6.128 +            assertNotNull(window, "Some window found");
   6.129 +            assertNotNull(firstWindow, "First window is known");
   6.130 +            assertNotSame(firstWindow, window, "The window objects should be different");
   6.131 +            
   6.132 +            assertEquals(increment(), 1, "Counting starts from zero");
   6.133 +            System.setProperty("finalSecond", "" + increment());
   6.134 +        }
   6.135 +        
   6.136 +        @JavaScriptBody(args = {}, body = "return window;")
   6.137 +        private static native Object window();
   6.138 +        
   6.139 +        @JavaScriptBody(args = {}, body = ""
   6.140 +            + "if (window.cnt) return ++window.cnt;"
   6.141 +            + "return window.cnt = 1;"
   6.142 +        )
   6.143 +        private static native int increment();
   6.144 +    }
   6.145 +    
   6.146 +    public static class App extends Application {
   6.147 +        static final CountDownLatch CDL = new CountDownLatch(1);
   6.148 +        private static BorderPane pane;
   6.149 +
   6.150 +        /**
   6.151 +         * @return the v1
   6.152 +         */
   6.153 +        static WebView getV1() {
   6.154 +            return (WebView)System.getProperties().get("v1");
   6.155 +        }
   6.156 +
   6.157 +        /**
   6.158 +         * @return the v2
   6.159 +         */
   6.160 +        static WebView getV2() {
   6.161 +            return (WebView)System.getProperties().get("v2");
   6.162 +        }
   6.163 +
   6.164 +        @Override
   6.165 +        public void start(Stage stage) throws Exception {
   6.166 +            pane= new BorderPane();
   6.167 +            Scene scene = new Scene(pane, 800, 600);
   6.168 +            stage.setScene(scene);
   6.169 +            
   6.170 +            System.getProperties().put("v1", new WebView());
   6.171 +            System.getProperties().put("v2", new WebView());
   6.172 +
   6.173 +            pane.setCenter(getV1());
   6.174 +            pane.setBottom(getV2());
   6.175 +
   6.176 +            stage.show();
   6.177 +            CDL.countDown();
   6.178 +        }
   6.179 +        
   6.180 +        
   6.181 +    }
   6.182 +}
     7.1 --- a/boot-fx/src/test/java/org/apidesign/html/boot/fx/BootstrapTest.java	Wed Sep 11 14:49:49 2013 +0200
     7.2 +++ b/boot-fx/src/test/java/org/apidesign/html/boot/fx/BootstrapTest.java	Thu Sep 12 09:33:50 2013 +0200
     7.3 @@ -26,6 +26,8 @@
     7.4  import java.util.List;
     7.5  import java.util.concurrent.Executors;
     7.6  import net.java.html.boot.BrowserBuilder;
     7.7 +import org.apidesign.html.boot.impl.FnUtils;
     7.8 +import org.apidesign.html.boot.spi.Fn;
     7.9  import static org.testng.Assert.*;
    7.10  import org.testng.annotations.Factory;
    7.11  import org.testng.annotations.Test;
    7.12 @@ -36,6 +38,7 @@
    7.13   */
    7.14  public class BootstrapTest {
    7.15      private static Class<?> browserClass;
    7.16 +    private static Fn.Presenter browserPresenter;
    7.17      
    7.18      public BootstrapTest() {
    7.19      }
    7.20 @@ -58,7 +61,7 @@
    7.21              asSubclass(Annotation.class);
    7.22          for (Method m : loadClass().getMethods()) {
    7.23              if (m.getAnnotation(test) != null) {
    7.24 -                res.add(new KOFx(m));
    7.25 +                res.add(new KOFx(browserPresenter, m));
    7.26              }
    7.27          }
    7.28          return res.toArray();
    7.29 @@ -73,6 +76,7 @@
    7.30      
    7.31      public static synchronized void ready(Class<?> browserCls) throws Exception {
    7.32          browserClass = browserCls;
    7.33 +        browserPresenter = FnUtils.currentPresenter();
    7.34          BootstrapTest.class.notifyAll();
    7.35      }
    7.36      
     8.1 --- a/boot-fx/src/test/java/org/apidesign/html/boot/fx/KOFx.java	Wed Sep 11 14:49:49 2013 +0200
     8.2 +++ b/boot-fx/src/test/java/org/apidesign/html/boot/fx/KOFx.java	Thu Sep 12 09:33:50 2013 +0200
     8.3 @@ -23,6 +23,8 @@
     8.4  import java.lang.reflect.InvocationTargetException;
     8.5  import java.lang.reflect.Method;
     8.6  import javafx.application.Platform;
     8.7 +import org.apidesign.html.boot.impl.FnUtils;
     8.8 +import org.apidesign.html.boot.spi.Fn;
     8.9  import org.testng.IHookCallBack;
    8.10  import org.testng.IHookable;
    8.11  import org.testng.ITest;
    8.12 @@ -34,11 +36,13 @@
    8.13   * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.14   */
    8.15  public final class KOFx implements ITest, IHookable, Runnable {
    8.16 +    private final Fn.Presenter p;
    8.17      private final Method m;
    8.18      private Object result;
    8.19      private Object inst;
    8.20  
    8.21 -    KOFx(Method m) {
    8.22 +    KOFx(Fn.Presenter p, Method m) {
    8.23 +        this.p = p;
    8.24          this.m = m;
    8.25      }
    8.26  
    8.27 @@ -65,6 +69,7 @@
    8.28      public synchronized void run() {
    8.29          boolean notify = true;
    8.30          try {
    8.31 +            FnUtils.currentPresenter(p);
    8.32              if (inst == null) {
    8.33                  inst = m.getDeclaringClass().newInstance();
    8.34              }
    8.35 @@ -86,6 +91,7 @@
    8.36              if (notify) {
    8.37                  notifyAll();
    8.38              }
    8.39 +            FnUtils.currentPresenter(null);
    8.40          }
    8.41      }
    8.42  
     9.1 --- a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java	Wed Sep 11 14:49:49 2013 +0200
     9.2 +++ b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java	Thu Sep 12 09:33:50 2013 +0200
     9.3 @@ -76,18 +76,22 @@
     9.4      private Runnable onLoad;
     9.5      private String methodName;
     9.6      private String[] methodArgs;
     9.7 +    private final Object[] context;
     9.8      
     9.9 -    private BrowserBuilder() {
    9.10 +    private BrowserBuilder(Object[] context) {
    9.11 +        this.context = context;
    9.12      }
    9.13  
    9.14      /** Entry method to obtain a new browser builder. Follow by calling 
    9.15       * its instance methods like {@link #loadClass(java.lang.Class)} and
    9.16       * {@link #loadPage(java.lang.String)}.
    9.17       * 
    9.18 +     * @param context any instances that should be available to the builder -
    9.19 +     *   implemenation dependant
    9.20       * @return new browser builder
    9.21       */
    9.22 -    public static BrowserBuilder newBrowser() {
    9.23 -        return new BrowserBuilder();
    9.24 +    public static BrowserBuilder newBrowser(Object... context) {
    9.25 +        return new BrowserBuilder(context);
    9.26      }
    9.27      
    9.28      /** The class to load when the browser is initialized. This class
    9.29 @@ -149,7 +153,6 @@
    9.30              throw new NullPointerException("Need to specify resource via loadPage method");
    9.31          }
    9.32          
    9.33 -        FImpl impl = new FImpl(clazz.getClassLoader());
    9.34          URL url = null;
    9.35          MalformedURLException mal = null;
    9.36          try {
    9.37 @@ -181,52 +184,79 @@
    9.38              }
    9.39              throw ise;
    9.40          }
    9.41 +        
    9.42 +        Fn.Presenter dfnr = null;
    9.43 +        for (Object o : context) {
    9.44 +            if (o instanceof Fn.Presenter) {
    9.45 +                dfnr = (Fn.Presenter)o;
    9.46 +                break;
    9.47 +            }
    9.48 +        }
    9.49  
    9.50 -        for (Fn.Presenter dfnr : ServiceLoader.load(Fn.Presenter.class)) {
    9.51 -            final ClassLoader loader = FnUtils.newLoader(impl, dfnr, clazz.getClassLoader().getParent());
    9.52 +        if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
    9.53 +            dfnr = o;
    9.54 +            break;
    9.55 +        }
    9.56 +        
    9.57 +        if (dfnr == null) {
    9.58 +            throw new IllegalStateException("Can't find any Fn.Presenter");
    9.59 +        }
    9.60 +        
    9.61 +        final ClassLoader loader;
    9.62 +        if (FnUtils.isJavaScriptCapable(clazz.getClassLoader())) {
    9.63 +            loader = clazz.getClassLoader();
    9.64 +        } else {
    9.65 +            FImpl impl = new FImpl(clazz.getClassLoader());
    9.66 +            loader = FnUtils.newLoader(impl, dfnr, clazz.getClassLoader().getParent());
    9.67 +        }
    9.68  
    9.69 -            class OnPageLoad implements Runnable {
    9.70 -                @Override
    9.71 -                public void run() {
    9.72 -                    try {
    9.73 -                        Thread.currentThread().setContextClassLoader(loader);
    9.74 -                        Class<?> newClazz = Class.forName(clazz.getName(), true, loader);
    9.75 -                        if (browserClass != null) {
    9.76 -                            browserClass[0] = newClazz;
    9.77 -                        }
    9.78 -                        if (onLoad != null) {
    9.79 -                            onLoad.run();
    9.80 -                        }
    9.81 -                        INIT: if (methodName != null) {
    9.82 -                            Throwable firstError = null;
    9.83 -                            if (methodArgs.length == 0) {
    9.84 -                                try {
    9.85 -                                    Method m = newClazz.getMethod(methodName);
    9.86 -                                    m.invoke(null);
    9.87 -                                    break INIT;
    9.88 -                                } catch (Throwable ex) {
    9.89 -                                    firstError = ex;
    9.90 -                                }
    9.91 -                            }
    9.92 +        final Fn.Presenter currentP = dfnr;
    9.93 +        class OnPageLoad implements Runnable {
    9.94 +            @Override
    9.95 +            public void run() {
    9.96 +                try {
    9.97 +                    Thread.currentThread().setContextClassLoader(loader);
    9.98 +                    Class<?> newClazz = Class.forName(clazz.getName(), true, loader);
    9.99 +                    if (browserClass != null) {
   9.100 +                        browserClass[0] = newClazz;
   9.101 +                    }
   9.102 +                    if (onLoad != null) {
   9.103 +                        onLoad.run();
   9.104 +                    }
   9.105 +                    INIT: if (methodName != null) {
   9.106 +                        Throwable firstError = null;
   9.107 +                        if (methodArgs.length == 0) {
   9.108                              try {
   9.109 -                                Method m = newClazz.getMethod(methodName, String[].class);
   9.110 -                                m.invoke(m, (Object) methodArgs);
   9.111 +                                Method m = newClazz.getMethod(methodName);
   9.112 +                                FnUtils.currentPresenter(currentP);
   9.113 +                                m.invoke(null);
   9.114 +                                break INIT;
   9.115                              } catch (Throwable ex) {
   9.116 -                                if (firstError != null) {
   9.117 -                                    LOG.log(Level.SEVERE, "Can't call " + methodName, firstError);
   9.118 -                                }
   9.119 -                                LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
   9.120 +                                firstError = ex;
   9.121 +                            } finally {
   9.122 +                                FnUtils.currentPresenter(null);
   9.123                              }
   9.124                          }
   9.125 -                    } catch (ClassNotFoundException ex) {
   9.126 -                        LOG.log(Level.SEVERE, "Can't load " + clazz.getName(), ex);
   9.127 +                        try {
   9.128 +                            Method m = newClazz.getMethod(methodName, String[].class);
   9.129 +                            FnUtils.currentPresenter(currentP);
   9.130 +                            m.invoke(m, (Object) methodArgs);
   9.131 +                        } catch (Throwable ex) {
   9.132 +                            if (firstError != null) {
   9.133 +                                LOG.log(Level.SEVERE, "Can't call " + methodName, firstError);
   9.134 +                            }
   9.135 +                            LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
   9.136 +                        } finally {
   9.137 +                            FnUtils.currentPresenter(null);
   9.138 +                        }
   9.139                      }
   9.140 +                } catch (ClassNotFoundException ex) {
   9.141 +                    LOG.log(Level.SEVERE, "Can't load " + clazz.getName(), ex);
   9.142                  }
   9.143              }
   9.144 -            dfnr.displayPage(url, new OnPageLoad());
   9.145 -            return;
   9.146          }
   9.147 -        throw new IllegalStateException("Can't find any Fn.Definer");
   9.148 +        dfnr.displayPage(url, new OnPageLoad());
   9.149 +        return;
   9.150      }
   9.151  
   9.152      private static final class FImpl implements FindResources {
    10.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Wed Sep 11 14:49:49 2013 +0200
    10.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Thu Sep 12 09:33:50 2013 +0200
    10.3 @@ -35,12 +35,21 @@
    10.4   * @author Jaroslav Tulach <jtulach@netbeans.org>
    10.5   */
    10.6  public final class FnUtils {
    10.7 +    private static final ThreadLocal<Fn.Presenter> CURRENT = new ThreadLocal<Fn.Presenter>();
    10.8 +    
    10.9      private FnUtils() {
   10.10      }
   10.11 -
   10.12 +    
   10.13      public static Fn define(Class<?> caller, String code, String... names) {
   10.14 -        JsClassLoader cl = (JsClassLoader)caller.getClassLoader();
   10.15 -        return cl.defineFn(code, names);
   10.16 +        return currentPresenter().defineFn(code, names);
   10.17 +    }
   10.18 +    
   10.19 +    public static boolean isJavaScriptCapable(ClassLoader l) {
   10.20 +        return l instanceof JsClassLoader;
   10.21 +    }
   10.22 +    
   10.23 +    public static boolean isValid(Fn fn) {
   10.24 +        return fn != null && fn.isValid();
   10.25      }
   10.26  
   10.27      public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
   10.28 @@ -111,4 +120,18 @@
   10.29              throw new IllegalStateException("Can't execute " + resource, ex);
   10.30          } 
   10.31      }
   10.32 +
   10.33 +    public static Fn.Presenter currentPresenter() {
   10.34 +        Fn.Presenter p = CURRENT.get();
   10.35 +        if (p == null) {
   10.36 +            throw new IllegalStateException("No current WebView context around!");
   10.37 +        }
   10.38 +        return p;
   10.39 +    }
   10.40 +    
   10.41 +    public static Fn.Presenter currentPresenter(Fn.Presenter p) {
   10.42 +        Fn.Presenter prev = CURRENT.get();
   10.43 +        CURRENT.set(p);
   10.44 +        return prev;
   10.45 +    }
   10.46  }
    11.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java	Wed Sep 11 14:49:49 2013 +0200
    11.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java	Thu Sep 12 09:33:50 2013 +0200
    11.3 @@ -219,8 +219,18 @@
    11.4              StringBuilder source = new StringBuilder();
    11.5              source.append("package ").append(pkgName).append(";\n");
    11.6              source.append("public final class $JsCallbacks$ {\n");
    11.7 -            source.append("  static final $JsCallbacks$ VM = new $JsCallbacks$();\n");
    11.8 -            source.append("  private $JsCallbacks$() {}\n");
    11.9 +            source.append("  static final $JsCallbacks$ VM = new $JsCallbacks$(null);\n");
   11.10 +            source.append("  private final org.apidesign.html.boot.spi.Fn.Presenter p;\n");
   11.11 +            source.append("  private $JsCallbacks$ last;\n");
   11.12 +            source.append("  private $JsCallbacks$(org.apidesign.html.boot.spi.Fn.Presenter p) {\n");
   11.13 +            source.append("    this.p = p;\n");
   11.14 +            source.append("  }\n");
   11.15 +            source.append("  final $JsCallbacks$ current() {\n");
   11.16 +            source.append("    org.apidesign.html.boot.spi.Fn.Presenter now = org.apidesign.html.boot.impl.FnUtils.currentPresenter();\n");
   11.17 +            source.append("    if (now == p) return this;\n");
   11.18 +            source.append("    if (last != null && now == last.p) return last;\n");
   11.19 +            source.append("    return last = new $JsCallbacks$(now);\n");
   11.20 +            source.append("  }\n");
   11.21              for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
   11.22                  final String mangled = entry.getKey();
   11.23                  final ExecutableElement m = entry.getValue();
   11.24 @@ -251,6 +261,7 @@
   11.25                      sep = ",";
   11.26                  }
   11.27                  source.append(" {\n");
   11.28 +                source.append("    org.apidesign.html.boot.spi.Fn.Presenter $$prev = org.apidesign.html.boot.impl.FnUtils.currentPresenter(p); try { \n");
   11.29                  source.append("    ");
   11.30                  if (m.getReturnType().getKind() != TypeKind.VOID) {
   11.31                      source.append("return ");
   11.32 @@ -274,6 +285,7 @@
   11.33                  if (m.getReturnType().getKind() == TypeKind.VOID) {
   11.34                      source.append("    return null;\n");
   11.35                  }
   11.36 +                source.append("    } finally { org.apidesign.html.boot.impl.FnUtils.currentPresenter($$prev); }\n");
   11.37                  source.append("  }\n");
   11.38              }
   11.39              source.append("}\n");
    12.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Wed Sep 11 14:49:49 2013 +0200
    12.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Thu Sep 12 09:33:50 2013 +0200
    12.3 @@ -73,6 +73,9 @@
    12.4          if (name.equals(Fn.class.getName())) {
    12.5              return Fn.class;
    12.6          }
    12.7 +        if (name.equals(Fn.Presenter.class.getName())) {
    12.8 +            return Fn.Presenter.class;
    12.9 +        }
   12.10          if (name.equals(FnUtils.class.getName())) {
   12.11              return FnUtils.class;
   12.12          }
   12.13 @@ -221,8 +224,13 @@
   12.14                      "Lorg/apidesign/html/boot/spi/Fn;"
   12.15                  );
   12.16                  super.visitInsn(Opcodes.DUP);
   12.17 +                super.visitMethodInsn(
   12.18 +                    Opcodes.INVOKESTATIC, 
   12.19 +                    "org/apidesign/html/boot/impl/FnUtils", "isValid", 
   12.20 +                    "(Lorg/apidesign/html/boot/spi/Fn;)Z"
   12.21 +                );
   12.22                  Label ifNotNull = new Label();
   12.23 -                super.visitJumpInsn(Opcodes.IFNONNULL, ifNotNull);
   12.24 +                super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
   12.25                  
   12.26                  // init Fn
   12.27                  super.visitInsn(Opcodes.POP);
   12.28 @@ -346,6 +354,7 @@
   12.29                      int lastSlash = FindInClass.this.name.lastIndexOf('/');
   12.30                      String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   12.31                      FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   12.32 +                    FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
   12.33                      FindInMethod.super.visitInsn(Opcodes.AASTORE);
   12.34                  }
   12.35                  
    13.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Wed Sep 11 14:49:49 2013 +0200
    13.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Thu Sep 12 09:33:50 2013 +0200
    13.3 @@ -22,13 +22,28 @@
    13.4  
    13.5  import java.io.Reader;
    13.6  import java.net.URL;
    13.7 +import org.apidesign.html.boot.impl.FnUtils;
    13.8  
    13.9  /**
   13.10   *
   13.11   * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
   13.12   */
   13.13  public abstract class Fn {
   13.14 -    public abstract Object invoke(Object thiz, Object... args) throws Exception;
   13.15 +    private final Presenter presenter;
   13.16 +    
   13.17 +    protected Fn(Presenter presenter) {
   13.18 +        this.presenter = presenter;
   13.19 +    }
   13.20 +    
   13.21 +    public final boolean isValid() {
   13.22 +        return FnUtils.currentPresenter() == presenter;
   13.23 +    }
   13.24 +    
   13.25 +    public final Object invoke(Object thiz, Object... args) throws Exception {
   13.26 +        return handleInvoke(thiz, args);
   13.27 +    }
   13.28 +    
   13.29 +    protected abstract Object handleInvoke(Object thiz, Object... args) throws Exception;
   13.30      
   13.31      public interface Presenter {
   13.32          public Fn defineFn(String code, String... names);
    14.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java	Wed Sep 11 14:49:49 2013 +0200
    14.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java	Thu Sep 12 09:33:50 2013 +0200
    14.3 @@ -34,12 +34,14 @@
    14.4  import javax.script.ScriptException;
    14.5  import org.apidesign.html.boot.spi.Fn;
    14.6  import org.testng.annotations.BeforeClass;
    14.7 +import org.testng.annotations.BeforeMethod;
    14.8  
    14.9  /**
   14.10   *
   14.11   * @author Jaroslav Tulach <jtulach@netbeans.org>
   14.12   */
   14.13  public class FnTest extends JsClassLoaderBase {
   14.14 +    private static Fn.Presenter presenter;
   14.15      
   14.16      public FnTest() {
   14.17      }
   14.18 @@ -79,9 +81,9 @@
   14.19                  sb.append("})()");
   14.20                  try {
   14.21                      final Object val = eng.eval(sb.toString());
   14.22 -                    return new Fn() {
   14.23 +                    return new Fn(this) {
   14.24                          @Override
   14.25 -                        public Object invoke(Object thiz, Object... args) throws Exception {
   14.26 +                        public Object handleInvoke(Object thiz, Object... args) throws Exception {
   14.27                              List<Object> all = new ArrayList<Object>(args.length + 1);
   14.28                              all.add(thiz == null ? val : thiz);
   14.29                              all.addAll(Arrays.asList(args));
   14.30 @@ -111,9 +113,12 @@
   14.31          }
   14.32          Impl impl = new Impl();
   14.33          ClassLoader loader = FnUtils.newLoader(impl, impl, parent);
   14.34 -       
   14.35 +        presenter = impl;
   14.36          
   14.37          methodClass = loader.loadClass(JsMethods.class.getName());
   14.38      }
   14.39 -    
   14.40 +
   14.41 +    @BeforeMethod public void initPresenter() {
   14.42 +        FnUtils.currentPresenter(presenter);
   14.43 +    }
   14.44  }
    15.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java	Wed Sep 11 14:49:49 2013 +0200
    15.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java	Thu Sep 12 09:33:50 2013 +0200
    15.3 @@ -34,12 +34,14 @@
    15.4  import javax.script.ScriptException;
    15.5  import org.testng.annotations.AfterClass;
    15.6  import org.testng.annotations.BeforeClass;
    15.7 +import org.testng.annotations.BeforeMethod;
    15.8  
    15.9  /**
   15.10   *
   15.11   * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
   15.12   */
   15.13  public class JsClassLoaderTest extends JsClassLoaderBase{
   15.14 +    private static Fn.Presenter loader;
   15.15  
   15.16      @BeforeClass
   15.17      public static void setUpClass() throws Exception {
   15.18 @@ -49,13 +51,18 @@
   15.19          final URL my = JsClassLoaderTest.class.getProtectionDomain().getCodeSource().getLocation();
   15.20          ClassLoader parent = JsClassLoaderTest.class.getClassLoader().getParent();
   15.21          final URLClassLoader ul = new URLClassLoader(new URL[] { my }, parent);
   15.22 -        JsClassLoader loader = new JsClassLoader(parent) {
   15.23 +        class MyCL extends JsClassLoader implements Fn.Presenter {
   15.24 +
   15.25 +            public MyCL(ClassLoader parent) {
   15.26 +                super(parent);
   15.27 +            }
   15.28 +            
   15.29              @Override
   15.30              protected URL findResource(String name) {
   15.31                  return ul.getResource(name);
   15.32              }
   15.33              @Override
   15.34 -            protected Fn defineFn(String code, String... names) {
   15.35 +            public Fn defineFn(String code, String... names) {
   15.36                  StringBuilder sb = new StringBuilder();
   15.37                  sb.append("(function() {");
   15.38                  sb.append("return function(");
   15.39 @@ -71,9 +78,9 @@
   15.40                  sb.append("})()");
   15.41                  try {
   15.42                      final Object val = eng.eval(sb.toString());
   15.43 -                    return new Fn() {
   15.44 +                    return new Fn(this) {
   15.45                          @Override
   15.46 -                        public Object invoke(Object thiz, Object... args) throws Exception {
   15.47 +                        public Object handleInvoke(Object thiz, Object... args) throws Exception {
   15.48                              List<Object> all = new ArrayList<Object>(args.length + 1);
   15.49                              all.add(thiz == null ? val : thiz);
   15.50                              all.addAll(Arrays.asList(args));
   15.51 @@ -93,16 +100,27 @@
   15.52  
   15.53              @Override
   15.54              protected Enumeration<URL> findResources(String name) {
   15.55 -                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
   15.56 +                throw new UnsupportedOperationException();
   15.57              }
   15.58  
   15.59              @Override
   15.60 -            protected void loadScript(Reader code) throws ScriptException {
   15.61 +            public void loadScript(Reader code) throws ScriptException {
   15.62                  eng.eval(code);
   15.63              }
   15.64 +
   15.65 +            @Override
   15.66 +            public void displayPage(URL page, Runnable onPageLoad) {
   15.67 +                throw new UnsupportedOperationException();
   15.68 +            }
   15.69          };
   15.70          
   15.71 -        methodClass = loader.loadClass(JsMethods.class.getName());
   15.72 +        MyCL l = new MyCL(parent);
   15.73 +        methodClass = l.loadClass(JsMethods.class.getName());
   15.74 +        loader = l;
   15.75 +    }
   15.76 +    
   15.77 +    @BeforeMethod public void initPresenter() {
   15.78 +        FnUtils.currentPresenter(loader);
   15.79      }
   15.80  
   15.81      @AfterClass
    16.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java	Wed Sep 11 14:49:49 2013 +0200
    16.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java	Thu Sep 12 09:33:50 2013 +0200
    16.3 @@ -27,6 +27,8 @@
    16.4  import javafx.application.Platform;
    16.5  import net.java.html.js.JavaScriptBody;
    16.6  import netscape.javascript.JSObject;
    16.7 +import org.apidesign.html.boot.impl.FnUtils;
    16.8 +import org.apidesign.html.boot.spi.Fn;
    16.9  import org.apidesign.html.context.spi.Contexts;
   16.10  import org.apidesign.html.json.spi.FunctionBinding;
   16.11  import org.apidesign.html.json.spi.JSONCall;
   16.12 @@ -44,35 +46,28 @@
   16.13   *
   16.14   * @author Jaroslav Tulach <jtulach@netbeans.org>
   16.15   */
   16.16 -@ServiceProvider(service = Contexts.Provider.class)
   16.17  public final class FXContext
   16.18 -implements Technology.BatchInit<JSObject>, Transfer, Contexts.Provider, WSTransfer<LoadWS> {
   16.19 +implements Technology.BatchInit<JSObject>, Transfer, WSTransfer<LoadWS> {
   16.20      static final Logger LOG = Logger.getLogger(FXContext.class.getName());
   16.21      private static Boolean javaScriptEnabled;
   16.22 +    private final Fn.Presenter browserContext;
   16.23 +
   16.24 +    public FXContext(Fn.Presenter browserContext) {
   16.25 +        this.browserContext = browserContext;
   16.26 +    }
   16.27      
   16.28      @JavaScriptBody(args = {}, body = "return true;")
   16.29      private static boolean isJavaScriptEnabledJs() {
   16.30          return false;
   16.31      }
   16.32      
   16.33 -    private static boolean isJavaScriptEnabled() {
   16.34 +    static boolean isJavaScriptEnabled() {
   16.35          if (javaScriptEnabled != null) {
   16.36              return javaScriptEnabled;
   16.37          }
   16.38          return javaScriptEnabled = isJavaScriptEnabledJs();
   16.39      }
   16.40  
   16.41 -    @Override
   16.42 -    public void fillContext(Contexts.Builder context, Class<?> requestor) {
   16.43 -        if (isJavaScriptEnabled()) {
   16.44 -            context.register(Technology.class, this, 100);
   16.45 -            context.register(Transfer.class, this, 100);
   16.46 -            if (areWebSocketsSupported()) {
   16.47 -                context.register(WSTransfer.class, this, 100);
   16.48 -            }
   16.49 -        }
   16.50 -    }
   16.51 -
   16.52      final boolean areWebSocketsSupported() {
   16.53          return LoadWS.isSupported();
   16.54      }
   16.55 @@ -153,11 +148,24 @@
   16.56      }
   16.57  
   16.58      @Override
   16.59 -    public void runSafe(Runnable r) {
   16.60 +    public void runSafe(final Runnable r) {
   16.61 +        class Wrap implements Runnable {
   16.62 +            @Override public void run() {
   16.63 +                Fn.Presenter prev = FnUtils.currentPresenter(browserContext);
   16.64 +                try {
   16.65 +                    r.run();
   16.66 +                } finally {
   16.67 +                    FnUtils.currentPresenter(prev);
   16.68 +                }
   16.69 +                
   16.70 +            }
   16.71 +        }
   16.72 +        Wrap w = new Wrap();
   16.73 +        
   16.74          if (Platform.isFxApplicationThread()) {
   16.75 -            r.run();
   16.76 +            w.run();
   16.77          } else {
   16.78 -            Platform.runLater(r);
   16.79 +            Platform.runLater(w);
   16.80          }
   16.81      }
   16.82  
   16.83 @@ -175,4 +183,20 @@
   16.84      public void close(LoadWS socket) {
   16.85          socket.close();
   16.86      }
   16.87 +    
   16.88 +    @ServiceProvider(service = Contexts.Provider.class)
   16.89 +    public static final class Prvdr implements Contexts.Provider {
   16.90 +        @Override
   16.91 +        public void fillContext(Contexts.Builder context, Class<?> requestor) {
   16.92 +            if (isJavaScriptEnabled()) {
   16.93 +                FXContext c = new FXContext(FnUtils.currentPresenter());
   16.94 +                
   16.95 +                context.register(Technology.class, c, 100);
   16.96 +                context.register(Transfer.class, c, 100);
   16.97 +                if (c.areWebSocketsSupported()) {
   16.98 +                    context.register(WSTransfer.class, c, 100);
   16.99 +                }
  16.100 +            }
  16.101 +        }
  16.102 +    }
  16.103  }
    17.1 --- a/ko-fx/src/test/java/org/apidesign/html/kofx/KOFx.java	Wed Sep 11 14:49:49 2013 +0200
    17.2 +++ b/ko-fx/src/test/java/org/apidesign/html/kofx/KOFx.java	Thu Sep 12 09:33:50 2013 +0200
    17.3 @@ -23,6 +23,8 @@
    17.4  import java.lang.reflect.InvocationTargetException;
    17.5  import java.lang.reflect.Method;
    17.6  import javafx.application.Platform;
    17.7 +import org.apidesign.html.boot.impl.FnUtils;
    17.8 +import org.apidesign.html.boot.spi.Fn;
    17.9  import org.testng.ITest;
   17.10  import org.testng.annotations.Test;
   17.11  
   17.12 @@ -31,12 +33,14 @@
   17.13   * @author Jaroslav Tulach <jtulach@netbeans.org>
   17.14   */
   17.15  public final class KOFx implements ITest, Runnable {
   17.16 +    private final Fn.Presenter p;
   17.17      private final Method m;
   17.18      private Object result;
   17.19      private Object inst;
   17.20      private int count;
   17.21  
   17.22 -    KOFx(Method m) {
   17.23 +    KOFx(Fn.Presenter p, Method m) {
   17.24 +        this.p = p;
   17.25          this.m = m;
   17.26      }
   17.27  
   17.28 @@ -63,6 +67,7 @@
   17.29      public synchronized void run() {
   17.30          boolean notify = true;
   17.31          try {
   17.32 +            FnUtils.currentPresenter(p);
   17.33              if (inst == null) {
   17.34                  inst = m.getDeclaringClass().newInstance();
   17.35              }
   17.36 @@ -86,6 +91,7 @@
   17.37              if (notify) {
   17.38                  notifyAll();
   17.39              }
   17.40 +            FnUtils.currentPresenter(null);
   17.41          }
   17.42      }
   17.43      
    18.1 --- a/ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java	Wed Sep 11 14:49:49 2013 +0200
    18.2 +++ b/ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java	Thu Sep 12 09:33:50 2013 +0200
    18.3 @@ -36,6 +36,8 @@
    18.4  import net.java.html.BrwsrCtx;
    18.5  import net.java.html.boot.BrowserBuilder;
    18.6  import net.java.html.js.JavaScriptBody;
    18.7 +import org.apidesign.html.boot.impl.FnUtils;
    18.8 +import org.apidesign.html.boot.spi.Fn;
    18.9  import org.apidesign.html.context.spi.Contexts;
   18.10  import org.apidesign.html.json.spi.Technology;
   18.11  import org.apidesign.html.json.spi.Transfer;
   18.12 @@ -55,6 +57,7 @@
   18.13  @ServiceProvider(service = KnockoutTCK.class)
   18.14  public final class KnockoutFXTest extends KnockoutTCK {
   18.15      private static Class<?> browserClass;
   18.16 +    private static Fn.Presenter browserContext;
   18.17      
   18.18      public KnockoutFXTest() {
   18.19      }
   18.20 @@ -99,7 +102,7 @@
   18.21              asSubclass(Annotation.class);
   18.22          for (Method m : c.getMethods()) {
   18.23              if (m.getAnnotation(koTest) != null) {
   18.24 -                res.add(new KOFx(m));
   18.25 +                res.add(new KOFx(browserContext, m));
   18.26              }
   18.27          }
   18.28      }
   18.29 @@ -113,6 +116,7 @@
   18.30      
   18.31      public static synchronized void initialized(Class<?> browserCls) throws Exception {
   18.32          browserClass = browserCls;
   18.33 +        browserContext = FnUtils.currentPresenter();
   18.34          KnockoutFXTest.class.notifyAll();
   18.35      }
   18.36      
   18.37 @@ -120,11 +124,12 @@
   18.38          Class<?> classpathClass = ClassLoader.getSystemClassLoader().loadClass(KnockoutFXTest.class.getName());
   18.39          Method m = classpathClass.getMethod("initialized", Class.class);
   18.40          m.invoke(null, KnockoutFXTest.class);
   18.41 +        browserContext = FnUtils.currentPresenter();
   18.42      }
   18.43      
   18.44      @Override
   18.45      public BrwsrCtx createContext() {
   18.46 -        FXContext fx = new FXContext();
   18.47 +        FXContext fx = new FXContext(browserContext);
   18.48          Contexts.Builder cb = Contexts.newBuilder().
   18.49              register(Technology.class, fx, 10).
   18.50              register(Transfer.class, fx, 10);
    19.1 --- a/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusFX.java	Wed Sep 11 14:49:49 2013 +0200
    19.2 +++ b/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusFX.java	Thu Sep 12 09:33:50 2013 +0200
    19.3 @@ -23,6 +23,8 @@
    19.4  import java.lang.reflect.InvocationTargetException;
    19.5  import java.lang.reflect.Method;
    19.6  import javafx.application.Platform;
    19.7 +import org.apidesign.html.boot.impl.FnUtils;
    19.8 +import org.apidesign.html.boot.spi.Fn;
    19.9  import org.testng.ITest;
   19.10  import org.testng.annotations.Test;
   19.11  
   19.12 @@ -31,12 +33,14 @@
   19.13   * @author Jaroslav Tulach <jtulach@netbeans.org>
   19.14   */
   19.15  public final class TyrusFX implements ITest, Runnable {
   19.16 +    private final Fn.Presenter p;
   19.17      private final Method m;
   19.18      private Object result;
   19.19      private Object inst;
   19.20      private int count;
   19.21  
   19.22 -    TyrusFX(Method m) {
   19.23 +    TyrusFX(Fn.Presenter p, Method m) {
   19.24 +        this.p = p;
   19.25          this.m = m;
   19.26      }
   19.27  
   19.28 @@ -63,6 +67,7 @@
   19.29      public synchronized void run() {
   19.30          boolean notify = true;
   19.31          try {
   19.32 +            FnUtils.currentPresenter(p);
   19.33              if (inst == null) {
   19.34                  inst = m.getDeclaringClass().newInstance();
   19.35              }
   19.36 @@ -86,6 +91,7 @@
   19.37              if (notify) {
   19.38                  notifyAll();
   19.39              }
   19.40 +            FnUtils.currentPresenter(null);
   19.41          }
   19.42      }
   19.43      
    20.1 --- a/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusKnockoutTest.java	Wed Sep 11 14:49:49 2013 +0200
    20.2 +++ b/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusKnockoutTest.java	Thu Sep 12 09:33:50 2013 +0200
    20.3 @@ -36,6 +36,8 @@
    20.4  import net.java.html.BrwsrCtx;
    20.5  import net.java.html.boot.BrowserBuilder;
    20.6  import net.java.html.js.JavaScriptBody;
    20.7 +import org.apidesign.html.boot.impl.FnUtils;
    20.8 +import org.apidesign.html.boot.spi.Fn;
    20.9  import org.apidesign.html.context.spi.Contexts;
   20.10  import org.apidesign.html.json.spi.Technology;
   20.11  import org.apidesign.html.json.spi.Transfer;
   20.12 @@ -56,6 +58,7 @@
   20.13  @ServiceProvider(service = KnockoutTCK.class)
   20.14  public final class TyrusKnockoutTest extends KnockoutTCK {
   20.15      private static Class<?> browserClass;
   20.16 +    private static Fn.Presenter browserContext;
   20.17      
   20.18      public TyrusKnockoutTest() {
   20.19      }
   20.20 @@ -92,7 +95,7 @@
   20.21                  asSubclass(Annotation.class);
   20.22              for (Method m : c.getMethods()) {
   20.23                  if (m.getAnnotation(koTest) != null) {
   20.24 -                    res.add(new TyrusFX(m));
   20.25 +                    res.add(new TyrusFX(browserContext, m));
   20.26                  }
   20.27              }
   20.28          }
   20.29 @@ -108,6 +111,7 @@
   20.30      
   20.31      public static synchronized void initialized(Class<?> browserCls) throws Exception {
   20.32          browserClass = browserCls;
   20.33 +        browserContext = FnUtils.currentPresenter();
   20.34          TyrusKnockoutTest.class.notifyAll();
   20.35      }
   20.36      
   20.37 @@ -115,11 +119,12 @@
   20.38          Class<?> classpathClass = ClassLoader.getSystemClassLoader().loadClass(TyrusKnockoutTest.class.getName());
   20.39          Method m = classpathClass.getMethod("initialized", Class.class);
   20.40          m.invoke(null, TyrusKnockoutTest.class);
   20.41 +        browserContext = FnUtils.currentPresenter();
   20.42      }
   20.43      
   20.44      @Override
   20.45      public BrwsrCtx createContext() {
   20.46 -        FXContext fx = new FXContext();
   20.47 +        FXContext fx = new FXContext(browserContext);
   20.48          TyrusContext tc = new TyrusContext();
   20.49          Contexts.Builder cb = Contexts.newBuilder().
   20.50              register(Technology.class, fx, 10).