diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c23ef8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*/target/ +/target/ \ No newline at end of file diff --git a/.hgignore b/.hgignore deleted file mode 100644 index af27f20..0000000 --- a/.hgignore +++ /dev/null @@ -1,3 +0,0 @@ -.*~ -.*\.orig$ -.*target/.* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ba7c1a9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: java +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start || echo No X11 + - sleep 3 +script: + - jdk_switcher use oraclejdk8 || java -version + - java -version + - mvn install -DskipTests + - mvn verify +os: + - linux + diff --git a/boot-agent-test/pom.xml b/boot-agent-test/pom.xml index 8159679..a5fe6f4 100644 --- a/boot-agent-test/pom.xml +++ b/boot-agent-test/pom.xml @@ -48,7 +48,7 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 boot-agent-test jar diff --git a/boot-fx/pom.xml b/boot-fx/pom.xml index 26dd2ce..680447e 100644 --- a/boot-fx/pom.xml +++ b/boot-fx/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html.boot.fx - 2.0-SNAPSHOT + 1.4 FX WebView Bootstrap bundle http://maven.apache.org 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 9164140..5454047 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 @@ -47,10 +47,16 @@ import java.io.Closeable; import java.io.IOException; import java.io.Reader; import java.lang.ref.WeakReference; +import java.lang.reflect.Array; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.TreeSet; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; @@ -59,6 +65,7 @@ import javafx.scene.Parent; import javafx.scene.layout.BorderPane; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; +import netscape.javascript.JSException; import netscape.javascript.JSObject; import org.netbeans.html.boot.spi.Fn; @@ -76,7 +83,9 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { // transient - e.g. not cloneable private JSObject arraySize; private JSObject wrapArrImpl; + private JSObject newPOJOImpl; private Object undefined; + private JavaValues values; @Override protected AbstractFXPresenter clone() { @@ -85,6 +94,8 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { p.arraySize = null; p.wrapArrImpl = null; p.undefined = null; + p.newPOJOImpl = null; + p.values = null; return p; } catch (CloneNotSupportedException ex) { throw new IllegalStateException(ex); @@ -209,6 +220,13 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { return wrapArr; } + private final JavaValues values() { + if (values == null) { + values = new JavaValues(); + } + return values; + } + private final JSObject wrapArrFn() { if (wrapArrImpl == null) { try { @@ -225,6 +243,25 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { return wrapArrImpl; } + JSObject createPOJOWrapper(int hash, int id) { + if (newPOJOImpl == null) { + try { + newPOJOImpl = (JSObject) defineJSFn( + "var k = {};\n" + + "k.fxBrwsrId = function(hash, id) {\n" + + " return {\n" + + " 'fxBrwsrId' : function(callback) { callback.hashAndId(hash, id); }\n" + + " }\n" + + "};\n" + + "return k;\n", new String[] { "callback" }, null + ).invokeImpl(null, false); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + return (JSObject) newPOJOImpl.call("fxBrwsrId", hash, id); + } + final Object undefined() { if (undefined == null) { undefined = engine.executeScript("undefined"); @@ -232,29 +269,21 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { return undefined; } - final Object checkArray(Object val) { - if (!(val instanceof JSObject)) { - return val; - } + private int getArrayLength(Object val) throws JSException { int length = ((Number) arraySizeFn().call("array", val, null)).intValue(); - if (length == -1) { - return val; - } + return length; + } + + private Object[] toArray(int length, Object val) throws JSException { Object[] arr = new Object[length]; arraySizeFn().call("array", val, arr); - clearUndefinedArray(arr); + checkArray(arr); return arr; } - private void clearUndefinedArray(Object[] arr) { + private void checkArray(Object[] arr) { for (int i = 0; i < arr.length; i++) { - if (arr[i] == undefined) { - arr[i] = null; - continue; - } - if (arr[i] instanceof Object[]) { - clearUndefinedArray((Object[])arr[i]); - } + arr[i] = toJava(arr[i]); } } @@ -283,27 +312,64 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { @Override public Object toJava(Object toJS) { - if (toJS instanceof Weak) { - toJS = ((Weak)toJS).get(); - } if (toJS == undefined()) { return null; } - return checkArray(toJS); + if (!(toJS instanceof JSObject)) { + return toJS; + } + JSObject js = (JSObject) toJS; + int length = getArrayLength(toJS); + if (length != -1) { + Object[] arr = toArray(length, toJS); + return arr; + } + return values().realValue(js); } @Override - public Object toJavaScript(Object toReturn) { - if (toReturn instanceof Object[]) { - return convertArrays((Object[])toReturn); - } else { - if (toReturn instanceof Character) { - return (int)(Character)toReturn; + public Object toJavaScript(Object value) { + return toJavaScript(value, true); + } + + final Object toJavaScript(Object value, boolean keep) { + if (value == null) { + return null; + } + if (value instanceof String) { + return value; + } + if (value instanceof Number) { + return value; + } + if (value instanceof JSObject) { + return value; + } + if (value instanceof Boolean) { + return value; + } + if (value instanceof Character) { + return (int) (char) (Character) value; + } + if (value instanceof Enum) { + return value; + } + int len = isArray(value); + if (len >= 0) { + Object[] copy = new Object[len]; + for (int i = 0; i < len; i++) { + copy[i] = toJavaScript(Array.get(value, i)); } - return toReturn; + final JSObject wrapArr = (JSObject)wrapArrFn().call("array", copy); // NOI18N + return wrapArr; + } + if (value.getClass().getName().endsWith("$JsCallbacks$")) { + return value; } + return values().wrap(value, keep); } + @Override public void execute(final Runnable r) { if (Platform.isFxApplicationThread()) { Closeable c = Fn.activate(this); @@ -364,37 +430,23 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { LOG.log(Level.FINER, " params: {0}", Arrays.asList(args)); } List all = new ArrayList(args.length + 1); - all.add(thiz == null ? fn : thiz); + all.add(thiz == null ? presenter.undefined() : 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]; - conv = presenter.convertArrays(arr); - } - 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; - } + boolean alive = keepAlive == null || keepAlive[i]; + conv = presenter.toJavaScript(conv, alive); } all.add(conv); } Object ret = fn.call("call", all.toArray()); // NOI18N - if (ret instanceof Weak) { - ret = ((Weak)ret).get(); - } - if (ret == fn || ret == presenter.undefined()) { + if (ret == presenter.undefined()) { return null; } if (!arrayChecks) { return ret; } - return presenter.checkArray(ret); + return presenter.toJava(ret); } catch (Error t) { t.printStackTrace(); throw t; @@ -405,29 +457,174 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { } } - private static boolean isJSReady(Object obj) { - if (obj == null) { - return true; + protected int isArray(Object value) { + try { + return Array.getLength(value); + } catch (IllegalArgumentException ex) { + return -1; } - if (obj instanceof String) { - return true; + } + + private interface Ref extends Comparable { + Object value(); + int id(); + JSObject jsObj(); + } + + private final class WeakRef extends WeakReference implements Ref { + private final int id; + private final JSObject js; + + WeakRef(Object value, int id, JSObject js) { + super(value); + this.id = id; + this.js = js; } - if (obj instanceof Number) { - return true; + + @Override + public Object value() { + return get(); } - if (obj instanceof JSObject) { - return true; + + @Override + public int id() { + return id; } - if (obj instanceof Character) { - return true; + + @Override + public JSObject jsObj() { + return js; + } + + @Override + public int compareTo(Ref o) { + return this.id() - o.id(); } - return false; } - private static final class Weak extends WeakReference { - public Weak(Object referent) { - super(referent); - assert !(referent instanceof Weak); + private final class StrongRef implements Ref { + private final Object value; + private final int id; + private final JSObject js; + + StrongRef(Object value, int id, JSObject js) { + this.value = value; + this.id = id; + this.js = js; + } + + @Override + public Object value() { + return value; + } + + @Override + public int id() { + return id; + } + + @Override + public JSObject jsObj() { + return js; + } + + @Override + public int compareTo(Ref o) { + return this.id() - o.id(); + } + } + + public final class JavaValues { + private final Map> values; + private int hash; + private int id; + + JavaValues() { + this.values = new HashMap>(); + } + + synchronized final JSObject wrap(Object pojo, boolean keep) { + int hash = System.identityHashCode(pojo); + NavigableSet refs = values.get(hash); + if (refs != null) { + for (Ref ref : refs) { + if (ref.value() == pojo) { + return ref.jsObj(); + } + } + } else { + refs = new TreeSet(); + values.put(hash, refs); + } + int id = findId(refs); + JSObject js = createPOJOWrapper(hash, id); + Ref newRef = keep ? new StrongRef(pojo, id, js) : new WeakRef(pojo, id, js); + refs.add(newRef); + return newRef.jsObj(); } - } // end of Weak + + private int findId(NavigableSet refs) { + if (refs.isEmpty()) { + return 0; + } + final Ref first = refs.first(); + int previous = first.id(); + if (previous > 0) { + return 0; + } + for (Ref ref : refs.tailSet(first, false)) { + int next = ref.id(); + if (previous + 1 < next) { + return previous + 1; + } + previous = next; + } + return previous + 1; + } + + public void hashAndId(int hash, int id) { + assert this.hash == -1; + assert this.id == -1; + this.hash = hash; + this.id = id; + } + + Object realValue(JSObject obj) { + Object java = obj.getMember("fxBrwsrId"); + if (java instanceof JSObject) { + for (;;) { + int resultHash; + int resultId; + synchronized (this) { + this.hash = -1; + this.id = -1; + obj.call("fxBrwsrId", this); + assert this.hash != -1; + assert this.id != -1; + resultHash = this.hash; + resultId = this.id; + } + + final NavigableSet refs = values.get(resultHash); + Iterator it = refs.iterator(); + while (it.hasNext()) { + Ref next = it.next(); + Object pojo = next.value(); + if (next.id() == resultId) { + return pojo; + } + if (pojo == null) { + it.remove(); + } + } + if (refs.isEmpty()) { + values.remove(resultHash); + } + } + } + return obj; + } + } + + } 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 71c8547..e1a8df5 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 @@ -66,12 +66,15 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Text; +import javafx.scene.web.PopupFeatures; import javafx.scene.web.PromptData; +import javafx.scene.web.WebEngine; import javafx.scene.web.WebEvent; import javafx.scene.web.WebView; import javafx.stage.Modality; import javafx.stage.Screen; import javafx.stage.Stage; +import javafx.stage.StageStyle; import javafx.stage.Window; import javafx.stage.WindowEvent; import javafx.util.Callback; @@ -278,25 +281,7 @@ public class FXBrwsr extends Application { } }); - class Title implements ChangeListener { - - private String title; - - public Title() { - super(); - } - - @Override - public void changed(ObservableValue ov, String t, String t1) { - title = view.getEngine().getTitle(); - if (title != null) { - stage.setTitle(title); - } - } - } - final Title x = new Title(); - view.getEngine().titleProperty().addListener(x); - x.changed(null, null, null); + Title.observeView(view, stage); return view; } @@ -393,6 +378,18 @@ public class FXBrwsr extends Application { return res[0] ? line.getText() : null; } }); + view.getEngine().setCreatePopupHandler(new Callback() { + @Override + public WebEngine call(PopupFeatures param) { + final Stage stage = new Stage(StageStyle.UTILITY); + stage.initOwner(owner); + final WebView popUpView = new WebView(); + stage.setScene(new Scene(popUpView)); + Title.observeView(popUpView, stage); + stage.show(); + return popUpView.getEngine(); + } + }); } static void waitFinished() { @@ -423,5 +420,30 @@ public class FXBrwsr extends Application { } } } + private static class Title implements ChangeListener { + private String title; + private final WebView view; + private final Stage stage; + + private Title(WebView view, Stage stage) { + super(); + this.view = view; + this.stage = stage; + } + + public static void observeView(WebView view, Stage stage) { + Title t = new Title(view, stage); + view.getEngine().titleProperty().addListener(t); + t.changed(null, null, null); + } + + @Override + public void changed(ObservableValue ov, String t, String t1) { + title = view.getEngine().getTitle(); + if (title != null) { + stage.setTitle(title); + } + } + } } diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java index 25cc5f2..597b882 100644 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/WatchDir.java @@ -68,7 +68,11 @@ final class WatchDir implements Runnable { private final WebEngine engine; WatchDir(WebEngine eng) throws URISyntaxException, IOException { - dir = Paths.get(new URI(eng.getLocation())).getParent(); + URI loc = new URI(eng.getLocation()); + if (loc.getFragment() != null) { + loc = new URI(loc.getScheme(), loc.getHost(), loc.getPath(), null); + } + dir = Paths.get(loc).getParent(); engine = eng; ws = dir.getFileSystem().newWatchService(); key = dir.register(ws, diff --git a/boot-fx/src/test/java/org/netbeans/html/boot/fx/Periodicaly.java b/boot-fx/src/test/java/org/netbeans/html/boot/fx/Periodicaly.java new file mode 100644 index 0000000..8c663e5 --- /dev/null +++ b/boot-fx/src/test/java/org/netbeans/html/boot/fx/Periodicaly.java @@ -0,0 +1,103 @@ +/** + * 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-2016 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 org.netbeans.html.boot.fx; + +import java.util.Timer; +import java.util.TimerTask; +import net.java.html.BrwsrCtx; +import net.java.html.js.JavaScriptBody; + +// BEGIN: org.netbeans.html.boot.fx.Periodicaly +public final class Periodicaly extends TimerTask { + private final BrwsrCtx ctx; + private int counter; + + private Periodicaly(BrwsrCtx ctx) { + // remember the browser context and use it later + this.ctx = ctx; + this.counter = 0; + } + + @Override + public void run() { + // arrives on wrong thread, needs to be re-scheduled + ctx.execute(new Runnable() { + @Override + public void run() { + codeThatNeedsToBeRunInABrowserEnvironment(); + } + }); + } + + // called when your page is ready + public static void onPageLoad(String... args) throws Exception { + // the context at the time of page initialization + BrwsrCtx initialCtx = BrwsrCtx.findDefault(Periodicaly.class); + // the task that is associated with context + Periodicaly task = new Periodicaly(initialCtx); + // creates a new timer + Timer t = new Timer("Move the box"); + // run the task every 100ms + t.scheduleAtFixedRate(task, 0, 100); + } + + @JavaScriptBody(args = { "a", "b" }, body = "return a + b") + private static native int plus(int a, int b); + + void codeThatNeedsToBeRunInABrowserEnvironment() { + // invokes JavaScript function in the browser environment + counter = plus(counter, 1); +// FINISH: org.netbeans.html.boot.fx.Periodicaly + + synchronized (Periodicaly.class) { + globalCounter = counter; + Periodicaly.class.notifyAll(); + } + } + static int globalCounter; + static synchronized void assertTen() throws InterruptedException { + while (globalCounter < 10) { + Periodicaly.class.wait(); + } + } +} diff --git a/boot-fx/src/test/java/org/netbeans/html/boot/fx/PeriodicalyTest.java b/boot-fx/src/test/java/org/netbeans/html/boot/fx/PeriodicalyTest.java new file mode 100644 index 0000000..f3a3d21 --- /dev/null +++ b/boot-fx/src/test/java/org/netbeans/html/boot/fx/PeriodicalyTest.java @@ -0,0 +1,64 @@ +/** + * 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-2016 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 org.netbeans.html.boot.fx; + +import java.util.concurrent.Executors; +import net.java.html.boot.BrowserBuilder; +import org.testng.annotations.Test; + +public final class PeriodicalyTest { + @Test + public void runPeriodically() throws Exception { + final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXPresenter()) + .loadPage("empty.html") + .loadClass(Periodicaly.class) + .invoke("onPageLoad"); + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + bb.showAndWait(); + } + }); + Periodicaly.assertTen(); + } +} diff --git a/boot-fx/src/test/java/org/netbeans/html/boot/fx/PopupTest.java b/boot-fx/src/test/java/org/netbeans/html/boot/fx/PopupTest.java new file mode 100644 index 0000000..a2963bb --- /dev/null +++ b/boot-fx/src/test/java/org/netbeans/html/boot/fx/PopupTest.java @@ -0,0 +1,127 @@ +/** + * 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-2016 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 org.netbeans.html.boot.fx; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import javafx.stage.Stage; +import net.java.html.BrwsrCtx; +import net.java.html.boot.BrowserBuilder; +import net.java.html.js.JavaScriptBody; +import org.netbeans.html.boot.spi.Fn; +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class PopupTest { + public PopupTest() { + } + + @JavaScriptBody(args = { "page" }, body = + "return window.open(page, 'secondary', 'width=300,height=150');" + ) + private static native Object openSecondaryWindow(String page); + + @Test public void checkReload() throws Throwable { + final Throwable[] arr = { null }; + + class WhenInitialized implements Runnable { + CountDownLatch cdl = new CountDownLatch(1); + AbstractFXPresenter p; + BrwsrCtx ctx; + + @Override + public void run() { + try { + p = (AbstractFXPresenter) Fn.activePresenter(); + assertNotNull(p, "Presenter is defined"); + ctx = BrwsrCtx.findDefault(WhenInitialized.class); + } catch (Throwable ex) { + arr[0] = ex; + } finally { + cdl.countDown(); + } + } + } + WhenInitialized when = new WhenInitialized(); + + final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(PopupTest.class). + loadPage("empty.html"). + loadFinished(when); + + class ShowBrowser implements Runnable { + @Override + public void run() { + bb.showAndWait(); + } + } + + Executors.newSingleThreadExecutor().submit(new ShowBrowser()); + when.cdl.await(); + if (arr[0] != null) throw arr[0]; + + Stage s = FXBrwsr.findStage(); + assertEquals(s.getTitle(), "FX Presenter Harness"); + + final Object[] window = new Object[1]; + final CountDownLatch openWindow = new CountDownLatch(1); + when.ctx.execute(new Runnable() { + @Override + public void run() { + TitleTest.changeTitle("First window"); + window[0] = openSecondaryWindow("second.html"); + openWindow.countDown(); + } + }); + + openWindow.await(5, TimeUnit.SECONDS); + + assertNotNull(window[0], "Second window opened"); + + assertEquals(s.getTitle(), "First window", "The title is kept"); + } +} diff --git a/boot-fx/src/test/resources/org/netbeans/html/boot/fx/second.html b/boot-fx/src/test/resources/org/netbeans/html/boot/fx/second.html new file mode 100644 index 0000000..a450f29 --- /dev/null +++ b/boot-fx/src/test/resources/org/netbeans/html/boot/fx/second.html @@ -0,0 +1,55 @@ + + + + + Second Window + + + + +
Second Window
+ + diff --git a/boot-script/pom.xml b/boot-script/pom.xml index 55073fd..c243ec0 100644 --- a/boot-script/pom.xml +++ b/boot-script/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 Presenter via javax.script net.java.html.boot.script - 2.0-SNAPSHOT + 1.4 bundle NONE diff --git a/boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java b/boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java index 8ed88c5..d6487ec 100644 --- a/boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java +++ b/boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java @@ -48,10 +48,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.net.ConnectException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -108,10 +110,15 @@ public final class KnockoutEnvJSTest extends KnockoutTCK { baseUri = DynamicHTTP.initServer(); final Fn.Presenter p = Scripts.createPresenter(KOCase.JS); - URL envNashorn = new URL("https://bugs.openjdk.java.net/secure/attachment/11894/env.nashorn.1.2-debug.js"); - InputStream is = envNashorn.openStream(); - p.loadScript(new InputStreamReader(is)); - is.close(); + try { + URL envNashorn = new URL("https://bugs.openjdk.java.net/secure/attachment/11894/env.nashorn.1.2-debug.js"); + InputStream is = envNashorn.openStream(); + p.loadScript(new InputStreamReader(is)); + is.close(); + } catch (UnknownHostException | ConnectException ex) { + ex.printStackTrace(); + return new Object[0]; + } final BrowserBuilder bb = BrowserBuilder.newBrowser(p). loadClass(KnockoutEnvJSTest.class). @@ -149,6 +156,7 @@ public final class KnockoutEnvJSTest extends KnockoutTCK { final String ver = System.getProperty("java.runtime.version"); // NOI18N if ( ver.startsWith("1.8.0_25") || + ver.startsWith("1.8.0_31") || ver.startsWith("1.8.0_40") ) { return "Broken due to JDK-8047764"; diff --git a/boot-truffle/src/main/java/net/java/html/boot/truffle/JavaObject.java b/boot-truffle/src/main/java/net/java/html/boot/truffle/JavaObject.java index 234f396..1618089 100644 --- a/boot-truffle/src/main/java/net/java/html/boot/truffle/JavaObject.java +++ b/boot-truffle/src/main/java/net/java/html/boot/truffle/JavaObject.java @@ -46,6 +46,7 @@ import com.oracle.truffle.api.interop.ForeignAccess; import com.oracle.truffle.api.interop.MessageResolution; import com.oracle.truffle.api.interop.Resolve; import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnknownIdentifierException; import com.oracle.truffle.api.nodes.Node; @MessageResolution(receiverType = JavaObject.class, language = TrufflePresenter.JavaLang.class) @@ -79,4 +80,15 @@ final class JavaObject extends JavaValue implements TruffleObject { } } + @Resolve(message = "INVOKE") + static abstract class Methods extends Node { + + protected Object access(JavaObject javaObject, String methodName, Object[] args) { + if (methodName.equals("toString")) { + return javaObject.obj.toString(); + } + throw UnknownIdentifierException.raise(methodName); + } + } + } diff --git a/boot-truffle/src/test/java/net/java/html/boot/truffle/TruffleJavaScriptTest.java b/boot-truffle/src/test/java/net/java/html/boot/truffle/TruffleJavaScriptTest.java index ac4c4f5..0ee3cbe 100644 --- a/boot-truffle/src/test/java/net/java/html/boot/truffle/TruffleJavaScriptTest.java +++ b/boot-truffle/src/test/java/net/java/html/boot/truffle/TruffleJavaScriptTest.java @@ -74,7 +74,7 @@ public class TruffleJavaScriptTest { PolyglotEngine engine = PolyglotEngine.newBuilder().build(); PolyglotEngine.Value result = null; try { - result = engine.eval(Source.fromText("6 * 7", "test.js").withMimeType("text/javascript")); + result = engine.eval(Source.newBuilder("6 * 7").name("test.js").mimeType("text/javascript").build()); } catch (Exception notSupported) { if (notSupported.getMessage().contains("text/javascript")) { return new Object[] { new Skip(true, notSupported.getMessage()) }; diff --git a/boot/pom.xml b/boot/pom.xml index e1ec2ff..d902470 100644 --- a/boot/pom.xml +++ b/boot/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html.boot - 2.0-SNAPSHOT + 1.4 bundle Browser Bootstrap http://maven.apache.org diff --git a/context/pom.xml b/context/pom.xml index c127c48..511c75a 100644 --- a/context/pom.xml +++ b/context/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html - 2.0-SNAPSHOT + 1.4 bundle HTML Context http://maven.apache.org diff --git a/context/src/main/java/net/java/html/BrwsrCtx.java b/context/src/main/java/net/java/html/BrwsrCtx.java index 59a4a21..72ac800 100644 --- a/context/src/main/java/net/java/html/BrwsrCtx.java +++ b/context/src/main/java/net/java/html/BrwsrCtx.java @@ -132,37 +132,7 @@ public final class BrwsrCtx implements Executor { *

* Example Using a Timer *

-
-public final class Periodicaly extends {@link java.util.TimerTask} {
-    private final {@link BrwsrCtx} ctx;
-
-    private Periodicaly(BrwsrCtx ctx) {
-        // remember the browser context and use it later
-        this.ctx = ctx;
-    }
-    
-    public void run() {
-        // arrives on wrong thread, needs to be re-scheduled
-        ctx.{@link #execute(java.lang.Runnable) execute}(new Runnable() {
-            public void run() {
-                // code that needs to run in a browser environment
-            }
-        });
-    }
-
-    // called when your page is ready
-    public static void onPageLoad(String... args) throws Exception {
-        // the context at the time of page initialization
-        BrwsrCtx initialCtx = BrwsrCtx.findDefault(Periodicaly.class);
-        // the task that is associated with context 
-        Periodicaly task = new Periodicaly(initialCtx);
-        // creates a timer
-        {@link java.util.Timer} t = new {@link java.util.Timer}("Move the box");
-        // run the task every 100ms
-        t.{@link java.util.Timer#scheduleAtFixedRate(java.util.TimerTask, long, long) scheduleAtFixedRate}(task, 0, 100);
-    }
-}
-
+ * {@codesnippet org.netbeans.html.boot.fx.Periodicaly} * * @param exec the code to execute * @since 0.7.6 diff --git a/equinox-agentclass-hook/pom.xml b/equinox-agentclass-hook/pom.xml index 436c7d5..2d43cc7 100644 --- a/equinox-agentclass-hook/pom.xml +++ b/equinox-agentclass-hook/pom.xml @@ -48,7 +48,7 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 AgentClass Hook for Equinox equinox-agentclass-hook diff --git a/geo/pom.xml b/geo/pom.xml index 741c6d3..2836398 100644 --- a/geo/pom.xml +++ b/geo/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html.geo - 2.0-SNAPSHOT + 1.4 bundle Geolocation API http://maven.apache.org diff --git a/html4j-maven-plugin/pom.xml b/html4j-maven-plugin/pom.xml index 56cf9b6..41bb214 100644 --- a/html4j-maven-plugin/pom.xml +++ b/html4j-maven-plugin/pom.xml @@ -48,12 +48,12 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 maven-plugin org.netbeans.html html4j-maven-plugin - 2.0-SNAPSHOT + 1.4 Html for Java Maven Plugin http://maven.apache.org Maven plugin to post process the classes with @JavaScriptBody annotations diff --git a/json-tck/pom.xml b/json-tck/pom.xml index 161b19d..696dc26 100644 --- a/json-tck/pom.xml +++ b/json-tck/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html.json.tck - 2.0-SNAPSHOT + 1.4 bundle HTML for Java TCK http://maven.apache.org @@ -85,7 +85,7 @@ org.netbeans.html net.java.html.json - 2.0-SNAPSHOT + 1.4 jar diff --git a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java index 029a3a2..641b31f 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java @@ -92,6 +92,18 @@ final class Bodies { "return c.@net.java.html.js.tests.Sum::sum(II)(a, b);" ) public static native int sumIndirect(Sum c, int a, int b); + + @JavaScriptBody(args = { "c" }, javacall = true, body = + "return {\n" + + " 'sum' : function(a,b) {\n" + + " return c.@net.java.html.js.tests.Sum::sum(II)(a, b);\n" + + " }\n" + + "};\n" + ) + public static native Object sumDelayed(Sum c); + + @JavaScriptBody(args = { "sum", "a", "b" }, body = "return sum.sum(a, b);") + public static native int sumNow(Object sum, int a, int b); @JavaScriptBody(args = { "arr", "index" }, body = "return arr[index];") public static native Object select(Object[] arr, int index); @@ -105,6 +117,9 @@ final class Bodies { @JavaScriptBody(args = { "b" }, body = "return typeof b;") public static native String typeof(boolean b); + @JavaScriptBody(args = { "o" }, body = "return o.toString();") + public static native String toString(Object o); + @JavaScriptBody(args = { "o" }, body = "return Array.isArray(o);") public static native boolean isArray(Object o); diff --git a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java index 46d0775..cbb4984 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java +++ b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java @@ -67,6 +67,25 @@ public class GCBodyTest { s = null; assertGC(ref, "Can disappear!"); } + + @KOTest public void callbackInterfaceShallNotDisappear() throws InterruptedException { + Sum sum = new Sum(); + Object jsSum = Bodies.sumDelayed(sum); + Reference ref = new WeakReference(sum); + sum = null; + IllegalStateException gcError = null; + try { + assertNotGC(ref, false, "object s should still stay"); + } catch (IllegalStateException ex) { + gcError = ex; + } + + int res = Bodies.sumNow(jsSum, 22, 20); + assertEquals(res, 42, "Expecting 42"); + if (gcError != null) { + throw gcError; + } + } private Object assignInst() { Object obj = Bodies.instance(0); diff --git a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java index 1663fa8..6a63640 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java @@ -154,6 +154,15 @@ public class JavaScriptBodyTest { assertEquals("number", doubleType, "Expecting number type: " + doubleType); } + enum Two { + ONE, TWO; + } + + @KOTest public void toStringOfAnEnum() { + String enumStr = Bodies.toString(Two.ONE); + assertEquals(Two.ONE.toString(), enumStr, "Enum toString() used: " + enumStr); + } + @KOTest public void computeInARunnable() { final int[] sum = new int[2]; class First implements Runnable { diff --git a/json-tck/src/main/java/org/netbeans/html/json/tck/package.html b/json-tck/src/main/java/org/netbeans/html/json/tck/package.html new file mode 100644 index 0000000..4e1eb4a --- /dev/null +++ b/json-tck/src/main/java/org/netbeans/html/json/tck/package.html @@ -0,0 +1,56 @@ + + + + + + + + +
Entry point to the + test compatibility kit. +
+ + diff --git a/json-tck/src/main/resources/org/netbeans/html/json/tck/package.html b/json-tck/src/main/resources/org/netbeans/html/json/tck/package.html deleted file mode 100644 index 4e1eb4a..0000000 --- a/json-tck/src/main/resources/org/netbeans/html/json/tck/package.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - -
Entry point to the - test compatibility kit. -
- - diff --git a/json/pom.xml b/json/pom.xml index 77c95f4..acc6ddf 100644 --- a/json/pom.xml +++ b/json/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html.json - 2.0-SNAPSHOT + 1.4 bundle JSON Model in Java http://maven.apache.org diff --git a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java index b719a20..1c2389f 100644 --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java @@ -1315,14 +1315,21 @@ public final class ModelProcessor extends AbstractProcessor { } } String n = e.getSimpleName().toString(); + String c = inPckName(clazz, false); if (isWebSocket) { - body.append(" /** Performs WebSocket communication. Call with null data parameter\n"); + body.append(" /** Performs WebSocket communication and then calls {@link "); + body.append(c).append("#").append(n).append("}.\n"); + body.append(" * Call with null data parameter\n"); body.append(" * to open the connection (even if not required). Call with non-null data to\n"); body.append(" * send messages to server. Call again with null data to close the socket.\n"); body.append(" */\n"); if (onR.headers().length > 0) { error("WebSocket spec does not support headers", e); } + } else { + body.append(" /** Performs network communication and then calls {@link "); + body.append(c).append("#").append(n).append("}.\n"); + body.append(" */\n"); } body.append(" public void ").append(n).append("("); StringBuilder urlBefore = new StringBuilder(); diff --git a/json/src/main/resources/org/netbeans/html/json/spi/package.html b/json/src/main/resources/org/netbeans/html/json/spi/package.html deleted file mode 100644 index 80517a4..0000000 --- a/json/src/main/resources/org/netbeans/html/json/spi/package.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - -
Implement - Technology and - Transfer and use - ContextBuilder to create an instance - of Context representing your technology. -
- - diff --git a/ko-felix-test/pom.xml b/ko-felix-test/pom.xml index 591127e..44ab6a8 100644 --- a/ko-felix-test/pom.xml +++ b/ko-felix-test/pom.xml @@ -48,7 +48,7 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 KO Tests in Felix OSGi Container ko-felix-test diff --git a/ko-osgi-test/pom.xml b/ko-osgi-test/pom.xml index 2af6772..2ef0fef 100644 --- a/ko-osgi-test/pom.xml +++ b/ko-osgi-test/pom.xml @@ -48,7 +48,7 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 KO Tests in Equinox OSGi Container ko-osgi-test diff --git a/ko-ws-tyrus/pom.xml b/ko-ws-tyrus/pom.xml index 9415d83..b3c19a3 100644 --- a/ko-ws-tyrus/pom.xml +++ b/ko-ws-tyrus/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html ko-ws-tyrus - 2.0-SNAPSHOT + 1.4 bundle Tyrus Based WebSockets http://maven.apache.org diff --git a/ko4j/pom.xml b/ko4j/pom.xml index 5ba42a3..4bf89a3 100644 --- a/ko4j/pom.xml +++ b/ko4j/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html ko4j - 2.0-SNAPSHOT + 1.4 bundle Knockout.js for Java http://maven.apache.org @@ -140,6 +140,12 @@ org.netbeans.html + ko4j + provided + 1.3 + + + org.netbeans.html net.java.html.boot ${project.version} jar 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 3479068..8bd3705 100644 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java @@ -72,7 +72,7 @@ final class Knockout extends WeakReference { "if (property === null) ret = object;\n" + "else if (object === null) ret = null;\n" + "else ret = object[property];\n" + - "return ret ? ko.utils.unwrapObservable(ret) : null;" + "return ret ? ko['utils']['unwrapObservable'](ret) : null;" ) static Object getProperty(Object object, String property) { return null; diff --git a/pom.xml b/pom.xml index a19bfcb..18aba6d 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 4.0.0 org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 pom HTML APIs via Java @@ -79,7 +79,9 @@ ko-osgi-test equinox-agentclass-hook boot-script + boot-agent-test xhr4j @@ -95,10 +97,10 @@ http://netbeans.org - scm:hg:https://hg.netbeans.org/html4j - scm:hg:https://hg.netbeans.org/html4j - https://hg.netbeans.org/html4j - default + scm:git:https://github.com/jtulach/html-java-api.git + scm:git:https://github.com/jtulach/html-java-api.git + https://github.com/jtulach/html-java-api + release-1.4 @@ -199,6 +201,13 @@ org.netbeans.html.boot.impl:org.netbeans.html.boot.fx:org.netbeans.html.context. http://bits.netbeans.org/8.0/javadoc/org-openide-util-lookup/ http://docs.oracle.com/javase/8/javafx/api/ + org.apidesign.javadoc.codesnippet.Doclet + + org.apidesign.javadoc + codesnippet-doclet + 0.20 + + -snippetpath "${basedir}" diff --git a/sound/pom.xml b/sound/pom.xml index 62fce4b..0d59856 100644 --- a/sound/pom.xml +++ b/sound/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html net.java.html.sound - 2.0-SNAPSHOT + 1.4 bundle Sound API via HTML http://maven.apache.org @@ -81,7 +81,7 @@ org.netbeans.html net.java.html.boot - 2.0-SNAPSHOT + 1.4 jar diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 850c0d7..09f2410 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -81,11 +81,28 @@ treated as null. Better behavior under multi-threaded load. + + Workaround for garbage collector behavior of modern JavaFX WebView + implementations (JDK8 u112 and newer). + JavaFX Presenter can + + show popup window. + Development has switched to + + Git repository thanks to + + conversion by Emilian Bold. + Better support for obfuscation of knockout module + (bug + 270013). +

Improvements in version 1.3

- {@link net.java.html.json.Model Model classes} can have + {@link net.java.html.json.Model Model classes} can have {@link net.java.html.json.Model#instance() per-instance private data}. {@link net.java.html.json.Model Model classes} can generate builder-like construction methods if builder @@ -124,8 +141,33 @@ of CORS by handling the {@link net.java.html.json.OnReceive} connections in Java. +

What's new in older versions?

+ +

+ Click the + link + to view even more + historic changes... +

+ + +
+ +

What's Been Improved in Version 1.2.3?

+

One can control {@link net.java.html.json.OnReceive#headers() HTTP request headers} when connecting to server using the {@link net.java.html.json.OnReceive} annotation. It is possible to have @@ -137,6 +179,7 @@ demonstrates. Bugfix of issues 250503, 252987. +

What's New in Version 1.1?

@@ -211,27 +254,6 @@ prevent endless debugging when one forgets to do so.

-

- What's new in older versions? Click the - link - to view even more - historic changes below: -

- - -
-

What's New in Version 0.9?

@@ -349,7 +371,7 @@ $ mvn archetype:generate \ -DarchetypeGroupId=com.dukescript.archetype \ -DarchetypeArtifactId=knockout4j-archetype \ - -DarchetypeVersion=0.11 # or newer version, if available + -DarchetypeVersion=0.16 # or newer version, if available Answer few questions (for example choose myfirstbrwsrpage as artifactId) and then you can: @@ -608,6 +630,7 @@ $ ls client/src/main/webapp/pages/index.html online:
  • Current development version +
  • Version 1.3
  • Version 1.2.3
  • Version 1.1
  • Version 1.0 diff --git a/xhr4j/pom.xml b/xhr4j/pom.xml index 947ef26..6f9d158 100644 --- a/xhr4j/pom.xml +++ b/xhr4j/pom.xml @@ -48,11 +48,11 @@ org.netbeans.html pom - 2.0-SNAPSHOT + 1.4 org.netbeans.html xhr4j - 2.0-SNAPSHOT + 1.4 bundle XHR via Java http://maven.apache.org