Revision: 5707
Author:   [email protected]
Date:     Thu Dec 18 19:31:03 2014 UTC
Log:      Backport to es53 branch: 5701,5702,5703,5704,5705,5706
https://codereview.appspot.com/186440043

All changes since the previous merge.

The merge command was:
svn merge -c 5701,5702,5703,5704,5705,5706 ^/trunk
and there were several conflicts; omitting ones which were trivial
(modified code that doesn't exist on this branch, etc.), there were:

* MainBrowserTest has new commentary and timeout on guest-scan-* being
  slow.
* test-scan-guest.js's handling of [[ThrowTypeError]] is now completely
  different between es5 and es53 modes.

[email protected]

https://code.google.com/p/google-caja/source/detail?r=5707

Modified:
 /branches/es53
 /branches/es53/src/com/google/caja/plugin/csslexer.js
 /branches/es53/src/com/google/caja/plugin/domado.js
 /branches/es53/src/com/google/caja/plugin/html-emitter.js
 /branches/es53/src/com/google/caja/plugin/ses-frame-group.js
 /branches/es53/src/com/google/caja/ses/repairES5.js
 /branches/es53/src/com/google/caja/ses/startSES.js
 /branches/es53/tests/com/google/caja/plugin/MainBrowserTest.java
 /branches/es53/tests/com/google/caja/plugin/jsunit.js
 /branches/es53/tests/com/google/caja/plugin/test-cajajs-invocation.js
 /branches/es53/tests/com/google/caja/plugin/test-domado-events-guest.html
 /branches/es53/tests/com/google/caja/plugin/test-scan-guest.js
 /branches/es53/tests/com/google/caja/plugin/test-taming-tamed-guest.html

=======================================
--- /branches/es53/src/com/google/caja/plugin/csslexer.js Mon Sep 9 23:02:35 2013 UTC +++ /branches/es53/src/com/google/caja/plugin/csslexer.js Thu Dec 18 19:31:03 2014 UTC
@@ -225,9 +225,18 @@
    *    delimiters and to not otherwise contain double quotes.
    */
   lexCss = function (cssText) {
-    cssText = '' + cssText;
- var tokens = cssText.replace(/\r\n?/g, '\n') // Normalize CRLF & CR to LF.
-        .match(CSS_TOKEN) || [];
+ // Stringify input. Additionally, insert and remove a non-latin1 character + // to force Firefox 33 to switch to a wide string representation, avoiding
+    // a performance bug. This workaround should become unnecessary after
+    // Firefox 34. https://bugzilla.mozilla.org/show_bug.cgi?id=1081175
+    // https://code.google.com/p/google-caja/issues/detail?id=1941
+    cssText = ('\uffff' + cssText).replace(/^\uffff/, '');
+
+    // // Normalize CRLF & CR to LF.
+    cssText = cssText.replace(/\r\n?/g, '\n');
+
+    // Tokenize.
+    var tokens = cssText.match(CSS_TOKEN) || [];
     var j = 0;
     var last = ' ';
     for (var i = 0, n = tokens.length; i < n; ++i) {
=======================================
--- /branches/es53/src/com/google/caja/plugin/domado.js Fri Aug 29 23:08:55 2014 UTC +++ /branches/es53/src/com/google/caja/plugin/domado.js Thu Dec 18 19:31:03 2014 UTC
@@ -938,7 +938,8 @@
       rulebreaker,
       xmlHttpRequestMaker,
       naiveUriPolicy,
-      getBaseURL) {
+      getBaseURL,
+      onerrorTarget) {
     // See http://www.w3.org/TR/XMLHttpRequest/

     // TODO(ihab.awad): Improve implementation (interleaving, memory leaks)
@@ -974,7 +975,12 @@
           var self = this;
           privates.feral.onreadystatechange = function(event) {
             var evt = { target: self };
-            return handler.call(void 0, evt);
+            try {
+              return handler.call(void 0, evt);
+            } catch (e) {
+              Domado_.handleUncaughtException(
+                  onerrorTarget, e, '<XMLHttpRequest callback>');
+            }
           };
           // Store for later direct invocation if need be
           privates.handler = handler;
@@ -1304,7 +1310,7 @@
    * @return A record of functions attachDocument, dispatchEvent, and
    *     dispatchToHandler.
    */
-  return cajaVM.constFunc(function Domado_(opt_rulebreaker) {
+  function Domado_(opt_rulebreaker) {
     // Everything in this scope but not in function attachDocument() below
// does not contain lexical references to a particular DOM instance, but
     // may have some kind of privileged access to Domado internals.
@@ -1467,35 +1473,45 @@
       function tameSet(action, delayMillis) {
// Existing browsers treat a timeout/interval of null or undefined as a
         // noop.
-        var id;
-        if (action) {
-          if (typeof action === 'function') {
-            // OK
-          } else if (evalStrings && cajaVM.compileModule) {
-            // Note specific ordering: coercion to string occurs at time of
-            // call, syntax errors occur async.
-            var code = '' + action;
-            action = function callbackStringWrapper() {
-              cajaVM.compileModule(code)(environment);
-            };
-          } else {
-            // Early error for usability -- we also defend below.
-            // This check is not *necessary* for security.
-            throw new TypeError(
-                setName + ' called with a ' + typeof action + '.'
- + ' Please pass a function instead of a string of JavaScript');
-          }
-          // actionWrapper defends against:
-          //   * Passing a string-like object which gets taken as code.
-          //   * Non-standard arguments to the callback.
-          //   * Non-standard effects of callback's return value.
-          var actionWrapper = passArg
-            ? function(time) { action(+time); }  // requestAnimationFrame
-            : function() { action(); };  // setTimeout, setInterval
-          id = set(actionWrapper, delayMillis | 0);
+        if (!action) {
+          return undefined;
+        }
+
+        // Convert action to a function.
+        if (typeof action === 'function') {
+          // OK
+        } else if (evalStrings && cajaVM.compileModule) {
+          // Note specific ordering: coercion to string occurs at time of
+          // call, syntax errors occur async.
+          var code = '' + action;
+          action = function callbackStringWrapper() {
+            cajaVM.compileModule(code)(environment);
+          };
         } else {
-          id = undefined;
+          // Early error for usability -- we also defend below.
+          // This check is not *necessary* for security.
+          throw new TypeError(
+              setName + ' called with a ' + typeof action + '.'
+ + ' Please pass a function instead of a string of JavaScript');
         }
+
+        // actionWrapper defends against:
+        //   * Passing a string-like object which gets taken as code.
+        //   * Non-standard arguments to the callback.
+        //   * Non-standard effects of callback's return value.
+        function actionWrapper(arg) {
+          try {
+            if (passArg) {
+              action(+arg);  // requestAnimationFrame
+            } else {
+              action();  // setTimeout, setInterval
+            }
+          } catch (e) {
+            Domado_.handleUncaughtException(
+                target, e, '<setName callback>');
+          }
+        }
+        var id = set(actionWrapper, delayMillis | 0);
         var tamed = {};
         ids.set(tamed, id);
// Freezing is not *necessary*, but it makes testing/reasoning simpler
@@ -3708,14 +3724,8 @@
         try {
           Function.prototype.call.call(func, thisArg, tameEventObj);
         } catch (e) {
-          try {
-            tameWindow.onerror(
-                e.message,
- '<' + tameEventObj.type + ' handler>', // better than nothing
-                0);
-          } catch (e2) {
-            console.error('onerror handler failed\n', e, '\n', e2);
-          }
+          Domado_.handleUncaughtException(tameWindow, e,
+              '<' + tameEventObj.type + ' listener>');
         }
       }

@@ -7040,7 +7050,8 @@
                 makeFunctionAccessible(window.ActiveXObject),
                 makeFunctionAccessible(window.XDomainRequest)),
             naiveUriPolicy,
-            function () { return domicile.pseudoLocation.href; }));
+            function () { return domicile.pseudoLocation.href; },
+            tameWindow));
       });


