Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TIMOB-5123 Use Rhino native require() CommonJS implementation #493

Merged
merged 12 commits into from
Sep 28, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file modified android/titanium/lib/smalljs.jar
Binary file not shown.
23 changes: 23 additions & 0 deletions android/titanium/src/org/appcelerator/titanium/TiScriptRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import org.appcelerator.titanium.util.Log;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeFunction;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
Expand Down Expand Up @@ -86,6 +87,28 @@ public void setAppPackageName(String packageName)
appPackageName = packageName;
}

public Script getScript(Context context, Scriptable scope, String relativePath)
throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
String scriptClassName = getScriptClassName(relativePath);
TiScript tiScript = scripts.get(scriptClassName);
if (tiScript != null) {
return tiScript.script;
}
Class<?> scriptClass = Class.forName(scriptClassName);
tiScript = new TiScript();
tiScript.context = context;
tiScript.scope = scope;
if (scriptClass != null) {
Object scriptObj = scriptClass.newInstance();
tiScript.script = (Script) scriptObj;
tiScript.name = scriptClass.getName();
scripts.put(tiScript.name, tiScript);
return tiScript.script;
} else {
throw new ClassNotFoundException("CommonJS module class for \"" + relativePath + "\" not found.");
}
}

public Object runScript(Context context, Scriptable scope, String relativePath)
throws ClassNotFoundException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,20 @@
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.commonjs.module.ModuleScript;
import org.mozilla.javascript.commonjs.module.ModuleScriptProvider;
import org.mozilla.javascript.commonjs.module.Require;
import org.mozilla.javascript.commonjs.module.RequireBuilder;

import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.os.Process;

public class KrollContext implements Handler.Callback
public class KrollContext implements Handler.Callback, ModuleScriptProvider
{
private static final String LCAT = "KrollContext";
private static boolean DBG = TiConfig.DEBUG;
Expand All @@ -54,6 +59,7 @@ public class KrollContext implements Handler.Callback
private static KrollThreadListener threadListener;

private KrollHandlerThread thread;
private Require commonJsRequire;
private TiContext tiContext;
private ScriptableObject jsScope;
private String sourceUrl;
Expand Down Expand Up @@ -189,12 +195,78 @@ protected void initContext()
if (DBG) {
Log.d(LCAT, "Initialized scope: " + jsScope);
}
this.commonJsRequire = buildCommonJsRequire(ctx);
if (DBG) {
Log.d(LCAT, "Initialized commonJS require() function: " + this.commonJsRequire);
}
initialized.countDown();
} finally {
exit();
}
}

private Require buildCommonJsRequire(Context ctx)
{
RequireBuilder builder = new RequireBuilder();
builder.setModuleScriptProvider(this);
return builder.createRequire(ctx, jsScope);
}

@Override
public ModuleScript getModuleScript(Context context, String moduleId,
Scriptable paths) throws Exception
{
Script script = null;
String uri;

StringBuilder sb = new StringBuilder();
sb.append(TiC.URL_APP_PREFIX)
.append(moduleId)
.append(".js");
uri = sb.toString();

if (useOptimization) {
// get Script from compiled script class
script = TiScriptRunner.getInstance()
.getScript(context, jsScope, moduleId);
if (script == null) {
Log.e(LCAT, "Could not retrieve a Script object for module '" + moduleId + "'.");
Context.throwAsScriptRuntimeEx(new Exception("Unable to load Script for module '" + moduleId + "'."));
}
} else {
// make Script from JS source
TiBaseFile file = TiFileFactory.createTitaniumFile(tiContext, new String[] { uri }, false);
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(file.getInputStream()), 4000);
script = context.compileReader(br, uri, 1, null);
} catch (IOException e) {
Log.e(LCAT, "IOException reading module file: " + uri, e);
Context.throwAsScriptRuntimeEx(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// Ignore
}
}
}
}

return new ModuleScript(script, uri);
}

