1.1 --- a/boot/src/main/java/net/java/html/js/JavaScriptBody.java Tue Dec 09 21:32:09 2014 +0100
1.2 +++ b/boot/src/main/java/net/java/html/js/JavaScriptBody.java Fri Dec 12 11:22:40 2014 +0100
1.3 @@ -120,4 +120,33 @@
1.4 * of the JavaScript snippet
1.5 */
1.6 public boolean wait4js() default true;
1.7 +
1.8 + /** Controls garbage collection behavior of method parameters.
1.9 + * In general JavaScript garbage
1.10 + * collection system makes it close to impossible to find out whether
1.11 + * an object is supposed to be still used or not. Some systems have
1.12 + * an external hooks to find that out (like <em>JavaFX</em> <code>WebView</code>),
1.13 + * in some systems this information is not important (like the
1.14 + * <a href="http://bck2brwsr.apidesign.org">Bck2Brwsr</a> VM running
1.15 + * all in JavaScript), but other execution systems just can't find that
1.16 + * out. To prevent memory leaks on such systems and help them manage
1.17 + * memory more effectively, those who define JavaScript interfacing
1.18 + * methods may indicate whether the non-primitive parameters passed
1.19 + * in should be hold only for the time of method invocation or
1.20 + * for the whole application lifetime.
1.21 + * <p>
1.22 + * The default value is <code>true</code> as that is compatible with
1.23 + * previous behavior and also prevents unwanted surprises when something
1.24 + * garbage collects pre-maturaly. Framework developers are however
1.25 + * encouraged to use <code>keepAlive=false</code> as much as possible.
1.26 + *
1.27 + * @return whether Java objects passed as parameters of the method
1.28 + * should be made guaranteed to be available JavaScript
1.29 + * even after the method invocation is over (e.g. prevent them to be
1.30 + * garbage collected in Java until it is known they are not needed
1.31 + * from JavaScript at all).
1.32 + *
1.33 + * @since 1.1
1.34 + */
1.35 + public boolean keepAlive() default true;
1.36 }
2.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java Tue Dec 09 21:32:09 2014 +0100
2.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java Fri Dec 12 11:22:40 2014 +0100
2.3 @@ -296,6 +296,7 @@
2.4 // init Fn
2.5 super.visitInsn(Opcodes.POP);
2.6 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
2.7 + super.visitInsn(fia.keepAlive ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
2.8 super.visitLdcInsn(body);
2.9 super.visitIntInsn(Opcodes.SIPUSH, args.size());
2.10 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
2.11 @@ -311,7 +312,7 @@
2.12 }
2.13 super.visitMethodInsn(Opcodes.INVOKESTATIC,
2.14 "org/netbeans/html/boot/spi/Fn", "define",
2.15 - "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
2.16 + "(Ljava/lang/Class;ZLjava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
2.17 );
2.18 Label noPresenter = new Label();
2.19 super.visitInsn(Opcodes.DUP);
2.20 @@ -526,6 +527,7 @@
2.21 String body;
2.22 boolean javacall = false;
2.23 boolean wait4js = true;
2.24 + boolean keepAlive = false;
2.25
2.26 public FindInAnno() {
2.27 super(Opcodes.ASM4);
2.28 @@ -545,7 +547,11 @@
2.29 wait4js = (Boolean) value;
2.30 return;
2.31 }
2.32 - assert name.equals("body");
2.33 + if (name.equals("keepAlive")) { // NOI18N
2.34 + keepAlive = (Boolean) value;
2.35 + return;
2.36 + }
2.37 + assert name.equals("body"); // NOI18N
2.38 body = (String) value;
2.39 }
2.40
3.1 --- a/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java Tue Dec 09 21:32:09 2014 +0100
3.2 +++ b/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java Fri Dec 12 11:22:40 2014 +0100
3.3 @@ -115,8 +115,39 @@
3.4 * @since 0.7
3.5 */
3.6 public static Fn define(Class<?> caller, String code, String... names) {
3.7 + return define(caller, false, code, names);
3.8 + }
3.9 +
3.10 + /** Helper method to find current presenter and ask it to define new
3.11 + * function.
3.12 + *
3.13 + * @param caller the class who wishes to define the function
3.14 + * @param keepParametersAlive whether Java parameters should survive in JavaScript
3.15 + * after the method invocation is over
3.16 + * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
3.17 + * @param names names of individual parameters
3.18 + * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
3.19 + * - can return <code>null</code> if there is {@link #activePresenter() no presenter}
3.20 + * @since 1.1
3.21 + */
3.22 + public static Fn define(Class<?> caller, boolean keepParametersAlive, String code, String... names) {
3.23 final Presenter p = FnContext.currentPresenter(false);
3.24 - return p == null ? null : p.defineFn(code, names);
3.25 + if (p == null) {
3.26 + return null;
3.27 + }
3.28 + if (p instanceof KeepAlive) {
3.29 + boolean[] arr;
3.30 + if (!keepParametersAlive) {
3.31 + arr = new boolean[names.length];
3.32 + for (int i = 0; i < arr.length; i++) {
3.33 + arr[i] = false;
3.34 + }
3.35 + } else {
3.36 + arr = null;
3.37 + }
3.38 + return ((KeepAlive)p).defineFn(code, names, arr);
3.39 + }
3.40 + return p.defineFn(code, names);
3.41 }
3.42
3.43 private static final Map<String,Set<Presenter>> LOADED = new HashMap<String, Set<Presenter>>();
3.44 @@ -244,7 +275,7 @@
3.45 protected final Presenter presenter() {
3.46 return presenter;
3.47 }
3.48 -
3.49 +
3.50 /** The representation of a <em>presenter</em> - usually a browser window.
3.51 * Should be provided by a library included in the application and registered
3.52 * in <code>META-INF/services</code>, for example with
3.53 @@ -329,4 +360,29 @@
3.54 */
3.55 public Object toJava(Object js);
3.56 }
3.57 +
3.58 + /** Additional interface to {@link Presenter} to control more precisely
3.59 + * garbage collection behavior of individual parameters. See
3.60 + * {@link JavaScriptBody#keepAlive()} attribute for description of the
3.61 + * actual behavior of the interface.
3.62 + *
3.63 + * @since 1.1
3.64 + */
3.65 + public interface KeepAlive {
3.66 + /** Creates new function with given parameter names and provided body.
3.67 + *
3.68 + * @param code the body of the function. Can refer to variables named
3.69 + * as <code>names</code>
3.70 + * @param names names of parameters of the function - these will be
3.71 + * available when the <code>code</code> body executes
3.72 + * @param keepAlive array of booleans describing for each parameter
3.73 + * whether it should be kept alive or not. Length of the array
3.74 + * must be the same as length of <code>names</code> array. The
3.75 + * array may be <code>null</code> to signal that all parameters
3.76 + * should be <em>kept alive</em>.
3.77 + *
3.78 + * @return function that can be later invoked
3.79 + */
3.80 + public Fn defineFn(String code, String[] names, boolean[] keepAlive);
3.81 + }
3.82 }
4.1 --- a/boot/src/test/java/org/netbeans/html/boot/impl/JsMethods.java Tue Dec 09 21:32:09 2014 +0100
4.2 +++ b/boot/src/test/java/org/netbeans/html/boot/impl/JsMethods.java Fri Dec 12 11:22:40 2014 +0100
4.3 @@ -134,4 +134,7 @@
4.4 public static int plusOrMul(int x, int y) {
4.5 return x * y;
4.6 }
4.7 +
4.8 + @JavaScriptBody(args = { "x" }, keepAlive = false, body = "throw 'Do not call me!'")
4.9 + public static native boolean checkAllowGC(Object x);
4.10 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/boot/src/test/java/org/netbeans/html/boot/impl/KeepAliveTest.java Fri Dec 12 11:22:40 2014 +0100
5.3 @@ -0,0 +1,114 @@
5.4 +/**
5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5.6 + *
5.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
5.8 + *
5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
5.10 + * Other names may be trademarks of their respective owners.
5.11 + *
5.12 + * The contents of this file are subject to the terms of either the GNU
5.13 + * General Public License Version 2 only ("GPL") or the Common
5.14 + * Development and Distribution License("CDDL") (collectively, the
5.15 + * "License"). You may not use this file except in compliance with the
5.16 + * License. You can obtain a copy of the License at
5.17 + * http://www.netbeans.org/cddl-gplv2.html
5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
5.19 + * specific language governing permissions and limitations under the
5.20 + * License. When distributing the software, include this License Header
5.21 + * Notice in each file and include the License file at
5.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
5.23 + * particular file as subject to the "Classpath" exception as provided
5.24 + * by Oracle in the GPL Version 2 section of the License file that
5.25 + * accompanied this code. If applicable, add the following below the
5.26 + * License Header, with the fields enclosed by brackets [] replaced by
5.27 + * your own identifying information:
5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
5.29 + *
5.30 + * Contributor(s):
5.31 + *
5.32 + * The Original Software is NetBeans. The Initial Developer of the Original
5.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
5.34 + *
5.35 + * If you wish your version of this file to be governed by only the CDDL
5.36 + * or only the GPL Version 2, indicate your decision by adding
5.37 + * "[Contributor] elects to include this software in this distribution
5.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
5.39 + * single choice of license, a recipient has the option to distribute
5.40 + * your version of this file under either the CDDL, the GPL Version 2 or
5.41 + * to extend the choice of license to its licensees as provided above.
5.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
5.43 + * Version 2 license, then the option applies only if the new code is
5.44 + * made subject to such option by the copyright holder.
5.45 + */
5.46 +package org.netbeans.html.boot.impl;
5.47 +
5.48 +import java.io.Closeable;
5.49 +import java.io.Reader;
5.50 +import java.net.URL;
5.51 +import java.util.Collection;
5.52 +import org.netbeans.html.boot.spi.Fn;
5.53 +import static org.testng.Assert.assertFalse;
5.54 +import static org.testng.Assert.assertTrue;
5.55 +import org.testng.annotations.BeforeMethod;
5.56 +import org.testng.annotations.Test;
5.57 +
5.58 +public class KeepAliveTest implements Fn.Presenter, Fn.KeepAlive, FindResources {
5.59 + private Class<?> jsMethods;
5.60 + @Test public void keepAliveIsSetToFalse() throws Exception {
5.61 + Closeable c = Fn.activate(this);
5.62 + boolean ret = (Boolean)jsMethods.getMethod("checkAllowGC", Object.class).invoke(null, this);
5.63 + c.close();
5.64 + assertFalse(ret, "keepAlive returns false when the presenter is invoked");
5.65 + }
5.66 +
5.67 + @Test public void keepAliveIsPropagated() throws Exception {
5.68 + Closeable c = Fn.activate(this);
5.69 + boolean ret = (Boolean)jsMethods.getMethod("truth").invoke(null);
5.70 + c.close();
5.71 + assertTrue(ret, "keepAlive returns true when the presenter is invoked");
5.72 + }
5.73 +
5.74 + @BeforeMethod
5.75 + public void initClass() throws ClassNotFoundException {
5.76 + ClassLoader loader = FnUtils.newLoader(this, this, KeepAliveTest.class.getClassLoader().getParent());
5.77 + jsMethods = loader.loadClass(JsMethods.class.getName());
5.78 + }
5.79 +
5.80 + @Override
5.81 + public Fn defineFn(String code, String[] names, final boolean[] keepAlive) {
5.82 + return new Fn(this) {
5.83 + @Override
5.84 + public Object invoke(Object thiz, Object... args) throws Exception {
5.85 + boolean res = true;
5.86 + if (keepAlive != null) {
5.87 + for (int i = 0; i < keepAlive.length; i++) {
5.88 + res &= keepAlive[i];
5.89 + }
5.90 + }
5.91 + return res;
5.92 + }
5.93 + };
5.94 + }
5.95 +
5.96 + @Override
5.97 + public Fn defineFn(String code, String... names) {
5.98 + throw new UnsupportedOperationException("Never called!");
5.99 + }
5.100 +
5.101 + @Override
5.102 + public void displayPage(URL page, Runnable onPageLoad) {
5.103 + throw new UnsupportedOperationException();
5.104 + }
5.105 +
5.106 + @Override
5.107 + public void loadScript(Reader code) throws Exception {
5.108 + }
5.109 +
5.110 + @Override
5.111 + public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
5.112 + URL u = ClassLoader.getSystemClassLoader().getResource(path);
5.113 + if (u != null) {
5.114 + results.add(u);
5.115 + }
5.116 + }
5.117 +}
6.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java Tue Dec 09 21:32:09 2014 +0100
6.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java Fri Dec 12 11:22:40 2014 +0100
6.3 @@ -61,7 +61,7 @@
6.4 @JavaScriptBody(args = {"r"}, javacall = true, body = "r.@java.lang.Runnable::run()();")
6.5 static native void callback(Runnable r);
6.6
6.7 - @JavaScriptBody(args = {"r"}, wait4js = false, javacall = true, body = "r.@java.lang.Runnable::run()();")
6.8 + @JavaScriptBody(args = {"r"}, wait4js = false, keepAlive = false, javacall = true, body = "r.@java.lang.Runnable::run()();")
6.9 static native void asyncCallback(Runnable r);
6.10
6.11 @JavaScriptBody(args = {"c"}, javacall = true, body = "return c.@java.util.concurrent.Callable::call()();")
6.12 @@ -88,10 +88,10 @@
6.13 @JavaScriptBody(args = "o", body = "return o.x;")
6.14 public static native Object readX(Object o);
6.15
6.16 - @JavaScriptBody(args = { "o", "x" }, body = "o.x = x;")
6.17 + @JavaScriptBody(args = { "o", "x" }, keepAlive = false, body = "o.x = x;")
6.18 public static native Object setX(Object o, Object x);
6.19
6.20 - @JavaScriptBody(args = { "c" }, javacall = true, body =
6.21 + @JavaScriptBody(args = { "c" }, keepAlive = false, javacall = true, body =
6.22 "return c.@net.java.html.js.tests.Sum::sum(II)(40, 2);"
6.23 )
6.24 public static native int sumIndirect(Sum c);
7.1 --- a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java Tue Dec 09 21:32:09 2014 +0100
7.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java Fri Dec 12 11:22:40 2014 +0100
7.3 @@ -52,8 +52,9 @@
7.4 */
7.5 public class GCBodyTest {
7.6 Reference<?> ref;
7.7 + int[] arr;
7.8
7.9 - @KOTest public void callbackWithParameters() throws InterruptedException {
7.10 + @KOTest public void callbackInterfaceCanDisappear() throws InterruptedException {
7.11 if (ref != null) {
7.12 assertGC(ref, "Can disappear!");
7.13 return;
7.14 @@ -66,24 +67,60 @@
7.15 assertGC(ref, "Can disappear!");
7.16 }
7.17
7.18 + private Object assignInst() {
7.19 + Object obj = Bodies.instance(0);
7.20 + Object s = new EmptyInstance();
7.21 + Bodies.setX(obj, s);
7.22 + assert s == Bodies.readX(obj);
7.23 + ref = new WeakReference<Object>(s);
7.24 + return obj;
7.25 +}
7.26 +
7.27 @KOTest public void holdObjectAndReleaseObject() throws InterruptedException {
7.28 if (ref != null) {
7.29 assertGC(ref, "Can disappear!");
7.30 return;
7.31 }
7.32 - Sum s = new Sum();
7.33 - Object obj = Bodies.instance(0);
7.34 - Bodies.setX(obj, s);
7.35
7.36 - ref = new WeakReference<Object>(s);
7.37 - s = null;
7.38 - assertNotGC(ref, "Cannot disappear!");
7.39 + Object obj = assignInst();
7.40 + assert ref != null;
7.41
7.42 Bodies.setX(obj, null);
7.43 obj = null;
7.44
7.45 assertGC(ref, "Can disappear!");
7.46 }
7.47 +
7.48 + private static Reference<?> sendRunnable(final int[] arr) {
7.49 + Runnable r = new Runnable() {
7.50 + @Override
7.51 + public void run() {
7.52 + arr[0]++;
7.53 + }
7.54 + };
7.55 + Bodies.asyncCallback(r);
7.56 + return new WeakReference<Object>(r);
7.57 + }
7.58 +
7.59 + private static class EmptyInstance {
7.60 + }
7.61 +
7.62 + @KOTest public void parametersNeedToRemainInAsyncMode() throws InterruptedException {
7.63 + if (ref != null) {
7.64 + if (arr[0] != 1) {
7.65 + throw new InterruptedException();
7.66 + }
7.67 + assertGC(ref, "Now the runnable can disappear");
7.68 + return;
7.69 + }
7.70 + arr = new int[] { 0 };
7.71 + ref = sendRunnable(arr);
7.72 + if (arr[0] == 1) {
7.73 + return;
7.74 + }
7.75 + assertNotGC(ref, false, "The runnable should not be GCed");
7.76 + throw new InterruptedException();
7.77 + }
7.78
7.79 private static void assertGC(Reference<?> ref, String msg) throws InterruptedException {
7.80 for (int i = 0; i < 100; i++) {
7.81 @@ -109,12 +146,14 @@
7.82 return ref.get() == null;
7.83 }
7.84
7.85 - private static void assertNotGC(Reference<?> ref, String msg) throws InterruptedException {
7.86 + private static void assertNotGC(Reference<?> ref, boolean clearJS, String msg) throws InterruptedException {
7.87 for (int i = 0; i < 10; i++) {
7.88 if (ref.get() == null) {
7.89 throw new IllegalStateException(msg);
7.90 }
7.91 - int size = Bodies.gc(Math.pow(2.0, i));
7.92 + if (clearJS) {
7.93 + Bodies.gc(Math.pow(2.0, i));
7.94 + }
7.95 try {
7.96 System.gc();
7.97 System.runFinalization();
8.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Tue Dec 09 21:32:09 2014 +0100
8.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Fri Dec 12 11:22:40 2014 +0100
8.3 @@ -147,6 +147,7 @@
8.4
8.5 @JavaScriptBody(
8.6 javacall = true,
8.7 + keepAlive = false,
8.8 wait4js = false,
8.9 args = { "thiz", "ret", "propNames", "propReadOnly", "propValues", "funcNames" },
8.10 body =