@@ -7522,12 +7533,14 @@
       var domicile = windowToDomicile.get(imports);
       var node = domicile.tameNode(thisNode);
       var isUserAction = eventIsUserAction(event, window);
+      var tameEventObj = domicile.tameEvent(event);
       try {
         return dispatch(
           isUserAction, pluginId, handler,
- [ node, domicile.tameEvent(event), node ].slice(0, argCount + 1));
+          [ node, tameEventObj, node ].slice(0, argCount + 1));
       } catch (ex) {
-        imports.onerror(ex.message, 'unknown', 0);
+        Domado_.handleUncaughtException(imports, ex,
+            '<' + tameEventObj.type + ' handler>');
       }
     }

@@ -7593,7 +7606,64 @@
       plugin_dispatchToHandler: plugin_dispatchToHandler,
       getDomicileForWindow: windowToDomicile.get.bind(windowToDomicile)
     });
+  }
+
+  /**
+   * Invoke the possibly guest-supplied onerror handler due to an uncaught
+   * exception. This wrapper exists to ensure consistent behavior among the
+ * many places we need "top-level" catches. It is not a part of the domicile + * object because it is used even in DOM-less guest environments, but is in + * this file because it is a "browser" feature rather than a "JS" feature.
+   *
+   * handleUncaughtException attempts to never throw, even if the onerror
+   * handler is not a function, stringifying the exception throws, and so
+   * on.
+   *
+   * Usage:
+   * try {
+   *   ...guest callback of some sort, say an event handler...
+   * } catch (e) {
+   *   handleUncaughtException(tameWindow, e, 'event handler');
+   * }
+   *
+   * @param {Object} globalObj Object on which to find the onerror handler.
+   * @param {Error} error
+   * @param {string} context Script source URL if available, otherwise
+   *     a user-facing explanation of what kind of top-level handler caught
+   *     this error, in angle brackets; e.g. '<event listener>' or
+   *     '<setTimeout>'.
+   */
+  Domado_.handleUncaughtException =
+      cajaVM.constFunc(function(globalObj, error, context) {
+    // This is an approximate implementation of
+ // https://html.spec.whatwg.org/multipage/webappapis.html#runtime-script-errors
+    // The error event object is implicit.
+    try {
+      // Call with this == globalObj; this is intended behavior.
+      // Refs for arguments:
+ // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:onerroreventhandler + // https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers.onerror
+      globalObj.onerror(
+        'Uncaught ' + error,
+        context,
+        // TODO(kpreid): Once there is a standardized error stack trace
+        // interface, use it to recover more error information if we can.
+        -1,  // line number
+        -1,  // column number
+        error);
+    } catch (e) {
+      if (typeof console !== 'undefined') {
+        try {
+          console.error('Error while reporting guest script error: ', e);
+        } catch (metaError) {
+          console.error('Error while reporting error while reporting ' +
+              'guest script error. Sorry.');
+        }
+      }
+    }
   });
