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/10/26 08:23:32 UTC

[incubator-netbeans-html4j] 02/02: NETBEANS-99: Let @Model classes point directly to Knockout structures and only indirectly to JavaScript representation of ko objects. Stress test by FXGCPresenter

This is an automated email from the ASF dual-hosted git repository.

jtulach pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-netbeans-html4j.git

commit 246d0733a636def7bd4c951895e3b5d7cd73ce65
Author: Jaroslav Tulach <ja...@oracle.com>
AuthorDate: Thu Oct 26 10:24:07 2017 +0200

    NETBEANS-99: Let @Model classes point directly to Knockout structures and only indirectly to JavaScript representation of ko objects. Stress test by FXGCPresenter
---
 .../java/net/java/html/boot/fx/FXBrowsers.java     | 71 ++---------------
 .../netbeans/html/boot/fx/AbstractFXPresenter.java | 34 +++++---
 .../java/org/netbeans/html/boot/fx/FXBrwsr.java    |  4 +-
 .../fx/{FXPresenter.java => FXGCPresenter.java}    | 57 +++++++++++---
 .../org/netbeans/html/boot/fx/FXPresenter.java     |  6 +-
 .../netbeans/html/boot/fx/InitializeWebView.java   | 90 ++++++++++++++++++++++
 .../java/org/netbeans/html/json/impl/Bindings.java |  6 +-
 .../java/org/netbeans/html/json/impl/JSON.java     |  2 +-
 .../org/netbeans/html/json/spi/Technology.java     | 36 +++++++--
 .../netbeans/html/wstyrus/TyrusKnockoutTest.java   | 10 +--
 .../main/java/org/netbeans/html/ko4j/KOTech.java   | 41 ++++++----
 .../main/java/org/netbeans/html/ko4j/Knockout.java |  4 +
 .../org/netbeans/html/ko4j/KnockoutFXTest.java     |  3 +-
 src/main/javadoc/overview.html                     |  3 +
 .../org/netbeans/html/xhr4j/JsonKnockoutTest.java  |  3 +-
 15 files changed, 247 insertions(+), 123 deletions(-)

diff --git a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
index 8ceae82..4ae8909 100644
--- a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
+++ b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
@@ -18,16 +18,13 @@
  */
 package net.java.html.boot.fx;
 
+import org.netbeans.html.boot.fx.InitializeWebView;
 import java.net.URL;
 import javafx.application.Platform;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
-import javafx.concurrent.Worker;
 import javafx.scene.web.WebView;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
-import org.netbeans.html.boot.fx.AbstractFXPresenter;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.context.spi.Contexts.Id;
 
