Revision: 3654
Author: maoziqing
Date: Fri Aug 21 10:14:19 2009
Log: Dynamic module loading for Cajita and Valija.
http://code.google.com/p/google-caja/source/detail?r=3654
Added:
/trunk/src/com/google/caja/cajita-module.js
/trunk/src/com/google/caja/cajita-promise.js
/trunk/tests/com/google/caja/a.js
/trunk/tests/com/google/caja/c.js
/trunk/tests/com/google/caja/foo
/trunk/tests/com/google/caja/foo/b.js
/trunk/tests/com/google/caja/parser/quasiliteral/x.js
/trunk/tests/com/google/caja/recursion.js
/trunk/tests/com/google/caja/x.js
Modified:
/trunk/build.xml
/trunk/src/com/google/caja/parser/quasiliteral/CajitaModuleRewriter.java
/trunk/src/com/google/caja/parser/quasiliteral/CajitaRewriter.java
/trunk/src/com/google/caja/parser/quasiliteral/DefaultValijaRewriter.java
/trunk/src/com/google/caja/parser/quasiliteral/ModuleManager.java
/trunk/src/com/google/caja/parser/quasiliteral/RewriterMessageType.java
/trunk/src/com/google/caja/valija-cajita.js
/trunk/tests/com/google/caja/parser/quasiliteral/CajitaRewriterTest.java
/trunk/tests/com/google/caja/parser/quasiliteral/DefaultValijaRewriterTest.java
/trunk/tests/com/google/caja/parser/quasiliteral/RewriterTestCase.java
/trunk/tests/com/google/caja/parser/quasiliteral/foo/b.js
/trunk/tests/com/google/caja/plugin/DomitaTest.java
/trunk/tests/com/google/caja/plugin/domita_test.html
/trunk/tests/com/google/caja/plugin/domita_test_untrusted.html
=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/cajita-module.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,171 @@
+// Copyright (C) 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @author [email protected]
+ * @requires ___
+ * @provides xhrModuleLoader, scriptModuleLoader
+ *
+ * Each loader object contains one method 'async', which returns a module
+ * function given the source URL.
+ */
+var xhrModuleLoader;
+var scriptModuleLoader;
+var clearModuleCache;
+
+(function() {
+ var cache = {};
+
+ function addJsExtension(src) {
+ if (src.toLowerCase().substring(src.length - 3) !== '.js') {
+ src = src + ".js";
+ }
+ return src;
+ }
+
+ function xhrLoad(src) {
+ var r = Q.defer();
+
+ if (cache[src] !== undefined) {
+ r.resolve(cache[src]);
+ }
+ else {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ var savedModuleHandler = ___.getNewModuleHandler();
+ ___.setNewModuleHandler(___.primFreeze({
+ handle: ___.markFuncFreeze(function theHandler(module) {
+ try {
+ var securedModule = ___.prepareModule(module);
+ r.resolve(securedModule);
+ cache[src] = securedModule;
+ } catch (e) {
+ r.resolve(Q.reject(e));
+ }
+ })
+ }));
+ //TODO: validate the response before eval it
+ eval(xhr.responseText);
+ ___.setNewModuleHandler(savedModuleHandler);
+ } else {
+ r.resolve(Q.reject(
+ "Retrieving module failed, status code = " + xhr.status));
+ }
+ }
+ };
+
+ src = addJsExtension(src);
+ xhr.open("GET", src, true);
+ xhr.overrideMimeType("application/javascript");
+ xhr.send(null);
+ }
+ return r.promise;
+ }
+
+ xhrModuleLoader = ___.primFreeze({ async: ___.markFuncFreeze(xhrLoad) });
+
+ var head = 0;
+ var queue = [];
+ var busy = false;
+
+ function scriptLoad(src) {
+ var r = Q.defer();
+
+ src = addJsExtension(src);
+
+ function dequeue() {
+ if (head < queue.length) {
+ busy = true;
+ var savedHead = head;
+
+ ___.setNewModuleHandler(___.primFreeze({
+ handle: ___.markFuncFreeze(function theHandler(module) {
+ if (savedHead === head) {
+ var r = queue[head].defer;
+ try {
+ var securedModule = ___.prepareModule(module);
+ r.resolve(securedModule);
+ cache[queue[head].src] = securedModule;
+ } catch (e) {
+ r.resolve(Q.reject(e));
+ }
+ delete queue[head];
+ head++;
+ dequeue();
+ } else {
+ // this should not happen
+ // the module may have been mistakenly treated as a failure
+ }
+ })
+ }));
+
+ function timeout() {
+ if (savedHead === head) {
+ var r = queue[head].defer;
+ r.resolve(Q.reject(
+ "Retrieving the module" + queue[head].src + " failed."));
+ delete queue[head];
+ head++;
+ dequeue();
+ } else {
+ // the module has been loaded successfully
+ }
+ }
+
+ var script = document.createElement("script");
+ script.src = queue[head].src;
+ script.onerror = function() {
+ timeout();
+ script.onreadystatechange = script.onerror = null;
+ };
+ script.onreadystatechange = function() {
+ if (script.readyState === 'loaded'
+ || script.readyState === 'complete') {
+ timeout();
+ script.onreadystatechange = script.onerror = null;
+ }
+ };
+ document.getElementsByTagName('head')[0].appendChild(script);
+ } else {
+ busy = false;
+ }
+ }
+
+ if (cache[src] !== undefined) {
+ r.resolve(cache[src]);
+ } else {
+ var e = new Object();
+ e.src = src;
+ e.defer = r;
+ queue.push(e);
+
+ if (!busy) {
+ dequeue();
+ }
+ }
+
+ return r.promise;
+ }
+
+ scriptModuleLoader = ___.primFreeze(
+ { async: ___.markFuncFreeze(scriptLoad) });
+
+ clearModuleCache = ___.markFuncFreeze(function() {
+ cajita.forOwnKeys(cache, ___.markFuncFreeze(function(k, v) {
+ delete cache[k];
+ }));
+ });
+})();
=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/cajita-promise.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,296 @@
+// Copyright 2007-2009 Tyler Close
+// under the terms of the MIT X license found at
+// http://www.opensource.org/licenses/mit-license.html
+
+
+/**
+ * Implementation of promise for Cajita
+ * Export Q to the global scope
+ *
+ * @contributor: [email protected]
+ *
+ * Mostly taken from the ref_send implementation by Tyler Close
+ * Add the isPromise___ flag to support function promise
+ */
+
+var Q;
+
+(function() {
+ function reject(reason) {
+ function rejected(op, arg1, arg2, arg3) {
+ if (undefined === op) { return rejected; }
+ if ('WHEN' === op) { return arg2 ? arg2(reason) : reject(reason); }
+ return arg1 ? arg1(reject(reason)) : reject(reason);
+ }
+ rejected.reason = reason;
+ rejected.isPromise___ = true;
+ return rejected;
+ }
+
+ function ref(value) {
+ if (null === value || undefined === value) {
+ return reject({ 'class': [ 'NaO' ] });
+ }
+ if ('number' === typeof value && !isFinite(value)) {
+ return reject({ 'class': [ 'NaN' ] });
+ }
+ function fulfilled(op, arg1, arg2, arg3) {
+ if (undefined === op) { return value; }
+ var r;
+ switch (op) {
+ case 'WHEN':
+ r = value;
+ break;
+ case 'GET':
+ if (undefined === arg2 || null === arg2) {
+ r = value;
+ } else {
+ r = value[arg2];
+ }
+ break;
+ case 'POST':
+ if (undefined === arg2 || null === arg2) {
+ r = reject({});
+ } else {
+ r = value[arg2].apply(value, arg3);
+ }
+ break;
+ case 'PUT':
+ if (undefined === arg2 || null === arg2) {
+ r = reject({});
+ } else {
+ value[arg2] = arg3;
+ r = {};
+ }
+ break;
+ case 'DELETE':
+ if (undefined === arg2 || null === arg2) {
+ r = reject({});
+ } else {
+ delete value[arg2];
+ r = {};
+ }
+ break;
+ default:
+ r = reject({});
+ }
+ return arg1 ? arg1.apply(null, [r]) : r;
+ }
+ fulfilled.isPromise___ = true;
+ return fulfilled;
+ }
+
+ var enqueue = (function () {
+ var active = false;
+ var pending = [];
+ var run = function () {
+ var task = pending.shift();
+ if (0 === pending.length) {
+ active = false;
+ } else {
+ setTimeout(run, 0);
+ }
+ task();
+ };
+ return function (task) {
+ pending.push(task);
+ if (!active) {
+ setTimeout(run, 0);
+ active = true;
+ }
+ };
+ }());
+
+ /**
+ * Enqueues a promise operation.
+ *
+ * The above functions, reject() and ref(), each construct a kind of
+ * promise. Other libraries can provide other kinds of promises by
+ * implementing the same API. A promise is a function with signature:
+ * function (op, arg1, arg2, arg3). The first argument determines the
+ * interpretation of the remaining arguments. The following cases exist:
+ *
+ * 'op' is undefined:
+ * Return the most resolved current value of the promise.
+ *
+ * 'op' is "WHEN":
+ * 'arg1': callback to invoke with the fulfilled value of the promise
+ * 'arg2': callback to invoke with the rejection reason for the promise
+ *
+ * 'op' is "GET":
+ * 'arg1': callback to invoke with the value of the named property
+ * 'arg2': name of the property to read
+ *
+ * 'op' is "POST":
+ * 'arg1': callback to invoke with the return value from the invocation
+ * 'arg2': name of the method to invoke
+ * 'arg3': array of invocation arguments
+ *
+ * 'op' is "PUT":
+ * 'arg1': callback to invoke with the return value from the operation
+ * 'arg2': name of the property to set
+ * 'arg3': new value of property
+ *
+ * 'op' is "DELETE":
+ * 'arg1': callback to invoke with the return value from the operation
+ * 'arg2': name of the property to delete
+ *
+ * 'op' is unrecognized:
+ * 'arg1': callback to invoke with a rejected promise
+ */
+ function forward(p, op, arg1, arg2, arg3) {
+ enqueue(function () { p(op, arg1, arg2, arg3); });
+ }
+
+ /**
+ * Gets the corresponding promise for a given reference.
+ */
+ function promised(value) {
+ return ('function' === typeof value && value.isPromise___ === true)
+ ? value : ref(value);
+ }
+
+ function defer() {
+ var value;
+ var pending = [];
+ function promise(op, arg1, arg2, arg3) {
+ if (undefined === op) { return pending ? promise : value(); }
+ if (pending) {
+ pending.push({ op: op, arg1: arg1, arg2: arg2, arg3: arg3 });
+ } else {
+ forward(value, op, arg1, arg2, arg3);
+ }
+ }
+ promise.isPromise___ = true;
+ return ___.primFreeze({
+ promise: ___.markFuncFreeze(promise),
+ resolve: ___.markFuncFreeze(function (p) {
+ if (!pending) { return; }
+
+ var todo = pending;
+ pending = null;
+ value = promised(p);
+ for (var i = 0; i !== todo.length; i += 1) {
+ var x = todo[+i];
+ forward(value, x.op, x.arg1, x.arg2, x.arg3);
+ }
+ })
+ });
+ }
+
+ Q = {
+ /**
+ * Enqueues a task to be run in a future turn.
+ * @param task function to invoke later
+ */
+ run: ___.markFuncFreeze(enqueue),
+
+ /**
+ * Constructs a rejected promise.
+ * @param reason value describing the failure
+ */
+ reject: ___.markFuncFreeze(reject),
+
+ /**
+ * Constructs a promise for an immediate reference.
+ * @param value immediate reference
+ */
+ ref: ___.markFuncFreeze(ref),
+
+ /**
+ * Constructs a ( promise, resolver ) pair.
+ *
+ * The resolver is a callback to invoke with a more resolved value for
+ * the promise. To fulfill the promise, simply invoke the resolver with
+ * an immediate reference. To reject the promise, invoke the resolver
+ * with the return from a call to reject(). To put the promise in the
+ * same state as another promise, invoke the resolver with that other
+ * promise.
+ */
+ defer: ___.markFuncFreeze(defer),
+
+ /**
+ * Gets the current value of a promise.
+ * @param value promise or immediate reference to evaluate
+ */
+ near: ___.markFuncFreeze(function (value) {
+ return ('function' === typeof value && value.isPromise___ === true)
+ ? value() : value;
+ }),
+
+ /**
+ * Registers an observer on a promise.
+ * @param value promise or immediate reference to observe
+ * @param fulfilled function to be called with the resolved value
+ * @param rejected function to be called with the rejection reason
+ * @return promise for the return value from the invoked callback
+ */
+ when: ___.markFuncFreeze(function (value, fulfilled, rejected) {
+ var r = defer();
+ var done = false; // ensure the untrusted promise makes at most a
+ // single call to one of the callbacks
+ forward(promised(value), 'WHEN', function (x) {
+ if (done) { throw new Error(); }
+ done = true;
+ r.resolve(ref(x)('WHEN', fulfilled, rejected));
+ }, function (reason) {
+ if (done) { throw new Error(); }
+ done = true;
+ r.resolve(rejected ? rejected.apply(null, [reason]) :
reject(reason));
+ });
+ return r.promise;
+ }),
+
+ /**
+ * Gets the value of a property in a future turn.
+ * @param target promise or immediate reference for target object
+ * @param noun name of property to get
+ * @return promise for the property value
+ */
+ get: ___.markFuncFreeze(function (target, noun) {
+ var r = defer();
+ forward(promised(target), 'GET', r.resolve, noun);
+ return r.promise;
+ }),
+
+ /**
+ * Invokes a method in a future turn.
+ * @param target promise or immediate reference for target object
+ * @param verb name of method to invoke
+ * @param argv array of invocation arguments
+ * @return promise for the return value
+ */
+ post: ___.markFuncFreeze(function (target, verb, argv) {
+ var r = defer();
+ forward(promised(target), 'POST', r.resolve, verb, argv);
+ return r.promise;
+ }),
+
+ /**
+ * Sets the value of a property in a future turn.
+ * @param target promise or immediate reference for target object
+ * @param noun name of property to set
+ * @param value new value of property
+ * @return promise for the return value
+ */
+ put: ___.markFuncFreeze(function (target, noun, value) {
+ var r = defer();
+ forward(promised(target), 'PUT', r.resolve, noun, value);
+ return r.promise;
+ }),
+
+ /**
+ * Deletes a property in a future turn.
+ * @param target promise or immediate reference for target object
+ * @param noun name of property to delete
+ * @return promise for the return value
+ */
+ remove: ___.markFuncFreeze(function (target, noun) {
+ var r = defer();
+ forward(promised(target), 'DELETE', r.resolve, noun);
+ return r.promise;
+ })
+ };
+
+ ___.primFreeze(Q);
+})();
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/a.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,1 @@
+x + y
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/c.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,1 @@
+x + 1
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/foo/b.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,10 @@
+var m = load.async('../c.co');
+var f1 = function(module) {
+ var r1 = module({x: x});
+ var r2 = module({x: y});
+ return r1 + r2;
+};
+var f2 = function(reason) {
+ fail('Loading module C failed, ' + reason);
+};
+Q.when(m, f1, f2);
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/parser/quasiliteral/x.js Fri Aug 21
10:14:19 2009
@@ -0,0 +1,1 @@
+x = 3;
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/recursion.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,15 @@
+var result = Q.defer();
+if (x <= 0) {
+ result.resolve(-1);
+}
+else if (x == 1) {
+ result.resolve(1);
+}
+else {
+ var m = load.async('recursion.co');
+ Q.when(m, function(module) {
+ var r = module({x: x - 1, load: load, Q: Q});
+ Q.when(r, function(r) {result.resolve(x * r); });
+ });
+}
+result.promise;
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/x.js Fri Aug 21 10:14:19 2009
@@ -0,0 +1,1 @@
+x = 3;
=======================================
--- /trunk/build.xml Thu Aug 13 12:03:06 2009
+++ /trunk/build.xml Fri Aug 21 10:14:19 2009
@@ -400,6 +400,38 @@
<output file="${lib}/com/google/caja/plugin/domita_test.vo.html"
language="valija"/>
</transform>
+ <transform>
+ <include file="${tests}/com/google/caja/a.js"/>
+ <output file="${lib}/com/google/caja/a.co.js" language="cajita"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/foo/b.js"/>
+ <output file="${lib}/com/google/caja/foo/b.co.js" language="cajita"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/c.js"/>
+ <output file="${lib}/com/google/caja/c.co.js" language="cajita"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/recursion.js"/>
+ <output file="${lib}/com/google/caja/recursion.co.js"
language="cajita"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/x.js"/>
+ <output file="${lib}/com/google/caja/x.vo.js" language="valija"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/foo/inc.js"/>
+ <output file="${lib}/com/google/caja/foo/inc.vo.js"
language="valija"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/add.js"/>
+ <output file="${lib}/com/google/caja/add.vo.js" language="valija"/>
+ </transform>
+ <transform>
+ <include file="${tests}/com/google/caja/serverJsRecursion.js"/>
+ <output file="${lib}/com/google/caja/serverJsRecursion.vo.js"
language="valija"/>
+ </transform>
<copy todir="${lib}">
<fileset dir="${src}" includes="**/*.html"/>
<fileset dir="${tests}" includes="**/*.html"/>
@@ -450,6 +482,8 @@
<copy todir="${lib}">
<fileset dir="${src}">
<include name="**/caja/cajita.js"/>
+ <include name="**/caja/cajita-promise.js"/>
+ <include name="**/caja/cajita-module.js"/>
<include name="**/caja/cajita-debugmode.js"/>
<include name="**/caja/console.js"/>
<include name="**/caja/log-to-console.js"/>
=======================================
---
/trunk/src/com/google/caja/parser/quasiliteral/CajitaModuleRewriter.java
Mon Aug 10 14:00:55 2009
+++
/trunk/src/com/google/caja/parser/quasiliteral/CajitaModuleRewriter.java
Fri Aug 21 10:14:19 2009
@@ -46,6 +46,7 @@
public class CajitaModuleRewriter extends Rewriter {
private final BuildInfo buildInfo;
private final PluginEnvironment pluginEnv;
+ private final boolean isFromValija;
final public Rule[] cajaRules = {
new Rule() {
@@ -73,7 +74,7 @@
public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
if (node instanceof UncajoledModule) {
ModuleManager moduleManager =
- new ModuleManager(buildInfo, pluginEnv, mq);
+ new ModuleManager(buildInfo, pluginEnv, mq, isFromValija);
moduleManager.appendUncajoledModule((UncajoledModule)node);
List<ParseTreeNode> moduleDefs = new ArrayList<ParseTreeNode>();
@@ -118,15 +119,18 @@
*/
public CajitaModuleRewriter(
BuildInfo buildInfo, PluginEnvironment pluginEnv, MessageQueue mq,
- boolean logging) {
+ boolean logging, boolean isFromValija) {
super(mq, false, logging);
this.buildInfo = buildInfo;
this.pluginEnv = pluginEnv;
+ this.isFromValija = isFromValija;
addRules(cajaRules);
}
public CajitaModuleRewriter(
- BuildInfo buildInfo, MessageQueue mq, boolean logging) {
- this(buildInfo, PluginEnvironment.CLOSED_PLUGIN_ENVIRONMENT, mq,
logging);
+ BuildInfo buildInfo, MessageQueue mq, boolean logging,
+ boolean isFromValija) {
+ this(buildInfo, PluginEnvironment.CLOSED_PLUGIN_ENVIRONMENT, mq,
logging,
+ isFromValija);
}
}
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/CajitaRewriter.java Mon
Aug 10 14:00:55 2009
+++ /trunk/src/com/google/caja/parser/quasiliteral/CajitaRewriter.java Fri
Aug 21 10:14:19 2009
@@ -219,19 +219,19 @@
}
},
- // Loading a Module
+ // Loading a static module
new Rule() {
@Override
@RuleDescription(
name="loadmodule",
- synopsis="rewrites the loader.load function.",
+ synopsis="rewrites the load function.",
reason="",
- matches="loader.load(@arg)",
+ matches="load(@arg)",
substitutes="modulemap_...@moduleindex]")
public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
Map<String, ParseTreeNode> bindings = match(node);
- if (bindings != null && scope.isOuter("loader")) {
+ if (bindings != null && scope.isImported("load")) {
ParseTreeNode arg = bindings.get("arg");
if (arg instanceof StringLiteral) {
assert(moduleManager != null);
@@ -240,15 +240,13 @@
if (index != -1) {
return substV("moduleIndex",
new IntegerLiteral(FilePosition.UNKNOWN, index));
- }
- else {
+ } else {
// error messages were logged in the function getModule
return node;
}
- }
- else {
+ } else {
mq.addMessage(
- RewriterMessageType.CANNOT_LOAD_A_DYNAMIC_MODULE,
+ RewriterMessageType.CANNOT_LOAD_A_DYNAMIC_CAJITA_MODULE,
node.getFilePosition());
return node;
}
=======================================
---
/trunk/src/com/google/caja/parser/quasiliteral/DefaultValijaRewriter.java
Mon Aug 10 14:00:55 2009
+++
/trunk/src/com/google/caja/parser/quasiliteral/DefaultValijaRewriter.java
Fri Aug 21 10:14:19 2009
@@ -177,7 +177,36 @@
return NONE;
}
},
-
+
+ // static module loading
+
+ new Rule() {
+ @Override
+ @RuleDescription(
+ name="staticModuleIncluding",
+ synopsis="Replaced with the Cajita module loading",
+ reason="",
+ matches="includeScript(@arg)",
+ substitutes="load(@arg)({$v: $v})")
+ public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
+ Map<String, ParseTreeNode> bindings = match(node);
+ if (bindings != null && scope.isOuter("includeScript")) {
+ ParseTreeNode arg = bindings.get("arg");
+ if (arg instanceof StringLiteral) {
+ return substV("arg",
+ new StringLiteral(FilePosition.UNKNOWN,
+ ((StringLiteral) arg).getUnquotedValue()));
+ } else {
+ mq.addMessage(
+ RewriterMessageType.CANNOT_LOAD_A_DYNAMIC_VALIJA_MODULE,
+ node.getFilePosition());
+ return node;
+ }
+ }
+ return NONE;
+ }
+ },
+
////////////////////////////////////////////////////////////////////////
// Module envelope
////////////////////////////////////////////////////////////////////////
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/ModuleManager.java Mon
Aug 10 14:00:55 2009
+++ /trunk/src/com/google/caja/parser/quasiliteral/ModuleManager.java Fri
Aug 21 10:14:19 2009
@@ -47,18 +47,21 @@
private final PluginEnvironment pluginEnv;
private final BuildInfo buildInfo;
private final MessageQueue mq;
-
- private final Map<String, Integer> moduleNameMap
+ private final boolean isValija;
+
+ private final Map<String, Integer> moduleNameMap
= new HashMap<String, Integer>();
private final Map<Integer, CajoledModule> moduleIndexMap
= new HashMap<Integer, CajoledModule>();
private int moduleCounter = 0;
public ModuleManager(
- BuildInfo buildInfo, PluginEnvironment pluginEnv, MessageQueue mq) {
+ BuildInfo buildInfo, PluginEnvironment pluginEnv, MessageQueue mq,
+ boolean isValija) {
this.buildInfo = buildInfo;
this.pluginEnv = pluginEnv;
this.mq = mq;
+ this.isValija = isValija;
}
public Map<Integer, CajoledModule> getModuleIndexMap() {
@@ -124,8 +127,17 @@
Block input = new Parser(tq, mq).parse();
tq.expectEmpty();
- CajitaRewriter dcr = new CajitaRewriter(buildInfo, this, mq, false);
- UncajoledModule uncajoledModule = new UncajoledModule(input);
+ Block intermediate;
+ if (isValija) {
+ DefaultValijaRewriter dvr = new DefaultValijaRewriter(mq);
+ intermediate = (Block) dvr.expand(input);
+ } else {
+ intermediate = input;
+ }
+
+ CajitaRewriter dcr =
+ new CajitaRewriter(buildInfo, this, mq, false);
+ UncajoledModule uncajoledModule = new UncajoledModule(intermediate);
CajoledModule cajoledModule = (CajoledModule)
dcr.expand(uncajoledModule);
moduleIndexMap.put(cur, cajoledModule);
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/RewriterMessageType.java
Tue Jul 14 13:50:32 2009
+++ /trunk/src/com/google/caja/parser/quasiliteral/RewriterMessageType.java
Fri Aug 21 10:14:19 2009
@@ -176,8 +176,16 @@
"%s: Parsing module failed: %s",
MessageLevel.FATAL_ERROR),
- CANNOT_LOAD_A_DYNAMIC_MODULE(
- "%s: Cannot load a dynamic module",
+ CANNOT_LOAD_A_DYNAMIC_CAJITA_MODULE(
+ "%s: Dynamically computed names should use load.async()",
+ MessageLevel.FATAL_ERROR),
+
+ CANNOT_LOAD_A_DYNAMIC_VALIJA_MODULE(
+ "%s: Dynamically computed names should use includeScript.async()",
+ MessageLevel.FATAL_ERROR),
+
+ CANNOT_LOAD_A_DYNAMIC_SERVERJS_MODULE(
+ "%s: Dynamically computed names should use require.async()",
MessageLevel.FATAL_ERROR);
private final String formatString;
=======================================
--- /trunk/src/com/google/caja/valija-cajita.js Wed Jul 22 15:37:38 2009
+++ /trunk/src/com/google/caja/valija-cajita.js Fri Aug 21 10:14:19 2009
@@ -487,7 +487,7 @@
return (v === void 0) ? key : ((v === undefIndicator) ? void 0 : v);
}
- // If you change these names, also change them in PermitTemplate.java
+ //If you change these names, also change them in PermitTemplate.java
return cajita.freeze({
typeOf: typeOf,
instanceOf: instanceOf,
=======================================
---
/trunk/tests/com/google/caja/parser/quasiliteral/CajitaRewriterTest.java
Mon Aug 10 14:00:55 2009
+++
/trunk/tests/com/google/caja/parser/quasiliteral/CajitaRewriterTest.java
Fri Aug 21 10:14:19 2009
@@ -2335,7 +2335,7 @@
}
/**
- * Tests the securable module loading
+ * Tests the static module loading
*/
// TODO: Refactor the test cases so that we can use CajitaModuleRewriter
// for all tests
@@ -2343,34 +2343,33 @@
// for those tests that run against other ParseTreeNode
public final void testModule() throws Exception {
CajitaModuleRewriter moduleRewriter = new CajitaModuleRewriter(
- new TestBuildInfo(), new TestPluginEnvironment(), mq, false);
+ new TestBuildInfo(), new TestPluginEnvironment(), mq, false,
false);
setRewriter(moduleRewriter);
rewriteAndExecute(
- "var r = loader.load('foo/b')({x: 6, y: 3}); "
+ "var r = load('foo/b')({x: 6, y: 3}); "
+ "assertEquals(r, 11);");
rewriteAndExecute(
- "var r1 = loader.load('foo/b')({x: 6, y: 3}); "
- + "var r2 = loader.load('foo/b')({x: 1, y: 2}); "
- + "var r3 = loader.load('c')({x: 2, y: 6}); "
+ "var r1 = load('foo/b')({x: 6, y: 3}); "
+ + "var r2 = load('foo/b')({x: 1, y: 2}); "
+ + "var r3 = load('c')({x: 2, y: 6}); "
+ "var r = r1 + r2 + r3; "
+ "assertEquals(r, 24);");
rewriteAndExecute(
- "var m = loader.load('foo/b');"
+ "var m = load('foo/b');"
+ "var s = m.cajolerName;"
+ "assertEquals('com.google.caja', s);");
checkAddsMessage(
- new UncajoledModule(js(fromString("var m =
loader.load('foo/c');"))),
+ new UncajoledModule(js(fromString("var m = load('foo/c');"))),
RewriterMessageType.MODULE_NOT_FOUND,
MessageLevel.FATAL_ERROR);
checkAddsMessage(
- new UncajoledModule(
- js(fromString("var s = 'c'; var m = loader.load(s);"))),
- RewriterMessageType.CANNOT_LOAD_A_DYNAMIC_MODULE,
+ new UncajoledModule(js(fromString("var s = 'c'; var m =
load(s);"))),
+ RewriterMessageType.CANNOT_LOAD_A_DYNAMIC_CAJITA_MODULE,
MessageLevel.FATAL_ERROR);
setRewriter(cajitaRewriter);
=======================================
---
/trunk/tests/com/google/caja/parser/quasiliteral/DefaultValijaRewriterTest.java
Mon Aug 10 14:00:55 2009
+++
/trunk/tests/com/google/caja/parser/quasiliteral/DefaultValijaRewriterTest.java
Fri Aug 21 10:14:19 2009
@@ -15,10 +15,13 @@
package com.google.caja.parser.quasiliteral;
import java.io.IOException;
-
+import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.ExternalReference;
+import com.google.caja.lexer.InputSource;
import com.google.caja.lexer.FilePosition;
import com.google.caja.lexer.ParseException;
import com.google.caja.parser.ParseTreeNode;
@@ -35,6 +38,7 @@
import com.google.caja.parser.js.Statement;
import com.google.caja.parser.js.SyntheticNodes;
import com.google.caja.parser.js.UncajoledModule;
+import com.google.caja.plugin.PluginEnvironment;
import com.google.caja.util.RhinoTestBed;
import com.google.caja.reporting.TestBuildInfo;
@@ -42,6 +46,24 @@
* @author [email protected]
*/
public class DefaultValijaRewriterTest extends CommonJsRewriterTestCase {
+ protected class TestPluginEnvironment implements PluginEnvironment {
+ public CharProducer loadExternalResource(
+ ExternalReference ref, String mimeType) {
+ URI uri = ref.getUri();
+ uri = ref.getReferencePosition().source().getUri().resolve(uri);
+ try {
+ InputSource is = new InputSource(uri);
+ return fromResource(uri.getPath().substring(1), is);
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ public String rewriteUri(ExternalReference uri, String mimeType) {
+ return null;
+ }
+ }
+
private Rewriter valijaRewriter;
private Rewriter cajitaRewriter;
private Rewriter innocentCodeRewriter;
@@ -50,7 +72,8 @@
public void setUp() throws Exception {
super.setUp();
valijaRewriter = new DefaultValijaRewriter(mq, false);
- cajitaRewriter = new CajitaRewriter(new TestBuildInfo(), mq, false);
+ cajitaRewriter = new CajitaModuleRewriter(
+ new TestBuildInfo(), new TestPluginEnvironment(), mq, false, true);
innocentCodeRewriter = new InnocentCodeRewriter(mq, false);
// Start with this one, then switch later to CajitaRewriter for
// the second pass.
@@ -620,7 +643,14 @@
+ "foo();"
);
}
-
+
+ public final void testStaticModuleLoading() throws Exception {
+ rewriteAndExecute(
+ "includeScript('x');"
+ + "assertEquals(x, 3);"
+ );
+ }
+
@Override
protected Object executePlain(String caja)
throws IOException, ParseException {
@@ -661,11 +691,14 @@
getClass(), "../../../../../js/json_sans_eval/json_sans_eval.js"),
new RhinoTestBed.Input(getClass(), "/com/google/caja/cajita.js"),
new RhinoTestBed.Input(
+ getClass(), "/com/google/caja/cajita-promise.js"),
+ new RhinoTestBed.Input(
getClass(), "../../../../../js/jsunit/2.2/jsUnitCore.js"),
new RhinoTestBed.Input(
getClass(), "/com/google/caja/log-to-console.js"),
new RhinoTestBed.Input(
"var testImports = ___.copy(___.sharedImports);\n" +
+ "testImports.Q = Q;" +
"testImports.loader = ___.freeze({\n" +
" provide: ___.markFuncFreeze(\n" +
" function(v){ valijaMaker = v; })\n" +
@@ -677,6 +710,8 @@
new RhinoTestBed.Input(
// Set up the imports environment.
"testImports = ___.copy(___.sharedImports);\n" +
+ "testImports.Q = Q;" +
+ "testImports.env = {x: 6};" +
"testImports.console = console;" +
"testImports.assertEquals = assertEquals;" +
"___.grantFunc(testImports, 'assertEquals');" +
=======================================
--- /trunk/tests/com/google/caja/parser/quasiliteral/RewriterTestCase.java
Mon Aug 10 14:00:55 2009
+++ /trunk/tests/com/google/caja/parser/quasiliteral/RewriterTestCase.java
Fri Aug 21 10:14:19 2009
@@ -27,6 +27,7 @@
import com.google.caja.parser.js.Identifier;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.js.SyntheticNodes;
+import com.google.caja.parser.js.UncajoledModule;
import com.google.caja.util.CajaTestCase;
import com.google.caja.util.RhinoAsserts;
import com.google.caja.util.TestUtil;
@@ -69,8 +70,8 @@
// TODO(ihab.awad): Refactor tests to use checkAddsMessage(...) instead
protected void checkFails(String input, String error) throws Exception {
mq.getMessages().clear();
- getRewriter().expand(new Block(
- FilePosition.UNKNOWN, Arrays.asList(js(fromString(input, is)))));
+ getRewriter().expand(new UncajoledModule(new Block(
+ FilePosition.UNKNOWN, Arrays.asList(js(fromString(input, is))))));
assertFalse(
"Expected error, found none: " + error,
=======================================
--- /trunk/tests/com/google/caja/parser/quasiliteral/foo/b.js Tue Jul 14
13:50:32 2009
+++ /trunk/tests/com/google/caja/parser/quasiliteral/foo/b.js Fri Aug 21
10:14:19 2009
@@ -1,1 +1,1 @@
-loader.load('../c')({x: x + 1, y: y + 1});
+load('../c')({x: x + 1, y: y + 1});
=======================================
--- /trunk/tests/com/google/caja/plugin/DomitaTest.java Thu Jul 23 09:16:30
2009
+++ /trunk/tests/com/google/caja/plugin/DomitaTest.java Fri Aug 21 10:14:19
2009
@@ -130,5 +130,7 @@
String title = driver.getTitle();
assertTrue("The title shows " + title.substring(title.lastIndexOf("-")
+ 1),
title.endsWith("all tests passed"));
+
+ driver.quit();
}
}
=======================================
--- /trunk/tests/com/google/caja/plugin/domita_test.html Thu Aug 6
10:29:37 2009
+++ /trunk/tests/com/google/caja/plugin/domita_test.html Fri Aug 21
10:14:19 2009
@@ -22,6 +22,10 @@
<script src="../../../../js/json_sans_eval/json_sans_eval.js"
onerror="console.error(this.src)"></script>
<script src="../cajita.js" onerror="console.error(this.src)"></script>
+ <script src="../cajita-promise.js" onerror="console.error(this.src)">
+ </script>
+ <script src="../cajita-module.js" onerror="console.error(this.src)">
+ </script>
<script src="../../../../js/jsunit/2.2/jsUnitCore.js"></script>
<script src="jsunit.js" onerror="console.error(this.src)"></script>
<script src="unicode.js" onerror="console.error(this.src)"></script>
@@ -276,7 +280,8 @@
s.appendChild(document.createTextNode('/* intentionally blank
*/'));
return testImports.tameNode___(s, true);
}),
- isValija: isValija
+ isValija: isValija,
+ clearModuleCache: clearModuleCache
});
// Marks a container green to indicate that test passed
@@ -383,6 +388,40 @@
testImports.valijaMode = !!valijaMaker;
+ testImports.Q = Q;
+
+ testImports.xhrModuleLoad = ___.primFreeze({
+ async: ___.markFuncFreeze(function(src) {
+ return xhrModuleLoader.async(
+ '/ant-lib/com/google/caja/' + src);
+ })
+ });
+
+ testImports.scriptModuleLoad = ___.primFreeze({
+ async: ___.markFuncFreeze(function(src) {
+ return scriptModuleLoader.async(
+ 'http://localhost:8000/ant-lib/com/google/caja/' + src);
+ })
+ });
+
+ testImports.xhrIncludeScript = ___.primFreeze({
+ async: ___.markFuncFreeze(function(src) {
+ var m = xhrModuleLoader.async(
+ '/ant-lib/com/google/caja/' + src);
+ return Q.when(m,
+ function(module) { return module({$v: testImports.$v}); });
+ })
+ });
+
+ testImports.scriptIncludeScript = ___.primFreeze({
+ async: ___.markFuncFreeze(function(src) {
+ var m = scriptModuleLoader.async(
+ 'http://localhost:8000/ant-lib/com/google/caja/' + src);
+ return Q.when(m,
+ function(module) { return module({$v: testImports.$v}); });
+ })
+ });
+
attachDocumentStub(
'-xyz___',
{
=======================================
--- /trunk/tests/com/google/caja/plugin/domita_test_untrusted.html Mon Aug
17 09:51:01 2009
+++ /trunk/tests/com/google/caja/plugin/domita_test_untrusted.html Fri Aug
21 10:14:19 2009
@@ -385,6 +385,25 @@
duck-4
</div>
+<p class="testcontainer" id="xhr-module-loader">XHR Module Loader</p>
+
+<p class="testcontainer" id="script-module-loader">Script Module Loader</p>
+
+<p class="testcontainer" id="xhr-module-loader-2">
+XHR Module Loader 2</p>
+
+<p class="testcontainer" id="script-module-loader-2">
+Script Module Loader 2</p>
+
+<p class="testcontainer" id="xhr-module-loader-failure">
+XHR Module Loader Failure</p>
+
+<p class="testcontainer" id="script-module-loader-failure">
+Script Module Loader Failure</p>
+
+<p class="testcontainer" id="recursive-module">
+Recursive Module</p>
+
<p class="testcontainer" id="test-document-body-appendChild"
>I should be the last element until something is appended to
document.body</p>
@@ -2651,6 +2670,132 @@
false);
});
+function testAsyncModuleLoader(load, includeScript, prefix) {
+ directAccess.clearModuleCache();
+ if (!valijaMode) {
+ var m = load.async('a.co');
+ var f1 = function(module) {
+ var r = module({x: 1, y: 2});
+ assertEquals(r, 3);
+ pass(prefix + '-module-loader');
+ };
+ var f2 = function(reason) {
+ fail("Loading module a failed, " + reason);
+ };
+ Q.when(m, f1, f2);
+ } else {
+ var m = includeScript.async('x.vo');
+ var f1 = function(result) {
+ assertEquals(x, 3);
+ pass(prefix + '-module-loader');
+ };
+ var f2 = function(reason) {
+ fail("Loading module x failed, " + reason);
+ };
+ Q.when(m, f1, f2);
+ }
+}
+
+jsunitRegister('testXhrModuleLoader',
+ function testXhrModuleLoader() {
+ testAsyncModuleLoader(xhrModuleLoad, xhrIncludeScript, "xhr");
+});
+
+jsunitRegister('testScriptModuleLoader',
+ function testScriptModuleLoader() {
+ testAsyncModuleLoader(scriptModuleLoad, scriptIncludeScript, "script");
+});
+
+function testAsyncModuleLoader2(load, prefix) {
+ directAccess.clearModuleCache();
+ if (!valijaMode) {
+ var m = load.async('foo/b.co');
+ var f1 = function(module) {
+ var r = module({x: 1, y: 2, load: { async: function(src)
+ { return load.async('foo/' + src); } },
+ Q: Q, fail: fail});
+ Q.when(r, function(result) {
+ assertEquals(result, 5);
+ pass(prefix + '-module-loader-2');
+ },
+ function(reason) {
+ fail("Waiting for result failed, " + reason);
+ });
+ };
+ var f2 = function(reason) {
+ fail("loading module B failed, " + reason);
+ };
+ Q.when(m, f1, f2);
+ } else {
+ pass(prefix + '-module-loader-2');
+ }
+}
+
+jsunitRegister('testXhrModuleLoader2',
+ function testXhrModuleLoader2() {
+ testAsyncModuleLoader2(xhrModuleLoad, "xhr");
+});
+
+jsunitRegister('testScriptModuleLoader2',
+ function testScriptModuleLoader2() {
+ testAsyncModuleLoader2(scriptModuleLoad, "script");
+});
+
+function testAsyncModuleLoaderFailure(load, includeScript, prefix) {
+ directAccess.clearModuleCache();
+ var f1 = function(module) {
+ fail('It should not be resolved.');
+ };
+
+ var f2 = function(reason) {
+ if (reason.substring(0, 26) === 'Retrieving module failed, ') {
+ pass(prefix + '-module-loader-failure');
+ }
+ };
+
+ if (!valijaMode) {
+ var m = xhrModuleLoad.async('b.co');
+ Q.when(m, f1, f2);
+ }
+ else {
+ var m = xhrIncludeScript.async('foo/x.vo');
+ Q.when(m, f1, f2);
+ }
+}
+
+jsunitRegister('testXhrModuleLoaderFailure',
+ function testXhrModuleLoaderFailure() {
+ testAsyncModuleLoaderFailure(xhrModuleLoad, xhrIncludeScript, 'xhr');
+});
+
+jsunitRegister('testScriptModuleLoaderFailure',
+ function testScriptModuleLoaderFailure() {
+ testAsyncModuleLoaderFailure(scriptModuleLoad,
scriptIncludeScript, 'script');
+});
+
+jsunitRegister('testRecursiveModule',
+ function testRecursiveModule() {
+ directAccess.clearModuleCache();
+ if (!valijaMode) {
+ var m = scriptModuleLoad.async('recursion.co');
+ var f1 = function(module) {
+ var r = module({x: 5, load: scriptModuleLoad, Q: Q});
+ Q.when(r, function(result) {
+ assertEquals(result, 120);
+ pass('recursive-module');
+ },
+ function(reason) {
+ fail("Waiting for result failed, " + reason);
+ });
+ };
+ var f2 = function(reason) {
+ fail("Loading module Recursion failed, " + reason);
+ };
+ Q.when(m, f1, f2);
+ } else {
+ pass('recursive-module');
+ }
+});
// Before onload events fire
jsunitRun();