+
+  return cajaVM.constFunc(Domado_);
 })();

 // Exports for closure compiler.
=======================================
--- /branches/es53/src/com/google/caja/plugin/html-emitter.js Tue Apr 8 17:39:52 2014 UTC +++ /branches/es53/src/com/google/caja/plugin/html-emitter.js Thu Dec 18 19:31:03 2014 UTC
@@ -462,7 +462,7 @@
             // a crypto hash
var compiledModule = compileModule(scriptInnerText, opt_mitigate);
             try {
-              compiledModule(opt_domicile.window);
+              compiledModule(domicile.window);

               // Success.
               domicile.fireVirtualEvent(scriptNode, 'Event', 'load');
@@ -481,23 +481,21 @@
       // TODO(kpreid): Include error message appropriately
       domicile.fireVirtualEvent(scriptNode, 'Event', 'error');

-      // Dispatch to the onerror handler.
-      try {
-        // TODO(kpreid): This should probably become a full event dispatch.
-        // TODO: Should this happen inline or be dispatched out of band?
-        opt_domicile.window.onerror(
-            errorMessage,
-            // URL where error was raised.
-            // If this is an external load, then we need to use that URL,
-            // but for inline scripts we maintain the illusion by using the
-            // domicile.pseudoLocation.href which was passed here.
-            scriptBaseUri,
-            1  // Line number where error was raised.
-            // TODO: remap by inspection of the error if possible.
-            );
-      } catch (_) {
-        // Ignore problems dispatching error.
-      }
+ // TODO(kpreid): How should the virtual event on the node interact with + // this handling? Should they actually be the same event and cancel here?
+      //
+ // Note on window.Domado: We don't have a @requires dep here because we + // can run without Domado, but we know that Domado is loaded if we're in
+      // this code.
+      window.Domado.handleUncaughtException(
+          domicile.window,
+ // TODO(kpreid): should have an Error instance here, not a string.
+          errorMessage,
+          // URL where error was raised.
+          // If this is an external load, then we need to use that URL,
+          // but for inline scripts we maintain the illusion by using the
+          // domicile.pseudoLocation.href which was passed here.
+          scriptBaseUri);

       return errorMessage;
     }
