Wednesday, 23 January 2013

Dojo: bootstrapping in an embedded Rhino context

Dojo supports Mozilla's Rhino engine for things like headless DOH tests and builds. This post demonstrates how to bootstrap Dojo using an embedded context.

This information pertains to Dojo 1.7.2, Rhino 1.7R2 and Java 6. Knowledge of JavaScript, Dojo and Java is assumed.

Dojo in the Rhino shell

The command java -cp js.jar org.mozilla.javascript.tools.shell.Main ./dojo/dojo/dojo.js baseUrl=./dojo/dojo load=doh results in the output:


------------------------------------------------------------
The Dojo Unit Test Harness, $Rev: 23869 $
Copyright (c) 2011, The Dojo Foundation, All Rights Reserved
------------------------------------------------------------ 

0 tests to run in 0 groups
------------------------------------------------------------
| TEST SUMMARY:
------------------------------------------------------------
  0 tests in 0 groups
  0 errors
  0 failures

This isn't particularly useful but it demonstrates that Dojo loaded without error.

Embedded Rhino

Embedding Rhino in a Java application gives you more programmatic control over JavaScript execution than would be the case if you just relied on the shell.

A trivial Rhino application:

import org.mozilla.javascript.*;

public class HelloEmbedded {

  public static void main(String[] args) {
    String source = "java.lang.System.out.println('Hello, Rhino!');";
    Context context = new ContextFactory().enterContext();
    ScriptableObject scope = context.initStandardObjects();
    context.evaluateString(scope, source, "<cmd>", 1, null);
  }

}

This code initializes the Rhino engine and uses JavaScript to print a string to STDOUT.

Embedded Dojo

Simply trying to load Dojo won't work:

// don't use
public class BrokenBootstrap {

  public static void main(String[] args) throws IOException {
    String baseUrl = "./dojo/dojo";
    String dojo = baseUrl + "/dojo.js";
    Object[] dojoArgs = { "baseUrl=" + baseUrl, "load=doh" };

    ContextFactory factory = new ContextFactory();
    Context context = factory.enterContext();
    ScriptableObject scope = context.initStandardObjects();
    Scriptable arguments = context.newArray(scope, dojoArgs);
    scope.defineProperty("arguments", arguments, ScriptableObject.DONTENUM);

    Reader reader = new FileReader(dojo);
    try {
      context.evaluateReader(scope, reader, dojo, 1, null);
    } finally {
      reader.close();
    }
  }

}

Rhino throws the exception Exception in thread "main" org.mozilla.javascript.EcmaError: ReferenceError: "location" is not defined. (./dojo/dojo/dojo.js#237). This is because Dojo attempts to use browser features because it has failed to detect that it is running in Rhino.

Dojo relies on features provided by the shell that are not present in an embedded context to detect the environment and load and execute modules. The simplest way round this is to reuse the Global type that provides these features to the shell:

import java.io.*;
import org.mozilla.javascript.*;
import org.mozilla.javascript.tools.shell.Global;

public class Bootstrap {

  public static void main(String[] args) throws IOException {
    String baseUrl = "./dojo/dojo";
    String dojo = baseUrl + "/dojo.js";
    Object[] dojoArgs = { "baseUrl=" + baseUrl, "load=doh" };

    ContextFactory factory = new ContextFactory();
    Context context = factory.enterContext();
    Global global = new Global();
    global.init(context);
    Scriptable arguments = context.newArray(global, dojoArgs);
    global.defineProperty("arguments", arguments, ScriptableObject.DONTENUM);

    Reader reader = new FileReader(dojo);
    try {
      context.evaluateReader(global, reader, dojo, 1, null);
    } finally {
      reader.close();
    }
  }

}

This gives us the same DOH bootstrap message from the shell command. There is likely a bit more work involved if you want to do something useful.

No comments:

Post a Comment

All comments are moderated