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();

Reply via email to