# HG changeset patch # User Jaroslav Tulach # Date 1418869554 -3600 # Node ID 15af7ebf1d0e7ea78c04cc085efc387e4760d29c # Parent 684f9cbfb450dca4eeeacc2b6f766d4222bc67ca Bringing the !keepAlive approach to javax.script presenter as well diff -r 684f9cbfb450 -r 15af7ebf1d0e boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java --- a/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java Wed Dec 17 22:00:41 2014 +0100 +++ b/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java Thu Dec 18 03:25:54 2014 +0100 @@ -45,6 +45,7 @@ import java.io.Closeable; import java.io.IOException; import java.io.Reader; +import java.lang.ref.WeakReference; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -70,8 +71,8 @@ * * @author Jaroslav Tulach */ -final class ScriptPresenter -implements Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { +final class ScriptPresenter implements Fn.KeepAlive, +Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { private static final Logger LOG = Logger.getLogger(ScriptPresenter.class.getName()); private final ScriptEngine eng; private final Executor exc; @@ -90,9 +91,14 @@ @Override public Fn defineFn(String code, String... names) { - return defineImpl(code, names); + return defineImpl(code, names, null); } - private FnImpl defineImpl(String code, String... names) { + + @Override + public Fn defineFn(String code, String[] names, boolean[] keepAlive) { + return defineImpl(code, names, keepAlive); + } + private FnImpl defineImpl(String code, String[] names, boolean[] keepAlive) { StringBuilder sb = new StringBuilder(); sb.append("(function() {"); sb.append(" return function("); @@ -112,7 +118,7 @@ } catch (ScriptException ex) { throw new IllegalStateException(ex); } - return new FnImpl(this, fn); + return new FnImpl(this, fn, keepAlive); } @Override @@ -150,7 +156,7 @@ private FnImpl wrapArrFn() { if (wrapArrImpl == null) { try { - wrapArrImpl = defineImpl("return Array.prototype.slice.call(arguments);"); + wrapArrImpl = defineImpl("return Array.prototype.slice.call(arguments);", null, null); } catch (Exception ex) { throw new IllegalStateException(ex); } @@ -181,7 +187,7 @@ + " var l = arr.length;\n" + " for (var i = 0; i < l; i++) to[i] = arr[i];\n" + " return l;\n" - + "}", "arr", "to" + + "}", new String[] { "arr", "to" }, null ); } catch (Exception ex) { throw new IllegalStateException(ex); @@ -192,6 +198,9 @@ @Override public Object toJava(Object jsArray) { + if (jsArray instanceof Weak) { + jsArray = ((Weak)jsArray).get(); + } try { return checkArray(jsArray); } catch (Exception ex) { @@ -239,10 +248,12 @@ private class FnImpl extends Fn { private final Object fn; + private final boolean[] keepAlive; - public FnImpl(Presenter presenter, Object fn) { + public FnImpl(Presenter presenter, Object fn, boolean[] keepAlive) { super(presenter); this.fn = fn; + this.keepAlive = keepAlive; } @Override @@ -254,19 +265,28 @@ List all = new ArrayList<>(args.length + 1); all.add(thiz == null ? fn : thiz); for (int i = 0; i < args.length; i++) { + Object conv = args[i]; if (arrayChecks) { if (args[i] instanceof Object[]) { Object[] arr = (Object[]) args[i]; - Object conv = ((ScriptPresenter)presenter()).convertArrays(arr); - args[i] = conv; + conv = ((ScriptPresenter) presenter()).convertArrays(arr); } - if (args[i] instanceof Character) { - args[i] = (int)((Character)args[i]); + if (conv != null && keepAlive != null + && !keepAlive[i] && !isJSReady(conv) + && !conv.getClass().getSimpleName().equals("$JsCallbacks$") // NOI18N + ) { + conv = new Weak(conv); + } + if (conv instanceof Character) { + conv = (int)(Character)conv; } } - all.add(args[i]); + all.add(conv); } Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N + if (ret instanceof Weak) { + ret = ((Weak)ret).get(); + } if (ret == fn) { return null; } @@ -277,4 +297,31 @@ } } + private static boolean isJSReady(Object obj) { + if (obj == null) { + return true; + } + if (obj instanceof String) { + return true; + } + if (obj instanceof Number) { + return true; + } + final String cn = obj.getClass().getName(); + if (cn.startsWith("jdk.nashorn") || ( // NOI18N + cn.contains(".mozilla.") && cn.contains(".Native") // NOI18N + )) { + return true; + } + if (obj instanceof Character) { + return true; + } + return false; + } + + private static final class Weak extends WeakReference { + public Weak(Object referent) { + super(referent); + } + } } diff -r 684f9cbfb450 -r 15af7ebf1d0e json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java --- a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java Wed Dec 17 22:00:41 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java Thu Dec 18 03:25:54 2014 +0100 @@ -89,6 +89,20 @@ assert obj != null : "Object is still present"; } + @KOTest public void strongReceiverBehavior() { + Object v = new EmptyInstance(); + Receiver r = new Receiver(v); + r.apply(); + assert v == r.value : "Value is as expected"; + } + + @KOTest public void gcReceiverBehavior() throws InterruptedException { + Receiver r = new Receiver(new EmptyInstance()); + assertGC(r.ref, "The empty instance can be GCed even when referenced from JS"); + r.apply(); + assert r.value == null : "Setter called with null value"; + } + private static Reference sendRunnable(final int[] arr) { Runnable r = new Runnable() { @Override diff -r 684f9cbfb450 -r 15af7ebf1d0e json-tck/src/main/java/net/java/html/js/tests/Receiver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-tck/src/main/java/net/java/html/js/tests/Receiver.java Thu Dec 18 03:25:54 2014 +0100 @@ -0,0 +1,80 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package net.java.html.js.tests; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import net.java.html.js.JavaScriptBody; + +/** + */ +public final class Receiver { + private final Object fn; + Object value; + final Reference ref; + + public Receiver(Object v) { + this.fn = initFn(v); + this.ref = new WeakReference(v); + this.value = this; + } + + public void apply() { + fnApply(fn, this); + } + + void set(Object v) { + value = v; + } + + @JavaScriptBody(args = { "v" }, keepAlive = false, javacall = true, + body = "return function(rec) {\n" + + " rec.@net.java.html.js.tests.Receiver::set(Ljava/lang/Object;)(v);\n" + + "};\n") + private static native Object initFn(Object v); + + @JavaScriptBody(args = { "fn", "thiz" }, body = + "fn(thiz);" + ) + private static native void fnApply(Object fn, Receiver thiz); +}