FX launcher can start tests model
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 28 Apr 2013 17:42:49 +0200
branchmodel
changeset 1041f18b7262fe91
parent 1040 784443774aef
child 1042 33b2625bf03a
FX launcher can start tests
launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java
launcher/fx/pom.xml
launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java
launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java
launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java
launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java
launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java
launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Run.java
launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/harness.xhtml
launcher/pom.xml
     1.1 --- a/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java	Sun Apr 28 17:11:12 2013 +0200
     1.2 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java	Sun Apr 28 17:42:49 2013 +0200
     1.3 @@ -79,12 +79,20 @@
     1.4       * @return launcher executing in external browser.
     1.5       */
     1.6      public static Launcher createBrowser(String cmd) {
     1.7 +        String msg = "Trying to create browser '" + cmd + "'";
     1.8          try {
     1.9 -            Class<?> c = loadClass("org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher");
    1.10 +            Class<?> c;
    1.11 +            if ("fx".equals(cmd)) { // NOI18N
    1.12 +                msg = "Please include org.apidesign.bck2brwsr:launcher.fx dependency!";
    1.13 +                c = loadClass("org.apidesign.bck2brwsr.launcher.FXBrwsrLauncher"); // NOI18N
    1.14 +            } else {
    1.15 +                msg = "Please include org.apidesign.bck2brwsr:launcher.html dependency!";
    1.16 +                c = loadClass("org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher"); // NOI18N
    1.17 +            }
    1.18              Constructor<?> cnstr = c.getConstructor(String.class);
    1.19              return (Launcher) cnstr.newInstance(cmd);
    1.20          } catch (Exception ex) {
    1.21 -            throw new IllegalStateException("Please include org.apidesign.bck2brwsr:launcher.html dependency!", ex);
    1.22 +            throw new IllegalStateException(msg, ex);
    1.23          }
    1.24      }
    1.25      
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/launcher/fx/pom.xml	Sun Apr 28 17:42:49 2013 +0200
     2.3 @@ -0,0 +1,54 @@
     2.4 +<?xml version="1.0"?>
     2.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     2.6 +  <modelVersion>4.0.0</modelVersion>
     2.7 +  <parent>
     2.8 +    <groupId>org.apidesign.bck2brwsr</groupId>
     2.9 +    <artifactId>launcher-pom</artifactId>
    2.10 +    <version>0.7-SNAPSHOT</version>
    2.11 +  </parent>
    2.12 +  <groupId>org.apidesign.bck2brwsr</groupId>
    2.13 +  <artifactId>launcher.fx</artifactId>
    2.14 +  <version>0.7-SNAPSHOT</version>
    2.15 +  <name>FXBrwsr Launcher</name>
    2.16 +  <url>http://maven.apache.org</url>
    2.17 +    <build>
    2.18 +        <plugins>
    2.19 +            <plugin>
    2.20 +                <groupId>org.apache.maven.plugins</groupId>
    2.21 +                <artifactId>maven-compiler-plugin</artifactId>
    2.22 +                <version>2.3.2</version>
    2.23 +                <configuration>
    2.24 +                    <source>1.7</source>
    2.25 +                    <target>1.7</target>
    2.26 +                </configuration>
    2.27 +            </plugin>
    2.28 +        </plugins>
    2.29 +    </build>
    2.30 +    <properties>
    2.31 +    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    2.32 +  </properties>
    2.33 +  <dependencies>
    2.34 +    <dependency>
    2.35 +      <groupId>${project.groupId}</groupId>
    2.36 +      <artifactId>launcher</artifactId>
    2.37 +      <version>${project.version}</version>
    2.38 +    </dependency>
    2.39 +    <dependency>
    2.40 +      <groupId>org.glassfish.grizzly</groupId>
    2.41 +      <artifactId>grizzly-http-server</artifactId>
    2.42 +      <version>2.2.19</version>
    2.43 +    </dependency>
    2.44 +    <dependency>
    2.45 +      <groupId>com.oracle</groupId>
    2.46 +      <artifactId>javafx</artifactId>
    2.47 +      <version>2.2</version>
    2.48 +      <scope>system</scope>
    2.49 +      <systemPath>${java.home}/lib/jfxrt.jar</systemPath>
    2.50 +    </dependency>
    2.51 +    <dependency>
    2.52 +        <groupId>${project.groupId}</groupId>
    2.53 +        <artifactId>vm4brwsr</artifactId>
    2.54 +        <version>${project.version}</version>
    2.55 +    </dependency>
    2.56 +  </dependencies>
    2.57 +</project>
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java	Sun Apr 28 17:42:49 2013 +0200
     3.3 @@ -0,0 +1,609 @@
     3.4 +/**
     3.5 + * Back 2 Browser Bytecode Translator
     3.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 + *
     3.8 + * This program is free software: you can redistribute it and/or modify
     3.9 + * it under the terms of the GNU General Public License as published by
    3.10 + * the Free Software Foundation, version 2 of the License.
    3.11 + *
    3.12 + * This program is distributed in the hope that it will be useful,
    3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 + * GNU General Public License for more details.
    3.16 + *
    3.17 + * You should have received a copy of the GNU General Public License
    3.18 + * along with this program. Look for COPYING file in the top folder.
    3.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    3.20 + */
    3.21 +package org.apidesign.bck2brwsr.launcher;
    3.22 +
    3.23 +import java.io.Closeable;
    3.24 +import java.io.File;
    3.25 +import java.io.IOException;
    3.26 +import java.io.InputStream;
    3.27 +import java.io.InterruptedIOException;
    3.28 +import java.io.OutputStream;
    3.29 +import java.io.UnsupportedEncodingException;
    3.30 +import java.io.Writer;
    3.31 +import java.net.URI;
    3.32 +import java.net.URISyntaxException;
    3.33 +import java.net.URL;
    3.34 +import java.util.ArrayList;
    3.35 +import java.util.Arrays;
    3.36 +import java.util.Enumeration;
    3.37 +import java.util.LinkedHashSet;
    3.38 +import java.util.List;
    3.39 +import java.util.Set;
    3.40 +import java.util.concurrent.BlockingQueue;
    3.41 +import java.util.concurrent.CountDownLatch;
    3.42 +import java.util.concurrent.LinkedBlockingQueue;
    3.43 +import java.util.concurrent.TimeUnit;
    3.44 +import java.util.logging.Level;
    3.45 +import java.util.logging.Logger;
    3.46 +import org.apidesign.bck2brwsr.launcher.InvocationContext.Resource;
    3.47 +import org.apidesign.vm4brwsr.Bck2Brwsr;
    3.48 +import org.glassfish.grizzly.PortRange;
    3.49 +import org.glassfish.grizzly.http.server.HttpHandler;
    3.50 +import org.glassfish.grizzly.http.server.HttpServer;
    3.51 +import org.glassfish.grizzly.http.server.NetworkListener;
    3.52 +import org.glassfish.grizzly.http.server.Request;
    3.53 +import org.glassfish.grizzly.http.server.Response;
    3.54 +import org.glassfish.grizzly.http.server.ServerConfiguration;
    3.55 +import org.glassfish.grizzly.http.util.HttpStatus;
    3.56 +
    3.57 +/**
    3.58 + * Lightweight server to launch Bck2Brwsr applications and tests.
    3.59 + * Supports execution in native browser as well as Java's internal 
    3.60 + * execution engine.
    3.61 + */
    3.62 +class BaseHTTPLauncher extends Launcher implements Closeable {
    3.63 +    private static final Logger LOG = Logger.getLogger(BaseHTTPLauncher.class.getName());
    3.64 +    private static final InvocationContext END = new InvocationContext(null, null, null);
    3.65 +    private final Set<ClassLoader> loaders = new LinkedHashSet<>();
    3.66 +    private final BlockingQueue<InvocationContext> methods = new LinkedBlockingQueue<>();
    3.67 +    private long timeOut;
    3.68 +    private final Res resources = new Res();
    3.69 +    private final String cmd;
    3.70 +    private Object[] brwsr;
    3.71 +    private HttpServer server;
    3.72 +    private CountDownLatch wait;
    3.73 +    
    3.74 +    public BaseHTTPLauncher(String cmd) {
    3.75 +        this.cmd = cmd;
    3.76 +        addClassLoader(Bck2Brwsr.class.getClassLoader());
    3.77 +        setTimeout(180000);
    3.78 +    }
    3.79 +    
    3.80 +    @Override
    3.81 +    InvocationContext runMethod(InvocationContext c) throws IOException {
    3.82 +        loaders.add(c.clazz.getClassLoader());
    3.83 +        methods.add(c);
    3.84 +        try {
    3.85 +            c.await(timeOut);
    3.86 +        } catch (InterruptedException ex) {
    3.87 +            throw new IOException(ex);
    3.88 +        }
    3.89 +        return c;
    3.90 +    }
    3.91 +    
    3.92 +    public void setTimeout(long ms) {
    3.93 +        timeOut = ms;
    3.94 +    }
    3.95 +    
    3.96 +    public void addClassLoader(ClassLoader url) {
    3.97 +        this.loaders.add(url);
    3.98 +    }
    3.99 +    
   3.100 +    ClassLoader[] loaders() {
   3.101 +        return loaders.toArray(new ClassLoader[loaders.size()]);
   3.102 +    }
   3.103 +
   3.104 +    public void showURL(String startpage) throws IOException {
   3.105 +        if (!startpage.startsWith("/")) {
   3.106 +            startpage = "/" + startpage;
   3.107 +        }
   3.108 +        HttpServer s = initServer(".", true);
   3.109 +        int last = startpage.lastIndexOf('/');
   3.110 +        String prefix = startpage.substring(0, last);
   3.111 +        String simpleName = startpage.substring(last);
   3.112 +        s.getServerConfiguration().addHttpHandler(new SubTree(resources, prefix), "/");
   3.113 +        try {
   3.114 +            launchServerAndBrwsr(s, simpleName);
   3.115 +        } catch (URISyntaxException | InterruptedException ex) {
   3.116 +            throw new IOException(ex);
   3.117 +        }
   3.118 +    }
   3.119 +
   3.120 +    void showDirectory(File dir, String startpage) throws IOException {
   3.121 +        if (!startpage.startsWith("/")) {
   3.122 +            startpage = "/" + startpage;
   3.123 +        }
   3.124 +        HttpServer s = initServer(dir.getPath(), false);
   3.125 +        try {
   3.126 +            launchServerAndBrwsr(s, startpage);
   3.127 +        } catch (URISyntaxException | InterruptedException ex) {
   3.128 +            throw new IOException(ex);
   3.129 +        }
   3.130 +    }
   3.131 +
   3.132 +    @Override
   3.133 +    public void initialize() throws IOException {
   3.134 +        try {
   3.135 +            executeInBrowser();
   3.136 +        } catch (InterruptedException ex) {
   3.137 +            final InterruptedIOException iio = new InterruptedIOException(ex.getMessage());
   3.138 +            iio.initCause(ex);
   3.139 +            throw iio;
   3.140 +        } catch (Exception ex) {
   3.141 +            if (ex instanceof IOException) {
   3.142 +                throw (IOException)ex;
   3.143 +            }
   3.144 +            if (ex instanceof RuntimeException) {
   3.145 +                throw (RuntimeException)ex;
   3.146 +            }
   3.147 +            throw new IOException(ex);
   3.148 +        }
   3.149 +    }
   3.150 +    
   3.151 +    private HttpServer initServer(String path, boolean addClasses) throws IOException {
   3.152 +        HttpServer s = HttpServer.createSimpleServer(path, new PortRange(8080, 65535));
   3.153 +
   3.154 +        final ServerConfiguration conf = s.getServerConfiguration();
   3.155 +        if (addClasses) {
   3.156 +            conf.addHttpHandler(new VM(), "/bck2brwsr.js");
   3.157 +            conf.addHttpHandler(new Classes(resources), "/classes/");
   3.158 +        }
   3.159 +        return s;
   3.160 +    }
   3.161 +    
   3.162 +    private void executeInBrowser() throws InterruptedException, URISyntaxException, IOException {
   3.163 +        wait = new CountDownLatch(1);
   3.164 +        server = initServer(".", true);
   3.165 +        final ServerConfiguration conf = server.getServerConfiguration();
   3.166 +        
   3.167 +        class DynamicResourceHandler extends HttpHandler {
   3.168 +            private final InvocationContext ic;
   3.169 +            public DynamicResourceHandler(InvocationContext ic) {
   3.170 +                if (ic == null || ic.resources.isEmpty()) {
   3.171 +                    throw new NullPointerException();
   3.172 +                }
   3.173 +                this.ic = ic;
   3.174 +                for (Resource r : ic.resources) {
   3.175 +                    conf.addHttpHandler(this, r.httpPath);
   3.176 +                }
   3.177 +            }
   3.178 +
   3.179 +            public void close() {
   3.180 +                conf.removeHttpHandler(this);
   3.181 +            }
   3.182 +            
   3.183 +            @Override
   3.184 +            public void service(Request request, Response response) throws Exception {
   3.185 +                for (Resource r : ic.resources) {
   3.186 +                    if (r.httpPath.equals(request.getRequestURI())) {
   3.187 +                        LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI());
   3.188 +                        response.setContentType(r.httpType);
   3.189 +                        r.httpContent.reset();
   3.190 +                        String[] params = null;
   3.191 +                        if (r.parameters.length != 0) {
   3.192 +                            params = new String[r.parameters.length];
   3.193 +                            for (int i = 0; i < r.parameters.length; i++) {
   3.194 +                                params[i] = request.getParameter(r.parameters[i]);
   3.195 +                            }
   3.196 +                        }
   3.197 +                        
   3.198 +                        copyStream(r.httpContent, response.getOutputStream(), null, params);
   3.199 +                    }
   3.200 +                }
   3.201 +            }
   3.202 +        }
   3.203 +        
   3.204 +        conf.addHttpHandler(new Page(resources, 
   3.205 +            "org/apidesign/bck2brwsr/launcher/fximpl/harness.xhtml"
   3.206 +        ), "/execute");
   3.207 +        
   3.208 +        conf.addHttpHandler(new HttpHandler() {
   3.209 +            int cnt;
   3.210 +            List<InvocationContext> cases = new ArrayList<>();
   3.211 +            DynamicResourceHandler prev;
   3.212 +            @Override
   3.213 +            public void service(Request request, Response response) throws Exception {
   3.214 +                String id = request.getParameter("request");
   3.215 +                String value = request.getParameter("result");
   3.216 +                if (value != null && value.indexOf((char)0xC5) != -1) {
   3.217 +                    value = toUTF8(value);
   3.218 +                }
   3.219 +                
   3.220 +                
   3.221 +                InvocationContext mi = null;
   3.222 +                int caseNmbr = -1;
   3.223 +                
   3.224 +                if (id != null && value != null) {
   3.225 +                    LOG.log(Level.INFO, "Received result for case {0} = {1}", new Object[]{id, value});
   3.226 +                    value = decodeURL(value);
   3.227 +                    int indx = Integer.parseInt(id);
   3.228 +                    cases.get(indx).result(value, null);
   3.229 +                    if (++indx < cases.size()) {
   3.230 +                        mi = cases.get(indx);
   3.231 +                        LOG.log(Level.INFO, "Re-executing case {0}", indx);
   3.232 +                        caseNmbr = indx;
   3.233 +                    }
   3.234 +                } else {
   3.235 +                    if (!cases.isEmpty()) {
   3.236 +                        LOG.info("Re-executing test cases");
   3.237 +                        mi = cases.get(0);
   3.238 +                        caseNmbr = 0;
   3.239 +                    }
   3.240 +                }
   3.241 +                
   3.242 +                if (prev != null) {
   3.243 +                    prev.close();
   3.244 +                    prev = null;
   3.245 +                }
   3.246 +                
   3.247 +                if (mi == null) {
   3.248 +                    mi = methods.take();
   3.249 +                    caseNmbr = cnt++;
   3.250 +                }
   3.251 +                if (mi == END) {
   3.252 +                    response.getWriter().write("");
   3.253 +                    wait.countDown();
   3.254 +                    cnt = 0;
   3.255 +                    LOG.log(Level.INFO, "End of data reached. Exiting.");
   3.256 +                    return;
   3.257 +                }
   3.258 +                
   3.259 +                if (!mi.resources.isEmpty()) {
   3.260 +                    prev = new DynamicResourceHandler(mi);
   3.261 +                }
   3.262 +                
   3.263 +                cases.add(mi);
   3.264 +                final String cn = mi.clazz.getName();
   3.265 +                final String mn = mi.methodName;
   3.266 +                LOG.log(Level.INFO, "Request for {0} case. Sending {1}.{2}", new Object[]{caseNmbr, cn, mn});
   3.267 +                response.getWriter().write("{"
   3.268 +                    + "className: '" + cn + "', "
   3.269 +                    + "methodName: '" + mn + "', "
   3.270 +                    + "request: " + caseNmbr
   3.271 +                );
   3.272 +                if (mi.html != null) {
   3.273 +                    response.getWriter().write(", html: '");
   3.274 +                    response.getWriter().write(encodeJSON(mi.html));
   3.275 +                    response.getWriter().write("'");
   3.276 +                }
   3.277 +                response.getWriter().write("}");
   3.278 +            }
   3.279 +        }, "/data");
   3.280 +
   3.281 +        this.brwsr = launchServerAndBrwsr(server, "/execute");
   3.282 +    }
   3.283 +    
   3.284 +    private static String encodeJSON(String in) {
   3.285 +        StringBuilder sb = new StringBuilder();
   3.286 +        for (int i = 0; i < in.length(); i++) {
   3.287 +            char ch = in.charAt(i);
   3.288 +            if (ch < 32 || ch == '\'' || ch == '"') {
   3.289 +                sb.append("\\u");
   3.290 +                String hs = "0000" + Integer.toHexString(ch);
   3.291 +                hs = hs.substring(hs.length() - 4);
   3.292 +                sb.append(hs);
   3.293 +            } else {
   3.294 +                sb.append(ch);
   3.295 +            }
   3.296 +        }
   3.297 +        return sb.toString();
   3.298 +    }
   3.299 +    
   3.300 +    @Override
   3.301 +    public void shutdown() throws IOException {
   3.302 +        methods.offer(END);
   3.303 +        for (;;) {
   3.304 +            int prev = methods.size();
   3.305 +            try {
   3.306 +                if (wait != null && wait.await(timeOut, TimeUnit.MILLISECONDS)) {
   3.307 +                    break;
   3.308 +                }
   3.309 +            } catch (InterruptedException ex) {
   3.310 +                throw new IOException(ex);
   3.311 +            }
   3.312 +            if (prev == methods.size()) {
   3.313 +                LOG.log(
   3.314 +                    Level.WARNING, 
   3.315 +                    "Timeout and no test has been executed meanwhile (at {0}). Giving up.", 
   3.316 +                    methods.size()
   3.317 +                );
   3.318 +                break;
   3.319 +            }
   3.320 +            LOG.log(Level.INFO, 
   3.321 +                "Timeout, but tests got from {0} to {1}. Trying again.", 
   3.322 +                new Object[]{prev, methods.size()}
   3.323 +            );
   3.324 +        }
   3.325 +        stopServerAndBrwsr(server, brwsr);
   3.326 +    }
   3.327 +    
   3.328 +    static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException {
   3.329 +        for (;;) {
   3.330 +            int ch = is.read();
   3.331 +            if (ch == -1) {
   3.332 +                break;
   3.333 +            }
   3.334 +            if (ch == '$' && params.length > 0) {
   3.335 +                int cnt = is.read() - '0';
   3.336 +                if (baseURL != null && cnt == 'U' - '0') {
   3.337 +                    os.write(baseURL.getBytes("UTF-8"));
   3.338 +                } else {
   3.339 +                    if (cnt >= 0 && cnt < params.length) {
   3.340 +                        os.write(params[cnt].getBytes("UTF-8"));
   3.341 +                    } else {
   3.342 +                        os.write('$');
   3.343 +                        os.write(cnt + '0');
   3.344 +                    }
   3.345 +                }
   3.346 +            } else {
   3.347 +                os.write(ch);
   3.348 +            }
   3.349 +        }
   3.350 +    }
   3.351 +
   3.352 +    private Object[] launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException {
   3.353 +        server.start();
   3.354 +        NetworkListener listener = server.getListeners().iterator().next();
   3.355 +        int port = listener.getPort();
   3.356 +        
   3.357 +        URI uri = new URI("http://localhost:" + port + page);
   3.358 +        return showBrwsr(uri);
   3.359 +    }
   3.360 +    private static String toUTF8(String value) throws UnsupportedEncodingException {
   3.361 +        byte[] arr = new byte[value.length()];
   3.362 +        for (int i = 0; i < arr.length; i++) {
   3.363 +            arr[i] = (byte)value.charAt(i);
   3.364 +        }
   3.365 +        return new String(arr, "UTF-8");
   3.366 +    }
   3.367 +    
   3.368 +    private static String decodeURL(String s) {
   3.369 +        for (;;) {
   3.370 +            int pos = s.indexOf('%');
   3.371 +            if (pos == -1) {
   3.372 +                return s;
   3.373 +            }
   3.374 +            int i = Integer.parseInt(s.substring(pos + 1, pos + 2), 16);
   3.375 +            s = s.substring(0, pos) + (char)i + s.substring(pos + 2);
   3.376 +        }
   3.377 +    }
   3.378 +    
   3.379 +    private void stopServerAndBrwsr(HttpServer server, Object[] brwsr) throws IOException {
   3.380 +        if (brwsr == null) {
   3.381 +            return;
   3.382 +        }
   3.383 +        Process process = (Process)brwsr[0];
   3.384 +        
   3.385 +        server.stop();
   3.386 +        InputStream stdout = process.getInputStream();
   3.387 +        InputStream stderr = process.getErrorStream();
   3.388 +        drain("StdOut", stdout);
   3.389 +        drain("StdErr", stderr);
   3.390 +        process.destroy();
   3.391 +        int res;
   3.392 +        try {
   3.393 +            res = process.waitFor();
   3.394 +        } catch (InterruptedException ex) {
   3.395 +            throw new IOException(ex);
   3.396 +        }
   3.397 +        LOG.log(Level.INFO, "Exit code: {0}", res);
   3.398 +
   3.399 +        deleteTree((File)brwsr[1]);
   3.400 +    }
   3.401 +    
   3.402 +    private static void drain(String name, InputStream is) throws IOException {
   3.403 +        int av = is.available();
   3.404 +        if (av > 0) {
   3.405 +            StringBuilder sb = new StringBuilder();
   3.406 +            sb.append("v== ").append(name).append(" ==v\n");
   3.407 +            while (av-- > 0) {
   3.408 +                sb.append((char)is.read());
   3.409 +            }
   3.410 +            sb.append("\n^== ").append(name).append(" ==^");
   3.411 +            LOG.log(Level.INFO, sb.toString());
   3.412 +        }
   3.413 +    }
   3.414 +
   3.415 +    private void deleteTree(File file) {
   3.416 +        if (file == null) {
   3.417 +            return;
   3.418 +        }
   3.419 +        File[] arr = file.listFiles();
   3.420 +        if (arr != null) {
   3.421 +            for (File s : arr) {
   3.422 +                deleteTree(s);
   3.423 +            }
   3.424 +        }
   3.425 +        file.delete();
   3.426 +    }
   3.427 +
   3.428 +    @Override
   3.429 +    public void close() throws IOException {
   3.430 +        shutdown();
   3.431 +    }
   3.432 +
   3.433 +    protected Object[] showBrwsr(URI uri) throws IOException {
   3.434 +        LOG.log(Level.INFO, "Showing {0}", uri);
   3.435 +        if (cmd == null) {
   3.436 +            try {
   3.437 +                LOG.log(Level.INFO, "Trying Desktop.browse on {0} {2} by {1}", new Object[] {
   3.438 +                    System.getProperty("java.vm.name"),
   3.439 +                    System.getProperty("java.vm.vendor"),
   3.440 +                    System.getProperty("java.vm.version"),
   3.441 +                });
   3.442 +                java.awt.Desktop.getDesktop().browse(uri);
   3.443 +                LOG.log(Level.INFO, "Desktop.browse successfully finished");
   3.444 +                return null;
   3.445 +            } catch (UnsupportedOperationException ex) {
   3.446 +                LOG.log(Level.INFO, "Desktop.browse not supported: {0}", ex.getMessage());
   3.447 +                LOG.log(Level.FINE, null, ex);
   3.448 +            }
   3.449 +        }
   3.450 +        {
   3.451 +            String cmdName = cmd == null ? "xdg-open" : cmd;
   3.452 +            String[] cmdArr = { 
   3.453 +                cmdName, uri.toString()
   3.454 +            };
   3.455 +            LOG.log(Level.INFO, "Launching {0}", Arrays.toString(cmdArr));
   3.456 +            final Process process = Runtime.getRuntime().exec(cmdArr);
   3.457 +            return new Object[] { process, null };
   3.458 +        }
   3.459 +    }
   3.460 +
   3.461 +    void generateBck2BrwsrJS(StringBuilder sb, Bck2Brwsr.Resources loader) throws IOException {
   3.462 +        Bck2Brwsr.generate(sb, loader);
   3.463 +        sb.append(
   3.464 +            "(function WrapperVM(global) {"
   3.465 +            + "  function ldCls(res) {\n"
   3.466 +            + "    var request = new XMLHttpRequest();\n"
   3.467 +            + "    request.open('GET', '/classes/' + res, false);\n"
   3.468 +            + "    request.send();\n"
   3.469 +            + "    if (request.status !== 200) return null;\n"
   3.470 +            + "    var arr = eval('(' + request.responseText + ')');\n"
   3.471 +            + "    return arr;\n"
   3.472 +            + "  }\n"
   3.473 +            + "  var prevvm = global.bck2brwsr;\n"
   3.474 +            + "  global.bck2brwsr = function() {\n"
   3.475 +            + "    var args = Array.prototype.slice.apply(arguments);\n"
   3.476 +            + "    args.unshift(ldCls);\n"
   3.477 +            + "    return prevvm.apply(null, args);\n"
   3.478 +            + "  };\n"
   3.479 +            + "})(this);\n");
   3.480 +    }
   3.481 +
   3.482 +    private class Res implements Bck2Brwsr.Resources {
   3.483 +        @Override
   3.484 +        public InputStream get(String resource) throws IOException {
   3.485 +            for (ClassLoader l : loaders) {
   3.486 +                URL u = null;
   3.487 +                Enumeration<URL> en = l.getResources(resource);
   3.488 +                while (en.hasMoreElements()) {
   3.489 +                    u = en.nextElement();
   3.490 +                }
   3.491 +                if (u != null) {
   3.492 +                    return u.openStream();
   3.493 +                }
   3.494 +            }
   3.495 +            throw new IOException("Can't find " + resource);
   3.496 +        }
   3.497 +    }
   3.498 +
   3.499 +    private static class Page extends HttpHandler {
   3.500 +        final String resource;
   3.501 +        private final String[] args;
   3.502 +        private final Res res;
   3.503 +        
   3.504 +        public Page(Res res, String resource, String... args) {
   3.505 +            this.res = res;
   3.506 +            this.resource = resource;
   3.507 +            this.args = args.length == 0 ? new String[] { "$0" } : args;
   3.508 +        }
   3.509 +
   3.510 +        @Override
   3.511 +        public void service(Request request, Response response) throws Exception {
   3.512 +            String r = computePage(request);
   3.513 +            if (r.startsWith("/")) {
   3.514 +                r = r.substring(1);
   3.515 +            }
   3.516 +            String[] replace = {};
   3.517 +            if (r.endsWith(".html")) {
   3.518 +                response.setContentType("text/html");
   3.519 +                LOG.info("Content type text/html");
   3.520 +                replace = args;
   3.521 +            }
   3.522 +            if (r.endsWith(".xhtml")) {
   3.523 +                response.setContentType("application/xhtml+xml");
   3.524 +                LOG.info("Content type application/xhtml+xml");
   3.525 +                replace = args;
   3.526 +            }
   3.527 +            OutputStream os = response.getOutputStream();
   3.528 +            try (InputStream is = res.get(r)) {
   3.529 +                copyStream(is, os, request.getRequestURL().toString(), replace);
   3.530 +            } catch (IOException ex) {
   3.531 +                response.setDetailMessage(ex.getLocalizedMessage());
   3.532 +                response.setError();
   3.533 +                response.setStatus(404);
   3.534 +            }
   3.535 +        }
   3.536 +
   3.537 +        protected String computePage(Request request) {
   3.538 +            String r = resource;
   3.539 +            if (r == null) {
   3.540 +                r = request.getHttpHandlerPath();
   3.541 +            }
   3.542 +            return r;
   3.543 +        }
   3.544 +    }
   3.545 +    
   3.546 +    private static class SubTree extends Page {
   3.547 +
   3.548 +        public SubTree(Res res, String resource, String... args) {
   3.549 +            super(res, resource, args);
   3.550 +        }
   3.551 +
   3.552 +        @Override
   3.553 +        protected String computePage(Request request) {
   3.554 +            return resource + request.getHttpHandlerPath();
   3.555 +        }
   3.556 +        
   3.557 +        
   3.558 +    }
   3.559 +
   3.560 +    private class VM extends HttpHandler {
   3.561 +        @Override
   3.562 +        public void service(Request request, Response response) throws Exception {
   3.563 +            response.setCharacterEncoding("UTF-8");
   3.564 +            response.setContentType("text/javascript");
   3.565 +            StringBuilder sb = new StringBuilder();
   3.566 +            generateBck2BrwsrJS(sb, BaseHTTPLauncher.this.resources);
   3.567 +            response.getWriter().write(sb.toString());
   3.568 +        }
   3.569 +    }
   3.570 +
   3.571 +    private static class Classes extends HttpHandler {
   3.572 +        private final Res loader;
   3.573 +
   3.574 +        public Classes(Res loader) {
   3.575 +            this.loader = loader;
   3.576 +        }
   3.577 +
   3.578 +        @Override
   3.579 +        public void service(Request request, Response response) throws Exception {
   3.580 +            String res = request.getHttpHandlerPath();
   3.581 +            if (res.startsWith("/")) {
   3.582 +                res = res.substring(1);
   3.583 +            }
   3.584 +            try (InputStream is = loader.get(res)) {
   3.585 +                response.setContentType("text/javascript");
   3.586 +                Writer w = response.getWriter();
   3.587 +                w.append("[");
   3.588 +                for (int i = 0;; i++) {
   3.589 +                    int b = is.read();
   3.590 +                    if (b == -1) {
   3.591 +                        break;
   3.592 +                    }
   3.593 +                    if (i > 0) {
   3.594 +                        w.append(", ");
   3.595 +                    }
   3.596 +                    if (i % 20 == 0) {
   3.597 +                        w.write("\n");
   3.598 +                    }
   3.599 +                    if (b > 127) {
   3.600 +                        b = b - 256;
   3.601 +                    }
   3.602 +                    w.append(Integer.toString(b));
   3.603 +                }
   3.604 +                w.append("\n]");
   3.605 +            } catch (IOException ex) {
   3.606 +                response.setStatus(HttpStatus.NOT_FOUND_404);
   3.607 +                response.setError();
   3.608 +                response.setDetailMessage(ex.getMessage());
   3.609 +            }
   3.610 +        }
   3.611 +    }
   3.612 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java	Sun Apr 28 17:42:49 2013 +0200
     4.3 @@ -0,0 +1,99 @@
     4.4 +/**
     4.5 + * Back 2 Browser Bytecode Translator
     4.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details.
    4.16 + *
    4.17 + * You should have received a copy of the GNU General Public License
    4.18 + * along with this program. Look for COPYING file in the top folder.
    4.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    4.20 + */
    4.21 +package org.apidesign.bck2brwsr.launcher;
    4.22 +
    4.23 +import org.apidesign.bck2brwsr.launcher.fximpl.FXBrwsr;
    4.24 +import java.io.IOException;
    4.25 +import java.lang.reflect.Method;
    4.26 +import java.net.URI;
    4.27 +import java.net.URL;
    4.28 +import java.net.URLClassLoader;
    4.29 +
    4.30 +import java.util.concurrent.Executors;
    4.31 +import java.util.logging.Level;
    4.32 +import java.util.logging.Logger;
    4.33 +import javafx.application.Platform;
    4.34 +import org.apidesign.bck2brwsr.launcher.fximpl.JVMBridge;
    4.35 +import org.apidesign.vm4brwsr.Bck2Brwsr;
    4.36 +
    4.37 +/**
    4.38 + *
    4.39 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.40 + */
    4.41 +final class FXBrwsrLauncher extends BaseHTTPLauncher {
    4.42 +    private static final Logger LOG = Logger.getLogger(FXBrwsrLauncher.class.getName());
    4.43 +    static {
    4.44 +        try {
    4.45 +            Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
    4.46 +            m.setAccessible(true);
    4.47 +            URL l = new URL("file://" + System.getProperty("java.home") + "/lib/jfxrt.jar");
    4.48 +            LOG.log(Level.INFO, "url : {0}", l);
    4.49 +            m.invoke(ClassLoader.getSystemClassLoader(), l);
    4.50 +        } catch (Exception ex) {
    4.51 +            throw new LinkageError("Can't add jfxrt.jar on the classpath", ex);
    4.52 +        }
    4.53 +    }
    4.54 +    
    4.55 +    public FXBrwsrLauncher(String ignore) {
    4.56 +        super(null);
    4.57 +    }
    4.58 +
    4.59 +    @Override
    4.60 +    protected Object[] showBrwsr(final URI url) throws IOException {
    4.61 +        try {
    4.62 +            LOG.log(Level.INFO, "showBrwsr for {0}", url);
    4.63 +            JVMBridge.registerClassLoaders(loaders());
    4.64 +            LOG.info("About to launch WebView");
    4.65 +            Executors.newSingleThreadExecutor().submit(new Runnable() {
    4.66 +                @Override
    4.67 +                public void run() {
    4.68 +                    LOG.log(Level.INFO, "In FX thread. Launching!");
    4.69 +                    try {
    4.70 +                        FXBrwsr.launch(FXBrwsr.class, url.toString());
    4.71 +                        LOG.log(Level.INFO, "Launcher is back. Closing");
    4.72 +                        close();
    4.73 +                    } catch (Throwable ex) {
    4.74 +                        LOG.log(Level.WARNING, "Error launching Web View", ex);
    4.75 +                    }
    4.76 +                }
    4.77 +            });
    4.78 +        } catch (Throwable ex) {
    4.79 +            LOG.log(Level.WARNING, "Can't open WebView", ex);
    4.80 +        }
    4.81 +        return null;
    4.82 +    }
    4.83 +    
    4.84 +    @Override
    4.85 +    void generateBck2BrwsrJS(StringBuilder sb, Bck2Brwsr.Resources loader) throws IOException {
    4.86 +        sb.append("(function() {\n"
    4.87 +            + "  var impl = this.bck2brwsr;\n"
    4.88 +            + "  this.bck2brwsr = function() { return impl; };\n"
    4.89 +            + "})(window);\n"
    4.90 +        );
    4.91 +        JVMBridge.onBck2BrwsrLoad();
    4.92 +    }
    4.93 +    
    4.94 +    
    4.95 +    
    4.96 +    @Override
    4.97 +    public void close() throws IOException {
    4.98 +        super.close();
    4.99 +        Platform.exit();
   4.100 +    }
   4.101 +    
   4.102 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java	Sun Apr 28 17:42:49 2013 +0200
     5.3 @@ -0,0 +1,415 @@
     5.4 +/**
     5.5 + * Back 2 Browser Bytecode Translator
     5.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     5.7 + *
     5.8 + * This program is free software: you can redistribute it and/or modify
     5.9 + * it under the terms of the GNU General Public License as published by
    5.10 + * the Free Software Foundation, version 2 of the License.
    5.11 + *
    5.12 + * This program is distributed in the hope that it will be useful,
    5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    5.15 + * GNU General Public License for more details.
    5.16 + *
    5.17 + * You should have received a copy of the GNU General Public License
    5.18 + * along with this program. Look for COPYING file in the top folder.
    5.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    5.20 + */
    5.21 +package org.apidesign.bck2brwsr.launcher.fximpl;
    5.22 +
    5.23 +import java.io.IOException;
    5.24 +import java.io.InputStream;
    5.25 +import java.io.UnsupportedEncodingException;
    5.26 +import java.lang.reflect.InvocationTargetException;
    5.27 +import java.lang.reflect.Method;
    5.28 +import java.lang.reflect.Modifier;
    5.29 +import java.net.URL;
    5.30 +import java.util.Enumeration;
    5.31 +import javafx.scene.web.WebEngine;
    5.32 +import netscape.javascript.JSObject;
    5.33 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    5.34 +
    5.35 +/**
    5.36 + *
    5.37 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    5.38 + */
    5.39 +public final class Console {
    5.40 +    public Console() {
    5.41 +    }
    5.42 +    
    5.43 +    @JavaScriptBody(args = {"elem", "attr"}, body = 
    5.44 +        "return elem[attr].toString();")
    5.45 +    private static Object getAttr(Object elem, String attr) {
    5.46 +        return InvokeJS.CObject.call("getAttr", elem, attr);
    5.47 +    }
    5.48 +
    5.49 +    @JavaScriptBody(args = {"id", "attr", "value"}, body = 
    5.50 +        "window.document.getElementById(id)[attr] = value;")
    5.51 +    private static void setAttr(String id, String attr, Object value) {
    5.52 +        InvokeJS.CObject.call("setAttrId", id, attr, value);
    5.53 +    }
    5.54 +    @JavaScriptBody(args = {"elem", "attr", "value"}, body = 
    5.55 +        "elem[attr] = value;")
    5.56 +    private static void setAttr(Object id, String attr, Object value) {
    5.57 +        InvokeJS.CObject.call("setAttr", id, attr, value);
    5.58 +    }
    5.59 +    
    5.60 +    @JavaScriptBody(args = {}, body = "return; window.close();")
    5.61 +    private static void closeWindow() {}
    5.62 +
    5.63 +    private static Object textArea;
    5.64 +    private static Object statusArea;
    5.65 +    
    5.66 +    private static void log(String newText) {
    5.67 +        if (textArea == null) {
    5.68 +            return;
    5.69 +        }
    5.70 +        String attr = "value";
    5.71 +        setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText);
    5.72 +        setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight"));
    5.73 +    }
    5.74 +    
    5.75 +    private static void beginTest(Case c) {
    5.76 +        Object[] arr = new Object[2];
    5.77 +        beginTest(c.getClassName() + "." + c.getMethodName(), c, arr);
    5.78 +        textArea = arr[0];
    5.79 +        statusArea = arr[1];
    5.80 +    }
    5.81 +    
    5.82 +    private static void finishTest(Case c, Object res) {
    5.83 +        if ("null".equals(res)) {
    5.84 +            setAttr(statusArea, "innerHTML", "Success");
    5.85 +        } else {
    5.86 +            setAttr(statusArea, "innerHTML", "Result " + res);
    5.87 +        }
    5.88 +        statusArea = null;
    5.89 +        textArea = null;
    5.90 +    }
    5.91 +
    5.92 +    private static final String BEGIN_TEST =  
    5.93 +        "var ul = window.document.getElementById('bck2brwsr.result');\n"
    5.94 +        + "var li = window.document.createElement('li');\n"
    5.95 +        + "var span = window.document.createElement('span');"
    5.96 +        + "span.innerHTML = test + ' - ';\n"
    5.97 +        + "var details = window.document.createElement('a');\n"
    5.98 +        + "details.innerHTML = 'Details';\n"
    5.99 +        + "details.href = '#';\n"
   5.100 +        + "var p = window.document.createElement('p');\n"
   5.101 +        + "var status = window.document.createElement('a');\n"
   5.102 +        + "status.innerHTML = 'running';"
   5.103 +        + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n"
   5.104 +        + "status.onclick = function() { c.again(arr); }\n"
   5.105 +        + "var pre = window.document.createElement('textarea');\n"
   5.106 +        + "pre.cols = 100;"
   5.107 +        + "pre.rows = 10;"
   5.108 +        + "li.appendChild(span);\n"
   5.109 +        + "li.appendChild(status);\n"
   5.110 +        + "var span = window.document.createElement('span');"
   5.111 +        + "span.innerHTML = ' ';\n"
   5.112 +        + "li.appendChild(span);\n"
   5.113 +        + "li.appendChild(details);\n"
   5.114 +        + "p.appendChild(pre);\n"
   5.115 +        + "ul.appendChild(li);\n"
   5.116 +        + "arr[0] = pre;\n"
   5.117 +        + "arr[1] = status;\n";
   5.118 +        
   5.119 +    @JavaScriptBody(args = { "test", "c", "arr" }, body = BEGIN_TEST)
   5.120 +    private static void beginTest(String test, Case c, Object[] arr) {
   5.121 +        InvokeJS.CObject.call("beginTest", test, c, arr);
   5.122 +    }
   5.123 +    
   5.124 +    private static final String LOAD_TEXT = 
   5.125 +          "var request = new XMLHttpRequest();\n"
   5.126 +        + "request.open('GET', url, true);\n"
   5.127 +        + "request.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');\n"
   5.128 +        + "request.onreadystatechange = function() {\n"
   5.129 +        + "  if (this.readyState!==4) return;\n"
   5.130 +        + " try {"
   5.131 +        + "  arr[0] = this.responseText;\n"
   5.132 +        + "  callback.run__V();\n"
   5.133 +        + " } catch (e) { alert(e); }"
   5.134 +        + "};"
   5.135 +        + "request.send();";
   5.136 +    @JavaScriptBody(args = { "url", "callback", "arr" }, body = LOAD_TEXT)
   5.137 +    private static void loadText(String url, Runnable callback, String[] arr) throws IOException {
   5.138 +        InvokeJS.CObject.call("loadText", url, new Run(callback), arr);
   5.139 +    }
   5.140 +    
   5.141 +    public static void runHarness(String url) throws IOException {
   5.142 +        new Console().harness(url);
   5.143 +    }
   5.144 +    
   5.145 +    public void harness(String url) throws IOException {
   5.146 +        log("Connecting to " + url);
   5.147 +        Request r = new Request(url);
   5.148 +    }
   5.149 +    
   5.150 +    private static class Request implements Runnable {
   5.151 +        private final String[] arr = { null };
   5.152 +        private final String url;
   5.153 +        private Case c;
   5.154 +        private int retries;
   5.155 +
   5.156 +        private Request(String url) throws IOException {
   5.157 +            this.url = url;
   5.158 +            loadText(url, this, arr);
   5.159 +        }
   5.160 +        private Request(String url, String u) throws IOException {
   5.161 +            this.url = url;
   5.162 +            loadText(u, this, arr);
   5.163 +        }
   5.164 +        
   5.165 +        @Override
   5.166 +        public void run() {
   5.167 +            try {
   5.168 +                if (c == null) {
   5.169 +                    String data = arr[0];
   5.170 +
   5.171 +                    if (data == null) {
   5.172 +                        log("Some error exiting");
   5.173 +                        closeWindow();
   5.174 +                        return;
   5.175 +                    }
   5.176 +
   5.177 +                    if (data.isEmpty()) {
   5.178 +                        log("No data, exiting");
   5.179 +                        closeWindow();
   5.180 +                        return;
   5.181 +                    }
   5.182 +
   5.183 +                    c = Case.parseData(data);
   5.184 +                    beginTest(c);
   5.185 +                    log("Got \"" + data + "\"");
   5.186 +                } else {
   5.187 +                    log("Processing \"" + arr[0] + "\" for " + retries + " time");
   5.188 +                }
   5.189 +                Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest();
   5.190 +                finishTest(c, result);
   5.191 +                
   5.192 +                String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   5.193 +                new Request(url, u);
   5.194 +            } catch (Exception ex) {
   5.195 +                if (ex instanceof InterruptedException) {
   5.196 +                    log("Re-scheduling in 100ms");
   5.197 +                    schedule(this, 100);
   5.198 +                    return;
   5.199 +                }
   5.200 +                log(ex.getClass().getName() + ":" + ex.getMessage());
   5.201 +            }
   5.202 +        }
   5.203 +    }
   5.204 +    
   5.205 +    private static String encodeURL(String r) throws UnsupportedEncodingException {
   5.206 +        final String SPECIAL = "%$&+,/:;=?@";
   5.207 +        StringBuilder sb = new StringBuilder();
   5.208 +        byte[] utf8 = r.getBytes("UTF-8");
   5.209 +        for (int i = 0; i < utf8.length; i++) {
   5.210 +            int ch = utf8[i] & 0xff;
   5.211 +            if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) {
   5.212 +                final String numbers = "0" + Integer.toHexString(ch);
   5.213 +                sb.append("%").append(numbers.substring(numbers.length() - 2));
   5.214 +            } else {
   5.215 +                if (ch == 32) {
   5.216 +                    sb.append("+");
   5.217 +                } else {
   5.218 +                    sb.append((char)ch);
   5.219 +                }
   5.220 +            }
   5.221 +        }
   5.222 +        return sb.toString();
   5.223 +    }
   5.224 +    
   5.225 +    static String invoke(String clazz, String method) throws 
   5.226 +    ClassNotFoundException, InvocationTargetException, IllegalAccessException, 
   5.227 +    InstantiationException, InterruptedException {
   5.228 +        final Object r = new Case(null).invokeMethod(clazz, method);
   5.229 +        return r == null ? "null" : r.toString().toString();
   5.230 +    }
   5.231 +
   5.232 +    /** Helper method that inspects the classpath and loads given resource
   5.233 +     * (usually a class file). Used while running tests in Rhino.
   5.234 +     * 
   5.235 +     * @param name resource name to find
   5.236 +     * @return the array of bytes in the given resource
   5.237 +     * @throws IOException I/O in case something goes wrong
   5.238 +     */
   5.239 +    public static byte[] read(String name) throws IOException {
   5.240 +        URL u = null;
   5.241 +        Enumeration<URL> en = Console.class.getClassLoader().getResources(name);
   5.242 +        while (en.hasMoreElements()) {
   5.243 +            u = en.nextElement();
   5.244 +        }
   5.245 +        if (u == null) {
   5.246 +            throw new IOException("Can't find " + name);
   5.247 +        }
   5.248 +        try (InputStream is = u.openStream()) {
   5.249 +            byte[] arr;
   5.250 +            arr = new byte[is.available()];
   5.251 +            int offset = 0;
   5.252 +            while (offset < arr.length) {
   5.253 +                int len = is.read(arr, offset, arr.length - offset);
   5.254 +                if (len == -1) {
   5.255 +                    throw new IOException("Can't read " + name);
   5.256 +                }
   5.257 +                offset += len;
   5.258 +            }
   5.259 +            return arr;
   5.260 +        }
   5.261 +    }
   5.262 +   
   5.263 +    @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;")
   5.264 +    private static void turnAssetionStatusOn() {
   5.265 +    }
   5.266 +
   5.267 +    @JavaScriptBody(args = {"r", "time"}, body =
   5.268 +        "return window.setTimeout(function() { r.run__V(); }, time);")
   5.269 +    private static Object schedule(Runnable r, int time) {
   5.270 +        return InvokeJS.CObject.call("schedule", new Run(r), time);
   5.271 +    }
   5.272 +    
   5.273 +    private static final class Case {
   5.274 +        private final Object data;
   5.275 +        private Object inst;
   5.276 +
   5.277 +        private Case(Object data) {
   5.278 +            this.data = data;
   5.279 +        }
   5.280 +        
   5.281 +        public static Case parseData(String s) {
   5.282 +            return new Case(toJSON(s));
   5.283 +        }
   5.284 +        
   5.285 +        public String getMethodName() {
   5.286 +            return (String) value("methodName", data);
   5.287 +        }
   5.288 +
   5.289 +        public String getClassName() {
   5.290 +            return (String) value("className", data);
   5.291 +        }
   5.292 +        
   5.293 +        public int getRequestId() {
   5.294 +            Object v = value("request", data);
   5.295 +            if (v instanceof Number) {
   5.296 +                return ((Number)v).intValue();
   5.297 +            }
   5.298 +            return Integer.parseInt(v.toString());
   5.299 +        }
   5.300 +
   5.301 +        public String getHtmlFragment() {
   5.302 +            return (String) value("html", data);
   5.303 +        }
   5.304 +        
   5.305 +        void again(Object[] arr) {
   5.306 +            try {
   5.307 +                textArea = arr[0];
   5.308 +                statusArea = arr[1];
   5.309 +                setAttr(textArea, "value", "");
   5.310 +                runTest();
   5.311 +            } catch (Exception ex) {
   5.312 +                log(ex.getClass().getName() + ":" + ex.getMessage());
   5.313 +            }
   5.314 +        }
   5.315 +
   5.316 +        private Object runTest() throws IllegalAccessException, 
   5.317 +        IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, 
   5.318 +        InvocationTargetException, InstantiationException, InterruptedException {
   5.319 +            if (this.getHtmlFragment() != null) {
   5.320 +                setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment());
   5.321 +            }
   5.322 +            log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId());
   5.323 +            Object result = invokeMethod(this.getClassName(), this.getMethodName());
   5.324 +            setAttr("bck2brwsr.fragment", "innerHTML", "");
   5.325 +            log("Result: " + result);
   5.326 +            result = encodeURL("" + result);
   5.327 +            log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result);
   5.328 +            return result;
   5.329 +        }
   5.330 +
   5.331 +        private Object invokeMethod(String clazz, String method)
   5.332 +        throws ClassNotFoundException, InvocationTargetException,
   5.333 +        InterruptedException, IllegalAccessException, IllegalArgumentException,
   5.334 +        InstantiationException {
   5.335 +            Method found = null;
   5.336 +            Class<?> c = Class.forName(clazz);
   5.337 +            for (Method m : c.getMethods()) {
   5.338 +                if (m.getName().equals(method)) {
   5.339 +                    found = m;
   5.340 +                }
   5.341 +            }
   5.342 +            Object res;
   5.343 +            if (found != null) {
   5.344 +                try {
   5.345 +                    if ((found.getModifiers() & Modifier.STATIC) != 0) {
   5.346 +                        res = found.invoke(null);
   5.347 +                    } else {
   5.348 +                        if (inst == null) {
   5.349 +                            inst = c.newInstance();
   5.350 +                        }
   5.351 +                        res = found.invoke(inst);
   5.352 +                    }
   5.353 +                } catch (Throwable ex) {
   5.354 +                    if (ex instanceof InvocationTargetException) {
   5.355 +                        ex = ((InvocationTargetException) ex).getTargetException();
   5.356 +                    }
   5.357 +                    if (ex instanceof InterruptedException) {
   5.358 +                        throw (InterruptedException)ex;
   5.359 +                    }
   5.360 +                    res = ex.getClass().getName() + ":" + ex.getMessage();
   5.361 +                }
   5.362 +            } else {
   5.363 +                res = "Can't find method " + method + " in " + clazz;
   5.364 +            }
   5.365 +            return res;
   5.366 +        }
   5.367 +        
   5.368 +        @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');")
   5.369 +        private static Object toJSON(String s) {
   5.370 +            return InvokeJS.CObject.call("toJSON", s);
   5.371 +        }
   5.372 +        
   5.373 +        @JavaScriptBody(args = {"p", "d"}, body = 
   5.374 +              "var v = d[p];\n"
   5.375 +            + "if (typeof v === 'undefined') return null;\n"
   5.376 +            + "return v.toString();"
   5.377 +        )
   5.378 +        private static Object value(String p, Object d) {
   5.379 +            return ((JSObject)d).getMember(p);
   5.380 +        }
   5.381 +    }
   5.382 +    
   5.383 +    private static String safe(String txt) {
   5.384 +        return "try {" + txt + "} catch (err) { alert(err); }";
   5.385 +    }
   5.386 +    
   5.387 +    static {
   5.388 +        turnAssetionStatusOn();
   5.389 +    }
   5.390 +    
   5.391 +    private static final class InvokeJS {
   5.392 +        static final JSObject CObject = initJS();
   5.393 +
   5.394 +        @JavaScriptBody(args = {  }, body = "return null;")
   5.395 +        private static JSObject initJS() {
   5.396 +            WebEngine web = (WebEngine) System.getProperties().get("webEngine");
   5.397 +            return (JSObject) web.executeScript("(function() {"
   5.398 +                + "var CObject = {};"
   5.399 +
   5.400 +                + "CObject.getAttr = function(elem, attr) { return elem[attr].toString(); };"
   5.401 +
   5.402 +                + "CObject.setAttrId = function(id, attr, value) { window.document.getElementById(id)[attr] = value; };"
   5.403 +                + "CObject.setAttr = function(elem, attr, value) { elem[attr] = value; };"
   5.404 +
   5.405 +                + "CObject.beginTest = function(test, c, arr) {" + safe(BEGIN_TEST) + "};"
   5.406 +
   5.407 +                + "CObject.loadText = function(url, callback, arr) {" + safe(LOAD_TEXT.replace("run__V", "run")) + "};"
   5.408 +
   5.409 +                + "CObject.schedule = function(r, time) { return window.setTimeout(function() { r.run(); }, time); };"
   5.410 +
   5.411 +                + "CObject.toJSON = function(s) { return eval('(' + s + ')'); };"
   5.412 +
   5.413 +                + "return CObject;"
   5.414 +            + "})(this)");
   5.415 +        }
   5.416 +    }
   5.417 +    
   5.418 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java	Sun Apr 28 17:42:49 2013 +0200
     6.3 @@ -0,0 +1,184 @@
     6.4 +/**
     6.5 + * Back 2 Browser Bytecode Translator
     6.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     6.7 + *
     6.8 + * This program is free software: you can redistribute it and/or modify
     6.9 + * it under the terms of the GNU General Public License as published by
    6.10 + * the Free Software Foundation, version 2 of the License.
    6.11 + *
    6.12 + * This program is distributed in the hope that it will be useful,
    6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.15 + * GNU General Public License for more details.
    6.16 + *
    6.17 + * You should have received a copy of the GNU General Public License
    6.18 + * along with this program. Look for COPYING file in the top folder.
    6.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    6.20 + */
    6.21 +package org.apidesign.bck2brwsr.launcher.fximpl;
    6.22 +
    6.23 +import java.util.List;
    6.24 +import java.util.TooManyListenersException;
    6.25 +import java.util.logging.Level;
    6.26 +import java.util.logging.Logger;
    6.27 +import javafx.application.Application;
    6.28 +import javafx.application.Platform;
    6.29 +import javafx.beans.value.ChangeListener;
    6.30 +import javafx.beans.value.ObservableValue;
    6.31 +import javafx.event.ActionEvent;
    6.32 +import javafx.event.EventHandler;
    6.33 +import javafx.geometry.HPos;
    6.34 +import javafx.geometry.Insets;
    6.35 +import javafx.geometry.Pos;
    6.36 +import javafx.geometry.VPos;
    6.37 +import javafx.scene.Node;
    6.38 +import javafx.scene.Scene;
    6.39 +import javafx.scene.control.Button;
    6.40 +import javafx.scene.layout.ColumnConstraints;
    6.41 +import javafx.scene.layout.GridPane;
    6.42 +import javafx.scene.layout.Pane;
    6.43 +import javafx.scene.layout.Priority;
    6.44 +import javafx.scene.layout.VBox;
    6.45 +import javafx.scene.text.Text;
    6.46 +import javafx.scene.web.WebEngine;
    6.47 +import javafx.scene.web.WebEvent;
    6.48 +import javafx.scene.web.WebView;
    6.49 +import javafx.stage.Modality;
    6.50 +import javafx.stage.Stage;
    6.51 +import netscape.javascript.JSObject;
    6.52 +
    6.53 +/**
    6.54 + * Demonstrates a WebView object accessing a web page.
    6.55 + *
    6.56 + * @see javafx.scene.web.WebView
    6.57 + * @see javafx.scene.web.WebEngine
    6.58 + */
    6.59 +public class FXBrwsr extends Application {
    6.60 +    private static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName());
    6.61 +
    6.62 +    @Override
    6.63 +    public void start(Stage primaryStage) throws Exception {
    6.64 +        Pane root = new WebViewPane(getParameters().getUnnamed());
    6.65 +        primaryStage.setScene(new Scene(root, 1024, 768));
    6.66 +        LOG.info("Showing the stage");
    6.67 +        primaryStage.show();
    6.68 +        LOG.log(Level.INFO, "State shown: {0}", primaryStage.isShowing());
    6.69 +    }
    6.70 +    
    6.71 +    /**
    6.72 +     * Create a resizable WebView pane
    6.73 +     */
    6.74 +    private class WebViewPane extends Pane {
    6.75 +        private final JVMBridge bridge = new JVMBridge();
    6.76 +
    6.77 +        public WebViewPane(List<String> params) {
    6.78 +            LOG.log(Level.INFO, "Initializing WebView with {0}", params);
    6.79 +            VBox.setVgrow(this, Priority.ALWAYS);
    6.80 +            setMaxWidth(Double.MAX_VALUE);
    6.81 +            setMaxHeight(Double.MAX_VALUE);
    6.82 +            WebView view = new WebView();
    6.83 +            view.setMinSize(500, 400);
    6.84 +            view.setPrefSize(500, 400);
    6.85 +            final WebEngine eng = view.getEngine();
    6.86 +            try {
    6.87 +                JVMBridge.addBck2BrwsrLoad(new InitBck2Brwsr(eng));
    6.88 +            } catch (TooManyListenersException ex) {
    6.89 +                LOG.log(Level.SEVERE, null, ex);
    6.90 +            }
    6.91 +            
    6.92 +            if (params.size() > 0) {
    6.93 +                LOG.log(Level.INFO, "loading page {0}", params.get(0));
    6.94 +                eng.load(params.get(0));
    6.95 +                LOG.fine("back from load");
    6.96 +            }
    6.97 +            eng.setOnAlert(new EventHandler<WebEvent<String>>() {
    6.98 +                @Override
    6.99 +                public void handle(WebEvent<String> t) {
   6.100 +                    final Stage dialogStage = new Stage();
   6.101 +                    dialogStage.initModality(Modality.WINDOW_MODAL);
   6.102 +                    dialogStage.setTitle("Warning");
   6.103 +                    final Button button = new Button("Close");
   6.104 +                    final Text text = new Text(t.getData());
   6.105 +                    
   6.106 +                    VBox box = new VBox();
   6.107 +                    box.setAlignment(Pos.CENTER);
   6.108 +                    box.setSpacing(10);
   6.109 +                    box.setPadding(new Insets(10));
   6.110 +                    box.getChildren().addAll(text, button);
   6.111 +                    
   6.112 +                    dialogStage.setScene(new Scene(box));
   6.113 +                    
   6.114 +                    button.setCancelButton(true);
   6.115 +                    button.setOnAction(new EventHandler<ActionEvent>() {
   6.116 +                        @Override
   6.117 +                        public void handle(ActionEvent t) {
   6.118 +                            dialogStage.close();
   6.119 +                        }
   6.120 +                    });
   6.121 +                    
   6.122 +                    dialogStage.centerOnScreen();
   6.123 +                    dialogStage.showAndWait();
   6.124 +                }
   6.125 +            });
   6.126 +            GridPane grid = new GridPane();
   6.127 +            grid.setVgap(5);
   6.128 +            grid.setHgap(5);
   6.129 +            GridPane.setConstraints(view, 0, 1, 2, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS);
   6.130 +            grid.getColumnConstraints().addAll(new ColumnConstraints(100, 100, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true), new ColumnConstraints(40, 40, 40, Priority.NEVER, HPos.CENTER, true));
   6.131 +            grid.getChildren().addAll(view);
   6.132 +            getChildren().add(grid);
   6.133 +        }
   6.134 +
   6.135 +        boolean initBck2Brwsr(WebEngine webEngine) {
   6.136 +            JSObject jsobj = (JSObject) webEngine.executeScript("window");
   6.137 +            LOG.log(Level.FINE, "window: {0}", jsobj);
   6.138 +            Object prev = jsobj.getMember("bck2brwsr");
   6.139 +            if ("undefined".equals(prev)) {
   6.140 +                System.getProperties().put("webEngine", webEngine);
   6.141 +                jsobj.setMember("bck2brwsr", bridge);
   6.142 +                return true;
   6.143 +            }
   6.144 +            return false;
   6.145 +        }
   6.146 +
   6.147 +        @Override
   6.148 +        protected void layoutChildren() {
   6.149 +            List<Node> managed = getManagedChildren();
   6.150 +            double width = getWidth();
   6.151 +            double height = getHeight();
   6.152 +            double top = getInsets().getTop();
   6.153 +            double right = getInsets().getRight();
   6.154 +            double left = getInsets().getLeft();
   6.155 +            double bottom = getInsets().getBottom();
   6.156 +            for (int i = 0; i < managed.size(); i++) {
   6.157 +                Node child = managed.get(i);
   6.158 +                layoutInArea(child, left, top, width - left - right, height - top - bottom, 0, Insets.EMPTY, true, true, HPos.CENTER, VPos.CENTER);
   6.159 +            }
   6.160 +        }
   6.161 +
   6.162 +        private class InitBck2Brwsr implements ChangeListener<Void>, Runnable {
   6.163 +            private final WebEngine eng;
   6.164 +
   6.165 +            public InitBck2Brwsr(WebEngine eng) {
   6.166 +                this.eng = eng;
   6.167 +            }
   6.168 +
   6.169 +            @Override
   6.170 +            public synchronized void changed(ObservableValue<? extends Void> ov, Void t, Void t1) {
   6.171 +                Platform.runLater(this);
   6.172 +                try {
   6.173 +                    wait();
   6.174 +                } catch (InterruptedException ex) {
   6.175 +                    LOG.log(Level.SEVERE, null, ex);
   6.176 +                }
   6.177 +            }
   6.178 +
   6.179 +            @Override
   6.180 +            public synchronized void run() {
   6.181 +                initBck2Brwsr(eng);
   6.182 +                notifyAll();
   6.183 +            }
   6.184 +        }
   6.185 +    }
   6.186 +    
   6.187 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java	Sun Apr 28 17:42:49 2013 +0200
     7.3 @@ -0,0 +1,64 @@
     7.4 +/**
     7.5 + * Back 2 Browser Bytecode Translator
     7.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     7.7 + *
     7.8 + * This program is free software: you can redistribute it and/or modify
     7.9 + * it under the terms of the GNU General Public License as published by
    7.10 + * the Free Software Foundation, version 2 of the License.
    7.11 + *
    7.12 + * This program is distributed in the hope that it will be useful,
    7.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.15 + * GNU General Public License for more details.
    7.16 + *
    7.17 + * You should have received a copy of the GNU General Public License
    7.18 + * along with this program. Look for COPYING file in the top folder.
    7.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    7.20 + */
    7.21 +package org.apidesign.bck2brwsr.launcher.fximpl;
    7.22 +
    7.23 +import java.util.TooManyListenersException;
    7.24 +import javafx.beans.value.ChangeListener;
    7.25 +
    7.26 +/**
    7.27 + *
    7.28 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.29 + */
    7.30 +public final class JVMBridge {
    7.31 +    private static ClassLoader[] ldrs;
    7.32 +    private static ChangeListener<Void> onBck2BrwsrLoad;
    7.33 +        
    7.34 +    public static void registerClassLoaders(ClassLoader[] loaders) {
    7.35 +        ldrs = loaders.clone();
    7.36 +    }
    7.37 +    
    7.38 +    public static void addBck2BrwsrLoad(ChangeListener<Void> l) throws TooManyListenersException {
    7.39 +        if (onBck2BrwsrLoad != null) {
    7.40 +            throw new TooManyListenersException();
    7.41 +        }
    7.42 +        onBck2BrwsrLoad = l;
    7.43 +    }
    7.44 +
    7.45 +    public static void onBck2BrwsrLoad() {
    7.46 +        ChangeListener<Void> l = onBck2BrwsrLoad;
    7.47 +        if (l != null) {
    7.48 +            l.changed(null, null, null);
    7.49 +        }
    7.50 +    }
    7.51 +    
    7.52 +    public Class<?> loadClass(String name) throws ClassNotFoundException {
    7.53 +        System.err.println("trying to load " + name);
    7.54 +        ClassNotFoundException ex = null;
    7.55 +        if (ldrs != null) for (ClassLoader l : ldrs) {
    7.56 +            try {
    7.57 +                return Class.forName(name, true, l);
    7.58 +            } catch (ClassNotFoundException ex2) {
    7.59 +                ex = ex2;
    7.60 +            }
    7.61 +        }
    7.62 +        if (ex == null) {
    7.63 +            ex = new ClassNotFoundException("No loaders");
    7.64 +        }
    7.65 +        throw ex;
    7.66 +    }
    7.67 +}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Run.java	Sun Apr 28 17:42:49 2013 +0200
     8.3 @@ -0,0 +1,35 @@
     8.4 +/**
     8.5 + * Back 2 Browser Bytecode Translator
     8.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     8.7 + *
     8.8 + * This program is free software: you can redistribute it and/or modify
     8.9 + * it under the terms of the GNU General Public License as published by
    8.10 + * the Free Software Foundation, version 2 of the License.
    8.11 + *
    8.12 + * This program is distributed in the hope that it will be useful,
    8.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    8.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    8.15 + * GNU General Public License for more details.
    8.16 + *
    8.17 + * You should have received a copy of the GNU General Public License
    8.18 + * along with this program. Look for COPYING file in the top folder.
    8.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    8.20 + */
    8.21 +
    8.22 +package org.apidesign.bck2brwsr.launcher.fximpl;
    8.23 +
    8.24 +/**
    8.25 + *
    8.26 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.27 + */
    8.28 +public final class Run implements Runnable {
    8.29 +    private final Runnable r;
    8.30 +    Run(Runnable r) {
    8.31 +        this.r = r;
    8.32 +    }
    8.33 +
    8.34 +    @Override
    8.35 +    public void run() {
    8.36 +        r.run();
    8.37 +    }
    8.38 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/harness.xhtml	Sun Apr 28 17:42:49 2013 +0200
     9.3 @@ -0,0 +1,52 @@
     9.4 +<?xml version="1.0" encoding="UTF-8"?>
     9.5 +<!--
     9.6 +
     9.7 +    Back 2 Browser Bytecode Translator
     9.8 +    Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     9.9 +
    9.10 +    This program is free software: you can redistribute it and/or modify
    9.11 +    it under the terms of the GNU General Public License as published by
    9.12 +    the Free Software Foundation, version 2 of the License.
    9.13 +
    9.14 +    This program is distributed in the hope that it will be useful,
    9.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    9.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    9.17 +    GNU General Public License for more details.
    9.18 +
    9.19 +    You should have received a copy of the GNU General Public License
    9.20 +    along with this program. Look for COPYING file in the top folder.
    9.21 +    If not, see http://opensource.org/licenses/GPL-2.0.
    9.22 +
    9.23 +-->
    9.24 +<!DOCTYPE html>
    9.25 +<html xmlns="http://www.w3.org/1999/xhtml">
    9.26 +    <head>
    9.27 +        <title>Bck2Brwsr Harness</title>
    9.28 +    </head>
    9.29 +    <body>
    9.30 +        <script src="/bck2brwsr.js"></script>
    9.31 +        <script>
    9.32 +            var vm = bck2brwsr();
    9.33 +        </script>
    9.34 +        
    9.35 +        <h1>Bck2Brwsr Execution Harness</h1>
    9.36 +        
    9.37 +        <ul id="bck2brwsr.result" style="width: 100%;" >
    9.38 +        </ul>
    9.39 +
    9.40 +        <div id="bck2brwsr.fragment"/>
    9.41 +        
    9.42 +        <script type="text/javascript">
    9.43 +            try {
    9.44 +                (function() {
    9.45 +                    var cls = vm.loadClass('org.apidesign.bck2brwsr.launcher.fximpl.Console');
    9.46 +                    // fxbrwsr mangling
    9.47 +                    var inst = cls.newInstance();
    9.48 +                    inst.harness('$U/../data');
    9.49 +                })();
    9.50 +            } catch (err) { 
    9.51 +                alert('Error executing harness: ' + err); 
    9.52 +            }
    9.53 +        </script>
    9.54 +    </body>
    9.55 +</html>
    10.1 --- a/launcher/pom.xml	Sun Apr 28 17:11:12 2013 +0200
    10.2 +++ b/launcher/pom.xml	Sun Apr 28 17:42:49 2013 +0200
    10.3 @@ -14,5 +14,6 @@
    10.4    <modules>
    10.5      <module>api</module>
    10.6      <module>http</module>
    10.7 +    <module>fx</module>
    10.8    </modules>
    10.9  </project>
   10.10 \ No newline at end of file