=======================================
--- /branches/es53/src/com/google/caja/plugin/ses-frame-group.js Mon Sep 9 18:40:16 2013 UTC +++ /branches/es53/src/com/google/caja/plugin/ses-frame-group.js Thu Dec 18 19:31:03 2014 UTC
@@ -348,7 +348,12 @@
     }

     Q.when(promise, function (compiledFunc) {
-      var result = compiledFunc(imports);
+      var result = undefined;
+      try {
+        result = compiledFunc(imports);
+      } catch (e) {
+        Domado.handleUncaughtException(imports, e, gman.getUrl());
+      }
       if (opt_runDone) {
         opt_runDone(result);
       }
=======================================
--- /branches/es53/src/com/google/caja/ses/repairES5.js Fri Aug 29 23:08:55 2014 UTC +++ /branches/es53/src/com/google/caja/ses/repairES5.js Thu Dec 18 19:31:03 2014 UTC
@@ -31,6 +31,7 @@
  * //provides ses.ok, ses.okToLoad, ses.getMaxSeverity
  * //provides ses.is, ses.makeDelayedTamperProof
  * //provides ses.makeCallerHarmless, ses.makeArgumentsHarmless
+ * //provides ses.noFuncPoison
  * //provides ses.verifyStrictFunctionBody
  *
  * @author Mark S. Miller
@@ -176,6 +177,42 @@

   var builtInForEach = Array.prototype.forEach;

+  /**
+   * At https://bugs.ecmascript.org/show_bug.cgi?id=3113#c24 Jason
+   * Orendorff states the best draft for a simpler safe spec for the
+   * .caller and .argument properties on functions, that may or may
+   * not make it into ES6, but is on a track to standardization
+   * regardless. In Firefox 34 and
+   * https://bugzilla.mozilla.org/show_bug.cgi?id=969478 apparently
+   * this was implemented, or a reasonable approximation that we need
+   * to determine can be made SES-safe. Since this is a very different
+   * situation that the ES5 spec for these, we test which regime we
+   * seem to be in up front, so we can switch other logic based on this.
+   *
+   * If we seem to be in the new regime, then we try to delete the
+   * poison properties for simple safety, rather than trying to find
+   * subtle corner cases by which they might lose safety. If any of
+   * this fails, then we proceed under the assumption we're in the old
+   * regime.
+   *
+   * If noFuncPoison, then we're in the new regime made simply safe by
+   * these deletions, and we do not treat the names 'caller' and
+   * 'arguments' on functions as special.
+   */
+  var noFuncPoison =
+     Function.prototype.hasOwnProperty('caller') &&
+     Function.prototype.hasOwnProperty('arguments') &&
+     !strictFnSpecimen.hasOwnProperty('caller') &&
+     !strictFnSpecimen.hasOwnProperty('arguments') &&
+     !builtInMapMethod.hasOwnProperty('caller') &&
+     !builtInMapMethod.hasOwnProperty('arguments') &&
+     delete Function.prototype.caller &&
+     delete Function.prototype.arguments &&
+     !Function.prototype.hasOwnProperty('caller') &&
+     !Function.prototype.hasOwnProperty('arguments');
+  ses.noFuncPoison = noFuncPoison;
+
+
   /**
    * http://wiki.ecmascript.org/doku.php?id=harmony:egal
    */
@@ -307,7 +344,7 @@
       if (typeof obj === 'function') {
         for (i = 0, j = 0; i < len; i++) {
           name = list[i];
-          if (name !== 'caller' && name !== 'arguments') {
+ if (noFuncPoison || (name !== 'caller' && name !== 'arguments')) {
             callback(name, j);
             j++;
           }
@@ -1390,6 +1427,7 @@
       // Seen in IE9. Harmless by itself
       return false;
     }
+    if (desc === void 0 && noFuncPoison) { return false; }
     return 'getOwnPropertyDesciptor returned unexpected caller descriptor';
   }

@@ -1411,6 +1449,7 @@
       return 'hasOwnProperty failed with: ' + err;
     }
     if (answer) { return false; }
+    if (noFuncPoison) { return false; }
     return 'strict_function.hasOwnProperty("caller") was false';
   }

@@ -1505,6 +1544,7 @@
       return '("caller" in strict_func) failed with: ' + err;
     } finally {}
     if (answer) { return false; }
