Bug 703176 - Ensure all browser chrome mochitests do fail when uncaught JS exceptions occur. (v1.1) r=jmaher
authorCameron McCormack <cam@mcc.id.au>
Thu, 01 Dec 2011 18:22:14 +1100
changeset 81896 7fe6db51869d248d5b3aa4a2c1a62fd62d10648e
parent 81895 8576199c846c2c9b08a3c1156c564dc48506b280
child 81897 de720961a78db6af0b412da18346eb9151a57863
push idunknown
push userunknown
push dateunknown
reviewersjmaher
bugs703176
milestone11.0a1
Bug 703176 - Ensure all browser chrome mochitests do fail when uncaught JS exceptions occur. (v1.1) r=jmaher
browser/base/content/test/browser_customize.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_pageinfo.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js
browser/components/sessionstore/test/browser/browser_586147.js
browser/components/sessionstore/test/browser/browser_607016.js
browser/devtools/highlighter/test/browser_inspector_bug_665880.js
browser/devtools/highlighter/test/browser_inspector_bug_690361.js
browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js
browser/devtools/highlighter/test/browser_inspector_infobar.js
browser/devtools/highlighter/test/browser_inspector_initialization.js
browser/devtools/highlighter/test/browser_inspector_registertools.js
browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
browser/devtools/highlighter/test/browser_inspector_tab_switch.js
browser/devtools/highlighter/test/browser_inspector_treeSelection.js
browser/devtools/styleinspector/test/browser/browser_bug683672.js
browser/devtools/styleinspector/test/browser/browser_styleinspector.js
image/test/browser/browser_image.js
testing/mochitest/browser-test.js
testing/mochitest/tests/SimpleTest/SimpleTest.js
toolkit/components/startup/tests/browser/browser_bug511456.js
toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js
toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js
toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js
toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js
toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js
toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js
--- a/browser/base/content/test/browser_customize.js
+++ b/browser/base/content/test/browser_customize.js
@@ -1,11 +1,12 @@
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   var frame = document.getElementById("customizeToolbarSheetIFrame");
   frame.addEventListener("load", testCustomizeFrameLoadedPre, true);
 
   document.getElementById("cmd_CustomizeToolbars").doCommand();
 }
 
 function testCustomizeFrameLoadedPre(){
   // This load listener can be called before
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_pageinfo.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_pageinfo.js
@@ -94,9 +94,10 @@ function test() {
       gBrowser.removeCurrentTab();
       gBrowser.removeCurrentTab();
 
       finish();
     });
   });
 
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
@@ -40,16 +40,17 @@
 
 function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
   let cm = Cc["@mozilla.org/cookiemanager;1"].
            getService(Ci.nsICookieManager);
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   const TEST_URL = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/title.sjs";
 
   function waitForCleanup(aCallback) {
     // delete all cookies
     cm.removeAll();
     // delete all history items
     waitForClearHistory(aCallback);
--- a/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js
+++ b/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js
@@ -13,16 +13,17 @@
 //
 // 3.  [backwards compat] Use a stringified state as formdata when opening about:sessionrestore
 // 3a. Make sure there are nodes in the tree on about:sessionrestore (skipped, checking formdata is sufficient)
 // 3b. Check that there are no backslashes in the formdata
 // 3c. Check that formdata (via getBrowserState) doesn't require JSON.parse
 
 function test() {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
   let crashState = { windows: [{ tabs: [{ entries: [{ url: "about:mozilla" }] }]}]};
 
   let pagedata = { url: "about:sessionrestore",
                    formdata: { "#sessionData": crashState } };
   let state = { windows: [{ tabs: [{ entries: [pagedata] }] }] };
 
--- a/browser/components/sessionstore/test/browser/browser_586147.js
+++ b/browser/components/sessionstore/test/browser/browser_586147.js
@@ -39,16 +39,17 @@ function observeOneRestore(callback) {
   Services.obs.addObserver(function() {
     Services.obs.removeObserver(arguments.callee, topic, false);
     callback();
   }, topic, false);
 };
 
 function test() {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
   let hiddenTab = gBrowser.addTab();
 
   is(gBrowser.visibleTabs.length, 2, "should have 2 tabs before hiding");
   gBrowser.showOnlyTheseTabs([origTab]);
   is(gBrowser.visibleTabs.length, 1, "only 1 after hiding");
--- a/browser/components/sessionstore/test/browser/browser_607016.js
+++ b/browser/components/sessionstore/test/browser/browser_607016.js
@@ -47,16 +47,17 @@ function cleanup() {
   } catch (e) {}
   ss.setBrowserState(stateBackup);
   executeSoon(finish);
 }
 
 function test() {
   /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly **/
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
   Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
--- a/browser/devtools/highlighter/test/browser_inspector_bug_665880.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_665880.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   let doc;
   let objectNode;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
--- a/browser/devtools/highlighter/test/browser_inspector_bug_690361.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_690361.js
@@ -122,16 +122,17 @@ function finishInspectorTests()
     gBrowser.removeCurrentTab();
     finish();
   });
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,basic tests for inspector";
--- a/browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js
+++ b/browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js
@@ -106,16 +106,17 @@ function inspectorRuleTrap()
   Services.obs.removeObserver(inspectorRuleTrap,
     InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
   is(InspectorUI.ruleView.doc.documentElement.children.length, 1, "RuleView elements.length == 1");
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   tab1 = gBrowser.addTab();
   gBrowser.selectedTab = tab1;
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
       true);
     waitForFocus(inspectorTabOpen1, content);
   }, true);
