2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
32 * If you wish your version of this file to be governed by only the CDDL
33 * or only the GPL Version 2, indicate your decision by adding
34 * "[Contributor] elects to include this software in this distribution
35 * under the [CDDL or GPL Version 2] license." If you do not indicate a
36 * single choice of license, a recipient has the option to distribute
37 * your version of this file under either the CDDL, the GPL Version 2 or
38 * to extend the choice of license to its licensees as provided above.
39 * However, if you add GPL Version 2 code and therefore, elected the GPL
40 * Version 2 license, then the option applies only if the new code is
41 * made subject to such option by the copyright holder.
43 package net.java.html.boot.script;
45 import java.io.Closeable;
46 import java.io.IOException;
47 import java.io.Reader;
48 import java.lang.ref.WeakReference;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.concurrent.Executor;
53 import java.util.logging.Level;
54 import java.util.logging.Logger;
55 import javax.script.Invocable;
56 import javax.script.ScriptEngine;
57 import javax.script.ScriptEngineManager;
58 import javax.script.ScriptException;
59 import org.netbeans.html.boot.spi.Fn;
60 import org.netbeans.html.boot.spi.Fn.Presenter;
62 /** Implementation of {@link Presenter} that delegates
63 * to Java {@link ScriptEngine scripting} API. The presenter runs headless
64 * without appropriate simulation of browser APIs. Its primary usefulness
65 * is inside testing environments.
67 * One can load in browser simulation for example from
68 * <a href="http://www.envjs.com/">env.js</a>. The best way to achieve so,
69 * is to wait until JDK-8046013 gets fixed....
72 * @author Jaroslav Tulach
74 final class ScriptPresenter implements Fn.KeepAlive,
75 Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor {
76 private static final Logger LOG = Logger.getLogger(ScriptPresenter.class.getName());
77 private final ScriptEngine eng;
78 private final Executor exc;
80 public ScriptPresenter(Executor exc) {
83 eng = new ScriptEngineManager().getEngineByName("javascript");
84 eng.eval("function alert(msg) { Packages.java.lang.System.out.println(msg); };");
85 eng.eval("function confirm(msg) { Packages.java.lang.System.out.println(msg); return true; };");
86 eng.eval("function prompt(msg, txt) { Packages.java.lang.System.out.println(msg + ':' + txt); return txt; };");
87 } catch (ScriptException ex) {
88 throw new IllegalStateException(ex);
93 public Fn defineFn(String code, String... names) {
94 return defineImpl(code, names, null);
98 public Fn defineFn(String code, String[] names, boolean[] keepAlive) {
99 return defineImpl(code, names, keepAlive);
101 private FnImpl defineImpl(String code, String[] names, boolean[] keepAlive) {
102 StringBuilder sb = new StringBuilder();
103 sb.append("(function() {\n");
104 sb.append(" return function(");
106 if (names != null) for (String n : names) {
107 sb.append(sep).append(n);
112 sb.append("\n };\n");
117 fn = eng.eval(sb.toString());
118 } catch (ScriptException ex) {
119 throw new IllegalStateException(ex);
121 return new FnImpl(this, fn, keepAlive);
125 public void displayPage(URL page, Runnable onPageLoad) {
127 eng.eval("if (typeof window !== 'undefined') window.location = '" + page + "'");
128 } catch (ScriptException ex) {
129 LOG.log(Level.SEVERE, "Cannot load " + page, ex);
131 if (onPageLoad != null) {
137 public void loadScript(Reader code) throws Exception {
145 final Object convertArrays(Object[] arr) throws Exception {
146 for (int i = 0; i < arr.length; i++) {
147 if (arr[i] instanceof Object[]) {
148 arr[i] = convertArrays((Object[]) arr[i]);
151 final Object wrapArr = wrapArrFn().invokeImpl(null, false, arr); // NOI18N
155 private FnImpl wrapArrImpl;
156 private FnImpl wrapArrFn() {
157 if (wrapArrImpl == null) {
159 wrapArrImpl = defineImpl("return Array.prototype.slice.call(arguments);", null, null);
160 } catch (Exception ex) {
161 throw new IllegalStateException(ex);
167 final Object checkArray(Object val) throws Exception {
168 final FnImpl fn = arraySizeFn();
169 final Object fnRes = fn.invokeImpl(null, false, val, null);
170 int length = ((Number) fnRes).intValue();
174 Object[] arr = new Object[length];
175 fn.invokeImpl(null, false, val, arr);
178 private FnImpl arraySize;
179 private FnImpl arraySizeFn() {
180 if (arraySize == null) {
182 arraySize = defineImpl("\n"
183 + "if (to === null) {\n"
184 + " if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;\n"
185 + " else return -1;\n"
187 + " var l = arr.length;\n"
188 + " for (var i = 0; i < l; i++) to[i] = arr[i];\n"
190 + "}", new String[] { "arr", "to" }, null
192 } catch (Exception ex) {
193 throw new IllegalStateException(ex);
200 public Object toJava(Object jsArray) {
201 if (jsArray instanceof Weak) {
202 jsArray = ((Weak)jsArray).get();
205 return checkArray(jsArray);
206 } catch (Exception ex) {
207 throw new IllegalStateException(ex);
212 public Object toJavaScript(Object toReturn) {
213 if (toReturn instanceof Object[]) {
215 return convertArrays((Object[])toReturn);
216 } catch (Exception ex) {
217 throw new IllegalStateException(ex);
225 public void execute(final Runnable command) {
226 if (Fn.activePresenter() == this) {
231 class Wrap implements Runnable {
233 try (Closeable c = Fn.activate(ScriptPresenter.this)) {
235 } catch (IOException ex) {
236 throw new IllegalStateException(ex);
240 final Runnable wrap = new Wrap();
248 private class FnImpl extends Fn {
250 private final Object fn;
251 private final boolean[] keepAlive;
253 public FnImpl(Presenter presenter, Object fn, boolean[] keepAlive) {
256 this.keepAlive = keepAlive;
260 public Object invoke(Object thiz, Object... args) throws Exception {
261 return invokeImpl(thiz, true, args);
264 final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
265 List<Object> all = new ArrayList<>(args.length + 1);
266 all.add(thiz == null ? fn : thiz);
267 for (int i = 0; i < args.length; i++) {
268 Object conv = args[i];
270 if (args[i] instanceof Object[]) {
271 Object[] arr = (Object[]) args[i];
272 conv = ((ScriptPresenter) presenter()).convertArrays(arr);
274 if (conv != null && keepAlive != null
275 && !keepAlive[i] && !isJSReady(conv)
276 && !conv.getClass().getSimpleName().equals("$JsCallbacks$") // NOI18N
278 conv = new Weak(conv);
280 if (conv instanceof Character) {
281 conv = (int)(Character)conv;
286 Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N
287 if (ret instanceof Weak) {
288 ret = ((Weak)ret).get();
296 return ((ScriptPresenter)presenter()).checkArray(ret);
300 private static boolean isJSReady(Object obj) {
304 if (obj instanceof String) {
307 if (obj instanceof Number) {
310 final String cn = obj.getClass().getName();
311 if (cn.startsWith("jdk.nashorn") || ( // NOI18N
312 cn.contains(".mozilla.") && cn.contains(".Native") // NOI18N
316 if (obj instanceof Character) {
322 private static final class Weak extends WeakReference<Object> {
323 public Weak(Object referent) {