+    if (noFuncPoison) { return false; }
     return '("caller" in strict_func) was false.';
   }

@@ -1526,6 +1566,7 @@
       return '("arguments" in strict_func) failed with: ' + err;
     } finally {}
     if (answer) { return false; }
+    if (noFuncPoison) { return false; }
     return '("arguments" in strict_func) was false.';
   }

@@ -1550,6 +1591,7 @@
       // Seen on IE 9
       return true;
     }
+    if (caller === void 0 && noFuncPoison) { return false; }
     return 'Unexpected "caller": ' + caller;
   }

@@ -1574,6 +1616,7 @@
       // Seen on IE 9
       return true;
     }
+    if (args === void 0 && noFuncPoison) { return false; }
     return 'Unexpected arguments: ' + arguments;
   }

@@ -2443,7 +2486,55 @@
   }

   function getThrowTypeError() {
- return Object.getOwnPropertyDescriptor(getThrowTypeError, 'arguments').get;
+    return Object.getOwnPropertyDescriptor(arguments, 'caller').get;
+  }
+
+  /**
+   * A known strict function which returns its arguments object.
+   */
+  function strictArguments() { return arguments; }
+
+  /**
+   * A known sloppy function which returns its arguments object.
+   *
+   * Defined using Function so it'll be sloppy (not strict and not
+   * builtin).
+   */
+  var sloppyArguments = Function('return arguments;');
+
+  /**
+   * [[ThrowTypeError]] is not unique (even after whatever cleanup was
+   * already done during the noPoison testing above).
+   */
+  function test_THROWTYPEERROR_NOT_UNIQUE() {
+    var tte = getThrowTypeError();
+    if (typeof tte !== 'function') {
+      return 'Unexpected [[ThrowTypeError]]: ' + tte;
+    }
+    var others = [];
+    strictForEachFn([
+      [Function.prototype, 'Function.prototype', ['caller', 'arguments']],
+      [builtInMapMethod, 'builtin function', ['caller', 'arguments']],
+      [strictArguments, 'strict function', ['caller', 'arguments']],
+      [sloppyArguments, 'sloppy function', ['caller', 'arguments']],
+      [strictArguments(), 'strict arguments', ['caller', 'callee']],
+      [sloppyArguments(), 'sloppy arguments', ['caller', 'callee']]
+    ], function(triple) {
+      var base = triple[0];
+      var where = triple[1];
+      var names = triple[2];
+      strictForEachFn(names, function(name) {
+        var desc = Object.getOwnPropertyDescriptor(base, name);
+        if (!desc) { return; }
+        strictForEachFn(['get', 'set'], function (attr) {
+          var otherTTE = desc[attr];
+          if (!otherTTE || otherTTE === tte) { return; }
+          others.push(where + ' ' + attr + ' ' + name);
+        });
+      });
+    });
+    if (others.length === 0) { return false; }
+    return 'Multiple [[ThrowTypeError]]s: ' + others.join(', ');
   }

   /**
@@ -4288,6 +4379,17 @@
       sections: [],  // TODO(kpreid): cite when ES6 is final
       tests: []  // TODO(kpreid): cite when ES6 is final
     },
+    {
+      id: 'THROWTYPEERROR_NOT_UNIQUE',
+      description: '[[ThrowTypeError]] is not unique',
+      test: test_THROWTYPEERROR_NOT_UNIQUE,
+      repair: void 0,
+      preSeverity: severities.UNSAFE_SPEC_VIOLATION,
+      canRepair: false,
+      urls: [],
+      sections: [],
+      tests: []
+    },
     {
       id: 'THROWTYPEERROR_UNFROZEN',
       description: '[[ThrowTypeError]] is not frozen',
=======================================
--- /branches/es53/src/com/google/caja/ses/startSES.js Thu Jun 5 18:53:51 2014 UTC +++ /branches/es53/src/com/google/caja/ses/startSES.js Thu Dec 18 19:31:03 2014 UTC
@@ -19,6 +19,7 @@
  * WeakMap spec. Compatible with ES5-strict or anticipated ES6.
  *
  * //requires ses.makeCallerHarmless, ses.makeArgumentsHarmless
+ * //requires ses.noFuncPoison
  * //requires ses.verifyStrictFunctionBody
  * //optionally requires ses.mitigateSrcGotchas
  * //provides ses.startSES ses.resolveOptions, ses.securableWrapperSrc
@@ -371,7 +372,7 @@
    * Obtain the ES5 singleton [[ThrowTypeError]].
    */
   function getThrowTypeError() {
-    return gopd(getThrowTypeError, "arguments").get;
+    return gopd(arguments, 'caller').get;
   }