@@ -89,7 +86,7 @@ public final class FXBrowsers {
     ) {
         Object[] context = new Object[args.length + 1];
         System.arraycopy(args, 0, context, 1, args.length);
-        final Load load = new Load(webView, null);
+        final InitializeWebView load = new InitializeWebView(webView, null);
         context[0] = load;
         BrowserBuilder.newBrowser(context).
             loadPage(url.toExternalForm()).
@@ -172,7 +169,7 @@ public final class FXBrowsers {
     ) {
         Object[] newCtx = new Object[context.length + 1];
         System.arraycopy(context, 0, newCtx, 1, context.length);
-        final Load load = new Load(webView, onPageLoad);
+        final InitializeWebView load = new InitializeWebView(webView, onPageLoad);
         newCtx[0] = load;
         BrowserBuilder.newBrowser(newCtx).
                 loadPage(url.toExternalForm()).
@@ -203,67 +200,9 @@ public final class FXBrowsers {
      */
     public static void runInBrowser(WebView webView, Runnable code) {
         Object ud = webView.getUserData();
-        if (!(ud instanceof Load)) {
+        if (!(ud instanceof InitializeWebView)) {
             throw new IllegalArgumentException();
         }
-        ((Load)ud).ctx.execute(code);
+        ((InitializeWebView)ud).runInContext(code);
     }
-    
-    private static class Load extends AbstractFXPresenter implements Runnable {
-        private final WebView webView;
-        private final Runnable myLoad;
-        private BrwsrCtx ctx;
-
-        public Load(WebView webView, Runnable onLoad) {
-            this.webView = webView;
-            this.myLoad = onLoad;
-            webView.setUserData(this);
-        }
-
-        public void run() {
-            ctx = BrwsrCtx.findDefault(Load.class);
-            if (myLoad != null) {
-                myLoad.run();
-            }
-        }
-        
-        @Override
-        protected void waitFinished() {
-            // don't wait
-        }
-
-        @Override
-        protected WebView findView(final URL resource) {
-            final Worker<Void> w = webView.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()) {
-                            onPageLoad();
-                        }
-                    }
-                    if (newState.equals(Worker.State.FAILED)) {
-                        checkValid();
-                        throw new IllegalStateException("Failed to load " + resource);
-                    }
-                }
-
-                private boolean checkValid() {
-                    final String crnt = webView.getEngine().getLocation();
-                    if (previous != null && !previous.equals(crnt)) {
-                        w.stateProperty().removeListener(this);
-                        return false;
-                    }
-                    previous = crnt;
-                    return true;
-                }
-            });
-
-            return webView;
-        }
-    }
-    
 }
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
index 08590c1..53dce08 100644
--- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
@@ -22,6 +22,7 @@ import java.io.BufferedReader;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.Reader;
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Array;
 import java.net.URL;
@@ -182,9 +183,9 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
         waitFinished();
     }
 
-    protected abstract void waitFinished();
+    abstract void waitFinished();
 