public Object callCommonJsRequire(String path)
{
Context ctx = enter();
try {
return commonJsRequire.call(ctx, jsScope, jsScope, new String[] { path });
} finally {
exit();
}
}

protected void threadEnded()
{
if (threadListener != null) {
Expand Down
78 changes: 14 additions & 64 deletions android/titanium/src/ti/modules/titanium/TitaniumModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,9 @@
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.titanium.TiApplication;
import org.appcelerator.titanium.TiBaseActivity;
import org.appcelerator.titanium.TiBlob;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.TiContext;
import org.appcelerator.titanium.TiLaunchActivity;
import org.appcelerator.titanium.io.TiBaseFile;
import org.appcelerator.titanium.io.TiFileFactory;
import org.appcelerator.titanium.kroll.KrollCallback;
import org.appcelerator.titanium.kroll.KrollContext;
import org.appcelerator.titanium.kroll.KrollCoverage;
Expand Down Expand Up @@ -424,7 +421,7 @@ protected KrollModule requireNativeModule(TiContext context, String path)
}

@Kroll.method @Kroll.topLevel
public KrollProxy require(KrollInvocation invocation, String path)
public Object require(KrollInvocation invocation, String path)
{
// 1. look for a native module first
// 2. then look for a cached module
Expand All @@ -443,71 +440,24 @@ public KrollProxy require(KrollInvocation invocation, String path)
return module;
}

// NOTE: CommonJS modules load absolute to app:// in Titanium
builder.setLength(0);
builder.append(TiC.URL_APP_PREFIX)
.append(path)
.append(".js");
String fileUrl = builder.toString();
TiBaseFile tbf = TiFileFactory.createTitaniumFile(ctx, new String[]{ fileUrl }, false);
if (tbf == null) {
//the spec says we are required to throw an exception
Context.reportError("Couldn't find module: " + path);
return null;
}

if (DBG) {
Log.d(LCAT, "Attempting to include JS module: " + tbf.nativePath());
Log.d(LCAT, "Attempting to include CommonJS module: " + path);
}
try {
TiBlob blob = (TiBlob) tbf.read();
if (blob == null) {
Log.e(LCAT, "Couldn't read required file: " + fileUrl);
return null;
}

// TODO: we need to switch to the Rhino native require()
// implementation, but in the meantime this will have to do

// create the CommonJS exporter
KrollProxy proxy = new KrollProxy(ctx);
try {
return ctx.getKrollContext().callCommonJsRequire(path);
} catch (Exception e) {
builder.setLength(0);
builder.append("(function(exports){")
.append(blob.getText())
.append("return exports;")
.append("})({})");

Object result = ctx.evalJS(builder.toString());

if (!(result instanceof Scriptable)) {
builder.setLength(0);
builder.append("Module did not correctly return an exports object: ")
.append(path)
.append(", result: ")
.append(result);
Context.throwAsScriptRuntimeEx(new Exception(builder.toString()));
return null;
}

Scriptable exports = (Scriptable) result;
// CommonJS modules export all functions/properties as
// properties of the special exports object provided
for (Object key : exports.getIds()) {
String propName = key.toString();
proxy.setProperty(propName, exports.get(propName, exports));
}

// spec says you must have a read-only id property - we don't
// currently support readonly in kroll so this is probably OK for now
proxy.setProperty(TiC.PROPERTY_ID, path);
// uri is optional but we point it to where we loaded it
proxy.setProperty(TiC.PROPERTY_URI, fileUrl);
return proxy;
} catch (Exception ex) {
Log.e(LCAT, "Error loading module named: " + path, ex);
Context.throwAsScriptRuntimeEx(ex);
return null;
builder.append("require(\"")
.append(path)
.append("\") failed: ")
.append(e.getMessage());
String msg = builder.toString();
Log.e(LCAT, msg, e);
Context.throwAsScriptRuntimeEx(new Exception(msg));
}

return Context.throwAsScriptRuntimeEx(new Exception("Cannot find module '" + path + "'"));
}

@Kroll.method
Expand Down