@@ -1539,7 +1540,7 @@
     }
     var diagnostic;

-    if (typeof base === 'function') {
+    if (typeof base === 'function' && !ses.noFuncPoison) {
       if (name === 'caller') {
         diagnostic = ses.makeCallerHarmless(base, path);
         // We can use a severity of SAFE here since if this isn't
=======================================
--- /branches/es53/tests/com/google/caja/plugin/MainBrowserTest.java Fri Aug 23 20:27:28 2013 UTC +++ /branches/es53/tests/com/google/caja/plugin/MainBrowserTest.java Thu Dec 18 19:31:03 2014 UTC
@@ -25,7 +25,9 @@
 @CatalogRunner.CatalogName("browser-tests.json")
 public class MainBrowserTest extends CatalogTestCase {
   /**
-   * Special case kludge for scan test which takes a long time under ES5/3.
+ * Special case kludge for scan test, which currently takes a very long time + * under the combination of webdriver+firefox only in ES5 mode, and always
+   * under ES5/3 mode.
    *
    * TODO(kpreid): Either extend the catalog with timeout data or add a way
* for the scanner to communicate it's making progress (which must be count-up
@@ -33,10 +35,8 @@
    */
   @Override
   protected int waitForCompletionTimeout() {
-    if (entry.getLabel().startsWith("guest-scan-es53-")) {
-      return 300000;    // msec
-    } else if (entry.getLabel().startsWith("guest-scan-es5-")) {
-      return 60000;     // msec
+    if (entry.getLabel().startsWith("guest-scan-")) {
+      return 1000 * 1000;  // milliseconds
     } else {
       return super.waitForCompletionTimeout();
     }
=======================================
--- /branches/es53/tests/com/google/caja/plugin/jsunit.js Fri Aug 30 17:29:12 2013 UTC +++ /branches/es53/tests/com/google/caja/plugin/jsunit.js Thu Dec 18 19:31:03 2014 UTC
@@ -239,6 +239,8 @@

 /** Run tests. */
 function jsunitRun(opt_testNames, opt_asyncEval) {
+ // TODO(kpreid): Arguments are incoherent. opt_testNames actually represents + // varargs, so the second argument is used as part of that AND opt_asyncEval.
   if (jsunit.alreadyRan) { return; }
   jsunit.alreadyRan = true;

=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-cajajs-invocation.js Wed Oct 2 23:01:41 2013 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-cajajs-invocation.js Thu Dec 18 19:31:03 2014 UTC
@@ -99,6 +99,41 @@
     }
   });

+  function testScriptError(hasDOM) {
+    caja.load(hasDOM ? createDiv() : undefined, uriPolicy,
+        jsunitCallback(function(frame) {
+      var url = 'http://caja-test-error-page.invalid';
+      var onerrorFired = 0;
+      frame.code(
+          url,
+          'text/javascript',
+          'throw new Error("toplevel error");')
+          .api({
+            onerror: frame.tame(frame.markFunction(
+                jsunitCallback(function(msg, loc, line, col, error) {
+              onerrorFired++;
+              assertEquals('Uncaught Error: toplevel error', msg);
+              assertEquals(url, loc);
+              assertEquals(-1, line);
+              assertEquals(-1, col);
+              assertEquals('[object Error]',
+                  Object.prototype.toString.call(error));
+            })))
+          })
+          .run(jsunitCallback(function(result) {
+            assertEquals(1, onerrorFired);
+            jsunitPass();
+          }));
+    }));
+  }
+ // Should in principle be working in ES5/3 mode too, but this is probably a
+  // wontfix.
+  jsunitRegisterIf(inES5Mode, 'testScriptErrorWithDom', function() {
+      testScriptError(true); });
+
+  jsunitRegisterIf(inES5Mode, 'testScriptErrorWithoutDom', function() {
+      testScriptError(false); });
+
   jsunitRegister('testDefaultHeight', function testDefaultHeight() {
     var hostPageDiv = createDiv();

=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-domado-events-guest.html Wed Dec 11 01:15:23 2013 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-domado-events-guest.html Thu Dec 18 19:31:03 2014 UTC
@@ -14,6 +14,18 @@
  - limitations under the License.
 -->

+<script type="text/javascript">
+  function installOneShotOnerror(testId, expectedMessage) {
+    var savedOnerror = window.onerror;
+    window.onerror = function(message, url, line) {
+      assertEquals(expectedMessage, message);
+      assertTrue('window == ' + this, window == this);
+      window.onerror = savedOnerror;
+      pass(testId);
+    };
+  }
+</script>
+
 <div class="clickme testcontainer" id="testEventBubble">
   testEventBubble
   <input type="checkbox">
@@ -39,14 +51,9 @@
 </p>
 <script type="text/javascript">
   window.die = function die() {
-    window.onerror = (function (onerror) {
-      return function (message, url, line) {
-          assertEquals('died!', message);
-          pass('testExceptionInEventHandler');
-          window.onerror = onerror;
-        };
-    })(onerror);
-
+    installOneShotOnerror(
+        'testExceptionInEventHandler',
+        'Uncaught Error: died!');
     throw new Error('died!');
   };
   jsunitRegister('testExceptionInEventHandler',
@@ -458,6 +465,20 @@
   });
 </script>

+<p class="testcontainer" id="testTimeoutError">
+  testTimeoutError (error handling in setTimeout)
+</p>
+<script type="text/javascript">
+  jsunitRegister('testTimeoutError', function() {
+    setTimeout(function() {  // NOT a jsunitCallback, must let error out
+      installOneShotOnerror(
+          'testTimeoutError',
+          'Uncaught Error: foo');
+      throw new Error('foo');
+    }, 0);
+  });
+</script>
+
 <p class="testcontainer" id="testRequestAnimationFrame">
   requestAnimationFrame<br>
 </p>
@@ -771,7 +792,6 @@
 <script type="text/javascript">
   jsunitRegister('testXhrAsync',
                  function testXhrAsync() {
-    var cont = document.getElementById('testXhrAsync');
     var xhr = new window.XMLHttpRequest();
     xhr.open('GET', './xhrTest.txt', true);
     var asyncFlag = 'PRE';
@@ -789,6 +809,24 @@
   });
 </script>

+<div id="testXhrError" class="testcontainer">XHR callback errors</div>
+<script type="text/javascript">
+  jsunitRegister('testXhrError',
+                 function testXhrError() {
+    var xhr = new window.XMLHttpRequest();
+    xhr.open('GET', './xhrTest.txt', true);
+    xhr.onreadystatechange = function(event) {  // NOT a jsunitCallback
+      if (event.target.readyState === 4) {
+        installOneShotOnerror(
+            'testXhrError',
+            'Uncaught testXhrError');
+        throw 'testXhrError';
+      }
+    };
+    xhr.send();
+  });
+</script>
+
 <p id="testEventMutation" class="clickme testcontainer">testEventMutation
     <span>(Assignment to properties of events)</span></p>
 <script type="text/javascript">
=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-scan-guest.js Fri Sep 19 22:00:17 2014 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-scan-guest.js Thu Dec 18 19:31:03 2014 UTC
@@ -572,16 +572,21 @@
     argsByAnyFrame('Function.prototype.toString',
         // TODO test invocation on Function.prototype itself
         G.tuple(G.value(THIS), G.tuple()));
-    [function guestFn(){}, window.setTimeout].forEach(function(f) {
-      if (!inES5Mode) {
+
+    if (!inES5Mode) {
+      [function guestFn(){}, window.setTimeout].forEach(function(f) {
         // Function.prototype() is necessarily broken in ES5/3
         var proto = Object.getPrototypeOf(f);
         expectedAlwaysThrow.setByIdentity(proto, true);
-      }
-      expectedAlwaysThrow.setByIdentity(getGetter(f, 'arguments'), true);
-      expectedAlwaysThrow.setByIdentity(getGetter(f, 'caller'), true);
-    });

+        // [[ThrowTypeError]] has multiple variants in ES5/3
+        expectedAlwaysThrow.setByIdentity(getGetter(f, 'arguments'), true);
+        expectedAlwaysThrow.setByIdentity(getGetter(f, 'caller'), true);
+      });
+    } else {
+      argsByIdentity(cajaVM['[[ThrowTypeError]]'], genAllCall());
+ expectedAlwaysThrow.setByIdentity(cajaVM['[[ThrowTypeError]]'], true);
+    }

     argsByIdentity(Number, genAllCall(genSmallInteger));
     argsByAnyFrame('Number.prototype.toExponential',
=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-taming-tamed-guest.html Wed Aug 14 17:51:03 2013 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-taming-tamed-guest.html Thu Dec 18 19:31:03 2014 UTC
@@ -142,7 +142,14 @@
       Object: getTamingFrameObject('Object'),
       Function: getTamingFrameObject('cajaVM.sharedImports.Function'),
       Array: getTamingFrameObject('Array'),
-      Error: getTamingFrameObject('Error')
+      Error: getTamingFrameObject('Error'),
+ // This object just to exercise this test's self-test, because the current + // state of affairs is that on Firefox there are no accessors in the above
+      // primordials.
+      _selfTestDummy: Object.freeze(Object.create({}, { accessorProp: {
+          get: Object.freeze(function() { return 10; }),
+          set: Object.freeze(function(value) {})
+      }}))
     };

     // Sanity checks to ensure our test has run

--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to