-    protected abstract WebView findView(final URL resource);
+    abstract WebView findView(final URL resource);
 
     final JSObject convertArrays(Object[] arr) {
         for (int i = 0; i < arr.length; i++) {
@@ -569,8 +570,9 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
             Object java = obj.getMember("fxBrwsrId");
             if (java instanceof JSObject) {
                 for (;;) {
-                    int resultHash;
-                    int resultId;
+                    final int resultHash;
+                    final int resultId;
+                    final NavigableSet<Ref> refs;
                     synchronized (this) {
                         this.hash = -1;
                         this.id = -1;
@@ -579,28 +581,38 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
                         assert this.id != -1;
                         resultHash = this.hash;
                         resultId = this.id;
+                        refs = values.get(resultHash);
+                        if (refs == null) {
+                            return null;
+                        }
                     }
 
-                    final NavigableSet<Ref> refs = values.get(resultHash);
                     Iterator<Ref> it = refs.iterator();
                     while (it.hasNext()) {
                         Ref next = it.next();
-                        Object pojo = next.value();
-                        if (next.id() == resultId) {
-                            return pojo;
-                        }
-                        if (pojo == null) {
+                        Object[] pojo = { next.value() };
+                        if (pojo[0] == null) {
                             it.remove();
+                            continue;
+                        }
+                        if (next.id() == resultId) {
+                            return emitJavaObject(pojo, resultHash, resultId);
                         }
                     }
                     if (refs.isEmpty()) {
-                        values.remove(resultHash);
+                        synchronized (this) {
+                            values.remove(resultHash);
+                        }
                     }
                 }
             }
             return obj;
         }
+
     }
 
+    Object emitJavaObject(Object[] pojo, int hash, int id) {
+        return pojo[0];
+    }
 
 }
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
index 5fd6039..a6f96f7 100644
--- 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
@@ -65,7 +65,7 @@ public class FXBrwsr extends Application {
     private static final CountDownLatch FINISHED = new CountDownLatch(1);
     private BorderPane root;
 
-    public static synchronized WebView findWebView(final URL url, final FXPresenter onLoad) {
+    public static synchronized WebView findWebView(final URL url, final AbstractFXPresenter onLoad) {
         if (INSTANCE == null) {
             final String callee = findCalleeClassName();
             Executors.newFixedThreadPool(1).submit(new Runnable() {
@@ -211,7 +211,7 @@ public class FXBrwsr extends Application {
         return arr;
     }
 
-    private WebView newView(final URL url, final FXPresenter onLoad) {
+    private WebView newView(final URL url, final AbstractFXPresenter onLoad) {
         final WebView view = new WebView();
         view.setContextMenuEnabled(false);
         Stage newStage;
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/FXGCPresenter.java
similarity index 53%
copy from boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
copy to boot-fx/src/main/java/org/netbeans/html/boot/fx/FXGCPresenter.java
index 3be269e..330b13a 100644
--- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXGCPresenter.java
@@ -19,22 +19,22 @@
 package org.netbeans.html.boot.fx;
 
 import java.io.File;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.logging.Level;
 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.
+/** Presenter for stress testing. It tries to force few GC cycles
+ * before returning a Java object from {@link WebView} to simulate the
+ * fact that in JDK8 newer than build 112 the Java objects exposed to
+ * {@link WebView} are held by weak references.
  *
  * @author Jaroslav Tulach
  */
-@ServiceProvider(service = Fn.Presenter.class)
-public final class FXPresenter extends AbstractFXPresenter {
+public final class FXGCPresenter extends AbstractFXPresenter {
     static {
         try {
             try {
@@ -54,11 +54,48 @@ public final class FXPresenter extends AbstractFXPresenter {
         }
     }
 
-    protected void waitFinished() {
+    @Override
+    void waitFinished() {
         FXBrwsr.waitFinished();
     }
 
-    protected WebView findView(final URL resource) {
+    @Override
+    WebView findView(final URL resource) {
         return FXBrwsr.findWebView(resource, this);
     }
+
+    @Override
+    Object emitJavaObject(Object[] pojo, int hash, int id) {
+        Reference<Object> ref = new WeakReference<Object>(pojo[0]);
+        boolean nonNull = ref.get() != null;
+        assertGC(ref);
+        Object r;
+        if ((r = ref.get()) == null && nonNull) {
+            throw new NullPointerException("Value has been GCed to null for " + hash + " and " + id);
+        }
+        return r;
+    }
+
+    private static boolean isGone(Reference<?> ref) {
+        return ref.get() == null;
+    }
+
+    private static void assertGC(Reference<Object> ref) {
+        long l = System.currentTimeMillis();
+        for (int i = 0; i < 3; i++) {
+            if (isGone(ref)) {
+                return;
+            }
+
+            try {
+                System.gc();
+                System.runFinalization();
+            } catch (Error err) {
+                LOG.log(Level.INFO, "Problems during GCing attempt of " + ref.get(), err);
+            }
+        }
+        final long took = System.currentTimeMillis() - l;
+        LOG.log(Level.FINE, "Good: No GC of {1} for {0} ms.", new Object[]{took, ref.get()});
+    }
+
 }
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
index 3be269e..1554113 100644
--- 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
@@ -54,11 +54,13 @@ public final class FXPresenter extends AbstractFXPresenter {
         }
     }
 
-    protected void waitFinished() {
+    @Override
+    void waitFinished() {
         FXBrwsr.waitFinished();
     }
 
-    protected WebView findView(final URL resource) {
+    @Override
+    WebView findView(final URL resource) {
         return FXBrwsr.findWebView(resource, this);
     }
 }
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java
new file mode 100644
index 0000000..b093565
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.net.URL;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.concurrent.Worker;
+import javafx.scene.web.WebView;
+import net.java.html.BrwsrCtx;
+import org.netbeans.html.boot.fx.AbstractFXPresenter;
+
+public final class InitializeWebView extends AbstractFXPresenter implements Runnable {
+
+    private final WebView webView;
+    private final Runnable myLoad;
+    BrwsrCtx ctx;
+
+    public InitializeWebView(WebView webView, Runnable onLoad) {
+        this.webView = webView;
+        this.myLoad = onLoad;
+        webView.setUserData(this);
+    }
+
+    @Override
+    public void run() {
+        ctx = BrwsrCtx.findDefault(InitializeWebView.class);
+        if (myLoad != null) {
+            myLoad.run();
+        }
+    }
+
+    @Override
+    void waitFinished() {
+        // don't wait
+    }
+
+    @Override
+    WebView findView(final URL resource) {
+        final Worker<Void> w = webView.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()) {
+                        onPageLoad();
+                    }
+                }
+                if (newState.equals(Worker.State.FAILED)) {
+                    checkValid();
+                    throw new IllegalStateException("Failed to load " + resource);
+                }
+            }
+
+            private boolean checkValid() {
+                final String crnt = webView.getEngine().getLocation();
+                if (previous != null && !previous.equals(crnt)) {
+                    w.stateProperty().removeListener(this);
+                    return false;
+                }
+                previous = crnt;
+                return true;
+            }
+        });
+        return webView;
+    }
+
+    public final void runInContext(Runnable r) {
+        ctx.execute(r);
+    }
+
+}
diff --git a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java
index fdd1b57..3b0e062 100644
--- a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java
+++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java
@@ -69,7 +69,11 @@ public final class Bindings<Data> {
     }
     
     
-    public Data koData() {
+    final Object jsObj() {
+        if (bp instanceof Technology.ToJavaScript) {
+            Technology.ToJavaScript<Data> toJS = (Technology.ToJavaScript<Data>) bp;
+            return toJS.toJavaScript(data);
+        }
         return data;
     }
 
diff --git a/json/src/main/java/org/netbeans/html/json/impl/JSON.java b/json/src/main/java/org/netbeans/html/json/impl/JSON.java
index 1745c0c..09c691f 100644
--- a/json/src/main/java/org/netbeans/html/json/impl/JSON.java
+++ b/json/src/main/java/org/netbeans/html/json/impl/JSON.java
@@ -267,7 +267,7 @@ public final class JSON {
             return null;
         }
         final Bindings b = PropertyBindingAccessor.getBindings(proto, true, null);
-        return b == null ? null : b.koData();
+        return b == null ? null : b.jsObj();
     }
 
     private static Proto findProto(Object object) {
diff --git a/json/src/main/java/org/netbeans/html/json/spi/Technology.java b/json/src/main/java/org/netbeans/html/json/spi/Technology.java
index 2e0a206..b138dbe 100644
--- a/json/src/main/java/org/netbeans/html/json/spi/Technology.java
+++ b/json/src/main/java/org/netbeans/html/json/spi/Technology.java
@@ -23,16 +23,19 @@ import net.java.html.json.Model;
 import net.java.html.json.Models;
 import org.netbeans.html.context.spi.Contexts.Id;
 
-/** An implementation of a binding between model classes (see {@link Model})
+/** *  An implementation of a binding between model classes (see {@link Model})
  * and particular technology like <a href="http://knockoutjs.com">knockout.js</a>
- * in a browser window, etc.
- * Since introduction of {@link Id technology identifiers} one can choose between
- * different background implementations to handle the conversion and
- * communication requests. The currently known provider is
- * <code>org.netbeans.html:ko4j</code> module which registers 
+ * in a browser window, etc.Since introduction of {@link Id technology identifiers} one can choose between
+ different background implementations to handle the conversion and
+ communication requests.
+ * The currently known provider is
+ <code>org.netbeans.html:ko4j</code> module which registers 
  * a <a href="http://knockoutjs.com" target="_blank">knockout.js</a>
  * implementation called <b>ko4j</b>.
  *
+ * @param <Data> technology internal type that keeps internal data for each
+ *    instance of {@linkplains Model model class}.
+ *
  * @author Jaroslav Tulach
  */
 public interface Technology<Data> {
@@ -189,4 +192,25 @@ public interface Technology<Data> {
          */
         public D wrapModel(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr);
     }
+
+    /** Convertor of the internal data type to object suitable as a JavaScript
+     * representation. Certain technologies need to keep some data in Java
+     * and only part of them in JavaScript-ready object. With the help of
+     * {@code ToJavaScript} interface, they can parametrize their
+     * {@link Technology} with the Java type and implement
+     * {@link #toJavaScript(java.lang.Object)}
+     * method to extract the proper JavaScript part from that object.
+     *
+     * @param <D> the internal data type
+     * @since 1.5.1
+     */
+    public static interface ToJavaScript<D> extends Technology<D> {
+        /** Extracts JavaScript ready representation.
+         *
+         * @param data technology's internal data structure
+         * @return object ready to represent the data in JavaScript
+         * @since 1.5.1
+         */
+        public Object toJavaScript(D data);
+    }
 }
diff --git a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java
index 0a17692..c651346 100644
--- a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java
+++ b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java
@@ -35,6 +35,9 @@ import java.util.concurrent.Executors;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.netbeans.html.boot.fx.FXGCPresenter;
 import org.netbeans.html.boot.spi.Fn;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.json.spi.Technology;
@@ -42,13 +45,10 @@ import org.netbeans.html.json.spi.Transfer;
 import org.netbeans.html.json.spi.WSTransfer;
 import org.netbeans.html.json.tck.KOTest;
 import org.netbeans.html.json.tck.KnockoutTCK;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.netbeans.html.boot.impl.FnContext;
 import org.netbeans.html.ko4j.KO4J;
 import org.openide.util.lookup.ServiceProvider;
 import org.testng.Assert;
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
 import org.testng.annotations.Factory;
 
 /**
@@ -75,7 +75,7 @@ public final class TyrusKnockoutTest extends KnockoutTCK {
         
         URI uri = TyrusDynamicHTTP.initServer();
     
-        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(TyrusKnockoutTest.class).
+        final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXGCPresenter()).loadClass(TyrusKnockoutTest.class).
             loadPage(uri.toString()).
             invoke("initialized");
         
diff --git a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
index c94e5ae..32f0ea9 100644
--- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
+++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
@@ -34,7 +34,8 @@ import org.netbeans.html.json.spi.Technology;
  */
 @Contexts.Id("ko4j")
 final class KOTech
-implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Technology.ApplyId<Object> {
+implements Technology.BatchCopy<Knockout>, Technology.ValueMutated<Knockout>,
+Technology.ApplyId<Knockout>, Technology.ToJavaScript<Knockout> {
     private Object[] jsObjects;
     private int jsIndex;
 
@@ -42,11 +43,11 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
     }
     
     @Override
-    public Object wrapModel(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr) {
+    public Knockout wrapModel(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr) {
         return createKO(model, copyFrom, propArr, funcArr, null);
     }
 
-    final Object createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) {
+    final Knockout createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) {
         String[] propNames = new String[propArr.length];
         Number[] propInfo = new Number[propArr.length];
         Object[] propValues = new Object[propArr.length];
@@ -76,7 +77,7 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
             ret, copyFrom,
             propNames, propInfo, propValues, funcNames
         );
-        return ret;
+        return newKO;
     }
     
     private Object getJSObject() {
@@ -95,42 +96,43 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
     }
     
     @Override
-    public Object wrapModel(Object model) {
+    public Knockout wrapModel(Object model) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void bind(PropertyBinding b, Object model, Object data) {
+    public void bind(PropertyBinding b, Object model, Knockout data) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void valueHasMutated(Object data, String propertyName) {
-        Knockout.cleanUp();
-        Knockout.valueHasMutated(data, propertyName, null, null);
+    public void valueHasMutated(Knockout data, String propertyName) {
+        valueHasMutated(data, propertyName, null, null);
     }
     
     @Override
-    public void valueHasMutated(Object data, String propertyName, Object oldValue, Object newValue) {
+    public void valueHasMutated(Knockout data, String propertyName, Object oldValue, Object newValue) {
         Knockout.cleanUp();
-        if (newValue instanceof Enum) {
-            newValue = newValue.toString();
+        if (data != null) {
+            if (newValue instanceof Enum) {
+                newValue = newValue.toString();
+            }
+            Knockout.valueHasMutated(data.js(), propertyName, oldValue, newValue);
         }
-        Knockout.valueHasMutated(data, propertyName, oldValue, newValue);
     }
 
     @Override
-    public void expose(FunctionBinding fb, Object model, Object d) {
+    public void expose(FunctionBinding fb, Object model, Knockout data) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void applyBindings(Object data) {
+    public void applyBindings(Knockout data) {
         applyBindings(null, data);
     }
     @Override
-    public void applyBindings(String id, Object data) {
-        Object ko = Knockout.applyBindings(id, data);
+    public void applyBindings(String id, Knockout data) {
+        Object ko = Knockout.applyBindings(id, data.js());
         if (ko instanceof Knockout) {
             ((Knockout)ko).hold();
             applied.add((Knockout) ko);
@@ -153,4 +155,9 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
     public <M> M toModel(Class<M> modelClass, Object data) {
         return modelClass.cast(Knockout.toModel(data));
     }
+
+    @Override
+    public Object toJavaScript(Knockout data) {
+        return data.js();
+    }
 }
diff --git a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
index 7500715..ae52c39 100644
--- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
+++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
@@ -71,6 +71,10 @@ final class Knockout  {
         }
     }
 
+    final Object js() {
+        return js;
+    }
+
     static void cleanUp() {
         for (;;) {
             Knockout ko = null;
diff --git a/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java b/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java
index 4306983..8bde691 100644
--- a/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java
+++ b/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java
@@ -34,6 +34,7 @@ import java.util.concurrent.Executors;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
+import org.netbeans.html.boot.fx.FXGCPresenter;
 import org.netbeans.html.boot.spi.Fn;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.json.spi.Technology;
@@ -70,7 +71,7 @@ public final class KnockoutFXTest extends KnockoutTCK {
         
         URI uri = DynamicHTTP.initServer();
     
-        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(KnockoutFXTest.class).
+        final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXGCPresenter()).loadClass(KnockoutFXTest.class).
             loadPage(uri.toString()).
             invoke("initialized");
         
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
index 9bdbc8b..b9f066a 100644
--- a/src/main/javadoc/overview.html
+++ b/src/main/javadoc/overview.html
@@ -60,6 +60,9 @@
         {@link net.java.html.json.ComputedProperty computing a property}.
         Regular subclassing of {@link org.netbeans.html.json.spi.Proto.Type} is
         possible.
+        Bugfix <a target="_blank" href="https://issues.apache.org/jira/browse/NETBEANS-99">#99</a>
+        - better garbage collector related behavior of <b>ko4j</b> instances thanks
+        to introduction of {@link org.netbeans.html.json.spi.Technology.ToJavaScript}.
         
         <h3>New in version 1.5</h3>
 
diff --git a/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java
index b148218..168de1d 100644
--- a/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java
+++ b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java
@@ -35,6 +35,7 @@ import java.util.concurrent.Executors;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
+import org.netbeans.html.boot.fx.FXGCPresenter;
 import org.netbeans.html.boot.spi.Fn;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.json.spi.Technology;
@@ -69,7 +70,7 @@ public final class JsonKnockoutTest extends KnockoutTCK {
         
         URI uri = JsonDynamicHTTP.initServer();
     
-        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(JsonKnockoutTest.class).
+        final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXGCPresenter()).loadClass(JsonKnockoutTest.class).
             loadPage(uri.toString()).
             invoke("initialized");
         

-- 
To stop receiving notification emails like this one, please contact
"commits@netbeans.apache.org" <co...@netbeans.apache.org>.