--- a/browser/devtools/highlighter/test/browser_inspector_infobar.js
+++ b/browser/devtools/highlighter/test/browser_inspector_infobar.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   let doc;
   let nodes;
   let cursor;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -213,16 +213,17 @@ function finishInspectorTests()
 
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,basic tests for inspector";
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js
@@ -208,16 +208,17 @@ function finishUp() {
   gBrowser.removeCurrentTab();
   InspectorUI.initTools = initToolsMethod;
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
   
   content.location = "data:text/html,registertool tests for inspector";
--- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
+++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
@@ -132,16 +132,17 @@ function ruleViewOpened2()
   gBrowser.removeCurrentTab();
   InspectorUI.closeInspectorUI();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   tab1 = gBrowser.addTab();
   gBrowser.selectedTab = tab1;
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
       true);
     waitForFocus(inspectorTabOpen1, content);
   }, true);
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -258,16 +258,17 @@ function inspectorTabUnload1(evt)
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   tab1 = gBrowser.addTab();
   gBrowser.selectedTab = tab1;
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
       true);
     waitForFocus(inspectorTabOpen1, content);
   }, true);
--- a/browser/devtools/highlighter/test/browser_inspector_treeSelection.js
+++ b/browser/devtools/highlighter/test/browser_inspector_treeSelection.js
@@ -100,16 +100,17 @@ function finishUp() {
   doc = h1 = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,basic tests for inspector";
--- a/browser/devtools/styleinspector/test/browser/browser_bug683672.js
+++ b/browser/devtools/styleinspector/test/browser/browser_bug683672.js
@@ -9,16 +9,17 @@ let stylePanel;
 
 const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/test/browser/browser_bug683672.html";
 
 Cu.import("resource:///modules/devtools/CssHtmlTree.jsm");
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   addTab(TEST_URI);
   browser.addEventListener("load", tabLoaded, true);
 }
 
 function tabLoaded()
 {
   browser.removeEventListener("load", tabLoaded, true);
   doc = content.document;
--- a/browser/devtools/styleinspector/test/browser/browser_styleinspector.js
+++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector.js
@@ -74,16 +74,17 @@ function finishUp()
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,basic style inspector tests";
--- a/image/test/browser/browser_image.js
+++ b/image/test/browser/browser_image.js
@@ -182,11 +182,12 @@ function nextTest() {
   if (tests.length == 0) {
     finish();
     return;
   }
   tests.shift()();
 }
 
 function test() {
+  ignoreAllUncaughtExceptions();
   nextTest();
 }
 
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -205,16 +205,20 @@ Tester.prototype = {
         try {
           func.apply(testScope);
         }
         catch (ex) {
           this.currentTest.addResult(new testResult(false, "Cleanup function threw an exception", ex, false));
         }
       };
 
+      if (this.SimpleTest.isExpectingUncaughtException()) {
+        this.currentTest.addResult(new testResult(false, "expectUncaughtException was called but no uncaught exception was detected!", "", false));
+      }
+
       // Clear document.popupNode.  The test could have set it to a custom value
       // for its own purposes, nulling it out it will go back to the default
       // behavior of returning the last opened popup.
       document.popupNode = null;
 
       // Note the test run time
       let time = Date.now() - this.lastStartTime;
       this.dumper.dump("INFO TEST-END | " + this.currentTest.path + " | finished in " + time + "ms\n");
@@ -245,26 +249,28 @@ Tester.prototype = {
       this.currentTestIndex++;
       this.execTest();
     }).bind(this));
   },
 
   execTest: function Tester_execTest() {
     this.dumper.dump("TEST-START | " + this.currentTest.path + "\n");
 
+    this.SimpleTest.reset();
+
     // Load the tests into a testscope
     this.currentTest.scope = new testScope(this, this.currentTest);
 
     // Import utils in the test scope.
     this.currentTest.scope.EventUtils = this.EventUtils;
     this.currentTest.scope.SimpleTest = this.SimpleTest;
     this.currentTest.scope.gTestPath = this.currentTest.path;
 
     // Override SimpleTest methods with ours.
-    ["ok", "is", "isnot", "todo", "todo_is", "todo_isnot"].forEach(function(m) {
+    ["ok", "is", "isnot", "todo", "todo_is", "todo_isnot", "info"].forEach(function(m) {
       this.SimpleTest[m] = this[m];
     }, this.currentTest.scope);
 
     //load the tools to work with chrome .jar and remote
     try {
       this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", this.currentTest.scope);
     } catch (ex) { /* no chrome-harness tools */ }
 
@@ -291,17 +297,23 @@ Tester.prototype = {
         this.currentTest.scope.waitForExplicitFinish();
         var result = this.currentTest.scope.generatorTest();
         this.currentTest.scope.__generator = result;
         result.next();
       } else {
         this.currentTest.scope.test();
       }
     } catch (ex) {
-      this.currentTest.addResult(new testResult(false, "Exception thrown", ex, false));
+      var isExpected = !!this.SimpleTest.isExpectingUncaughtException();
+      if (!this.SimpleTest.isIgnoringAllUncaughtExceptions()) {
+        this.currentTest.addResult(new testResult(isExpected, "Exception thrown", ex, false));
+        this.SimpleTest.expectUncaughtException(false);
+      } else {
+        this.currentTest.addResult(new testMessage("Exception thrown: " + ex));
+      }
       this.currentTest.scope.finish();
     }
 
     // If the test ran synchronously, move to the next test, otherwise the test
     // will trigger the next test when it is done.
     if (this.currentTest.scope.__done) {
       this.nextTest();
     }
@@ -374,45 +386,44 @@ function testMessage(aName) {
   this.info = true;
   this.result = "TEST-INFO";
 }
 
 // Need to be careful adding properties to this object, since its properties
 // cannot conflict with global variables used in tests.
 function testScope(aTester, aTest) {
   this.__tester = aTester;
-  this.__browserTest = aTest;
 
   var self = this;
   this.ok = function test_ok(condition, name, diag, stack) {
-    self.__browserTest.addResult(new testResult(condition, name, diag, false,
-                                                stack ? stack : Components.stack.caller));
+    aTest.addResult(new testResult(condition, name, diag, false,
+                                   stack ? stack : Components.stack.caller));
   };
   this.is = function test_is(a, b, name) {
     self.ok(a == b, name, "Got " + a + ", expected " + b, false,
             Components.stack.caller);
   };
   this.isnot = function test_isnot(a, b, name) {
     self.ok(a != b, name, "Didn't expect " + a + ", but got it", false,
             Components.stack.caller);
   };
   this.todo = function test_todo(condition, name, diag, stack) {
-    self.__browserTest.addResult(new testResult(!condition, name, diag, true,
-                                                stack ? stack : Components.stack.caller));
+    aTest.addResult(new testResult(!condition, name, diag, true,
+                                   stack ? stack : Components.stack.caller));
   };
   this.todo_is = function test_todo_is(a, b, name) {
     self.todo(a == b, name, "Got " + a + ", expected " + b,
               Components.stack.caller);
   };
   this.todo_isnot = function test_todo_isnot(a, b, name) {
     self.todo(a != b, name, "Didn't expect " + a + ", but got it",
               Components.stack.caller);
   };
   this.info = function test_info(name) {
-    self.__browserTest.addResult(new testMessage(name));
+    aTest.addResult(new testMessage(name));
   };
 
   this.executeSoon = function test_executeSoon(func) {
     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
 
     tm.mainThread.dispatch({
       run: function() {
         func();
@@ -433,17 +444,23 @@ function testScope(aTester, aTest) {
     }
 
     try {
       self.__generator.send(arg);
     } catch (ex if ex instanceof StopIteration) {
       // StopIteration means test is finished.
       self.finish();
     } catch (ex) {
-      aTest.addResult(new testResult(false, "Exception thrown", ex, false));
+      var isExpected = !!self.SimpleTest.isExpectingUncaughtException();
+      if (!self.SimpleTest.isIgnoringAllUncaughtExceptions()) {
+        aTest.addResult(new testResult(isExpected, "Exception thrown", ex, false));
+        self.SimpleTest.expectUncaughtException(false);
+      } else {
+        aTest.addResult(new testMessage("Exception thrown: " + ex));
+      }
       self.finish();
     }
   };
 
   this.waitForExplicitFinish = function test_waitForExplicitFinish() {
     self.__done = false;
   };
 
@@ -462,29 +479,26 @@ function testScope(aTester, aTest) {
   this.requestLongerTimeout = function test_requestLongerTimeout(aFactor) {
     self.__timeoutFactor = aFactor;
   };
 
   this.copyToProfile = function test_copyToProfile(filename) {
     self.SimpleTest.copyToProfile(filename);
   };
 
-  this.expectUncaughtException = function test_expectUncaughtException() {
-    self.SimpleTest.expectUncaughtException();
+  this.expectUncaughtException = function test_expectUncaughtException(aExpecting) {
+    self.SimpleTest.expectUncaughtException(aExpecting);
   };
 
-  this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions() {
-    self.SimpleTest.ignoreAllUncaughtExceptions();
+  this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions(aIgnoring) {
+    self.SimpleTest.ignoreAllUncaughtExceptions(aIgnoring);
   };
 
   this.finish = function test_finish() {
     self.__done = true;
-    if (self.SimpleTest._expectingUncaughtException) {
-      self.ok(false, "expectUncaughtException was called but no uncaught exception was detected!");
-    }
     if (self.__waitTimer) {
       self.executeSoon(function() {
         if (self.__done && self.__waitTimer) {
           clearTimeout(self.__waitTimer);
           self.__waitTimer = null;
           self.__tester.nextTest();
         }
       });
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -688,27 +688,53 @@ SimpleTest.expectChildProcessCrash = fun
         parentRunner.expectChildProcessCrash();
     }
 };
 
 /**
  * Indicates to the test framework that the next uncaught exception during
  * the test is expected, and should not cause a test failure.
  */
-SimpleTest.expectUncaughtException = function () {
-    SimpleTest._expectingUncaughtException = true;
+SimpleTest.expectUncaughtException = function (aExpecting) {
+    SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting;
+};
+
+/**
+ * Returns whether the test has indicated that it expects an uncaught exception
+ * to occur.
+ */
+SimpleTest.isExpectingUncaughtException = function () {
+    return SimpleTest._expectingUncaughtException;
 };
 
 /**
  * Indicates to the test framework that all of the uncaught exceptions
  * during the test are known problems that should be fixed in the future,
  * but which should not cause the test to fail currently.
  */
-SimpleTest.ignoreAllUncaughtExceptions = function () {
-    SimpleTest._ignoringAllUncaughtExceptions = true;
+SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) {
+    SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring;
+};
+
+/**
+ * Returns whether the test has indicated that all uncaught exceptions should be
+ * ignored.
+ */
+SimpleTest.isIgnoringAllUncaughtExceptions = function () {
+    return SimpleTest._ignoringAllUncaughtExceptions;
+};
+
+/**
+ * Resets any state this SimpleTest object has.  This is important for
+ * browser chrome mochitests, which reuse the same SimpleTest object
+ * across a run.
+ */
+SimpleTest.reset = function () {
+    SimpleTest._ignoringAllUncaughtExceptions = false;
+    SimpleTest._expectingUncaughtException = false;
 };
 
 if (isPrimaryTestWindow) {
     addLoadEvent(function() {
         if (SimpleTest._stopOnLoad) {
             SimpleTest.finish();
         }
     });
@@ -919,32 +945,31 @@ var isnot = SimpleTest.isnot;
 var todo = SimpleTest.todo;
 var todo_is = SimpleTest.todo_is;
 var todo_isnot = SimpleTest.todo_isnot;
 var isDeeply = SimpleTest.isDeeply;
 var info = SimpleTest.info;
 
 var gOldOnError = window.onerror;
 window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) {
-    var funcIdentifier = "[SimpleTest/SimpleTest.js, window.onerror]";
-
     // Log the message.
     // XXX Chrome mochitests sometimes trigger this window.onerror handler,
     // but there are a number of uncaught JS exceptions from those tests.
     // For now, for tests that self identify as having unintentional uncaught
     // exceptions, just dump it so that the error is visible but doesn't cause
     // a test failure.  See bug 652494.
-    var message = "An error occurred: " + errorMsg + " at " + url + ":" + lineNumber;
     var href = SpecialPowers.getPrivilegedProps(window, 'location.href');
     var isExpected = !!SimpleTest._expectingUncaughtException;
+    var message = "an " + (isExpected ? "" : "un") + "expected uncaught JS exception reported through window.onerror";
+    var error = errorMsg + " at " + url + ":" + lineNumber;
     if (!SimpleTest._ignoringAllUncaughtExceptions) {
-        SimpleTest.ok(isExpected, funcIdentifier, message);
+        SimpleTest.ok(isExpected, message, error);
         SimpleTest._expectingUncaughtException = false;
     } else {
-        SimpleTest.todo(false, funcIdentifier, message);
+        SimpleTest.todo(false, message + ": " + error);
     }
     // There is no Components.stack.caller to log. (See bug 511888.)
 
     // Call previous handler.
     if (gOldOnError) {
         try {
             // Ignore return value: always run default handler.
             gOldOnError(errorMsg, url, lineNumber);
--- a/toolkit/components/startup/tests/browser/browser_bug511456.js
+++ b/toolkit/components/startup/tests/browser/browser_bug511456.js
@@ -83,16 +83,17 @@ var Watcher = {
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 }
 
 function test() {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   Services.wm.addListener(Watcher);
 
   var win2 = window.openDialog(location, "", "chrome,all,dialog=no", "about:blank");
   win2.addEventListener("load", function() {
     win2.removeEventListener("load", arguments.callee, false);
 
     gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js
@@ -1,13 +1,14 @@
 // ----------------------------------------------------------------------------
 // Test whether passing a simple string to InstallTrigger.install throws an
 // exception
 function test() {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   var triggers = encodeURIComponent(JSON.stringify(TESTROOT + "unsigned.xpi"));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     // Allow the in-page load handler to run first
     executeSoon(page_loaded);
   }, true);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js
@@ -1,13 +1,14 @@
 // ----------------------------------------------------------------------------
 // Test whether passing an undefined url InstallTrigger.install throws an
 // exception
 function test() {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Unsigned XPI": {
       URL: undefined
     }
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // ----------------------------------------------------------------------------
 // Tests that cancelling multiple installs doesn't fail
 function test() {
+  ignoreAllUncaughtExceptions();
   Harness.installConfirmCallback = confirm_install;
   Harness.installEndedCallback = install_ended;
   Harness.installsCompletedCallback = finish_test;
   Harness.setup();
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js
@@ -1,11 +1,12 @@
 // ----------------------------------------------------------------------------
 // Test whether an InstallTrigger.install call fails when xpinstall is disabled
 function test() {
+  ignoreAllUncaughtExceptions();
   Harness.installDisabledCallback = install_disabled;
   Harness.installBlockedCallback = allow_blocked;
   Harness.installConfirmCallback = confirm_install;
   Harness.setup();
 
   Services.prefs.setBoolPref("xpinstall.enabled", false);
 
   var triggers = encodeURIComponent(JSON.stringify({
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js
@@ -1,13 +1,14 @@
 // ----------------------------------------------------------------------------
 // Test whether an install fails if the url is a local file when requested from
 // web content
 function test() {
   waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
 
   var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                      .getService(Components.interfaces.nsIChromeRegistry);
   
   var chromeroot = getChromeRoot(gTestPath);              
   try {
     var xpipath = cr.convertChromeURL(makeURI(chromeroot + "unsigned.xpi")).spec;
   } catch (ex) {
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js
@@ -1,12 +1,13 @@
 // ----------------------------------------------------------------------------
 // Tests installing an unsigned add-on through a navigation. Should not be
 // blocked since the referer is whitelisted.
 function test() {
+  ignoreAllUncaughtExceptions();
   Harness.installConfirmCallback = confirm_install;
   Harness.installsCompletedCallback = finish_test;
   Harness.setup();
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({