Bug 966452 part 1. Refactor the js_ReportUncaughtException to produce a (message, JSErrorReport*) pair before reporting. r=waldo and including the fix for bug 1034616 to fix JS tests to deal with this, r=jorendorff. r=terrence on the AutoStableStringChars bits
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 04 Jul 2014 01:24:54 -0400
changeset 217034 a2f5fa870c8aaabf3ca79e925988baece89391e1
parent 217033 7784c83c72a20a527652ddc2e4e6b8b47cad6085
child 217035 f169e4abd298a70cd6b3ecd3a98f33b76f3b17bd
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswaldo, jorendorff, terrence
bugs966452, 1034616
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 966452 part 1. Refactor the js_ReportUncaughtException to produce a (message, JSErrorReport*) pair before reporting. r=waldo and including the fix for bug 1034616 to fix JS tests to deal with this, r=jorendorff. r=terrence on the AutoStableStringChars bits
js/src/jit-test/tests/debug/Frame-onPop-generators-02.js
js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
js/src/jsexn.cpp
js/src/jsfriendapi.h
js/src/vm/String.cpp
js/src/vm/String.h
toolkit/components/ctypes/tests/chrome/test_ctypes.xul
--- a/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js
@@ -1,8 +1,10 @@
+// |jit-test| error: fit
+
 // Throwing an exception from an onPop handler when yielding terminates the debuggee
 // but does not close the generator-iterator.
 
 var g = newGlobal();
 var dbg = new Debugger;
 var gw = dbg.addDebuggee(g);
 dbg.onDebuggerStatement = function handleDebugger(frame) {
     frame.onPop = function (c) {
--- a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
+++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
@@ -1,8 +1,10 @@
+// |jit-test| error: fit
+
 // Throwing an exception from an onPop handler when yielding terminates the debuggee
 // but does not close the generator-iterator.
 
 load(libdir + 'iteration.js')
 
 var g = newGlobal();
 var dbg = new Debugger;
 var gw = dbg.addDebuggee(g);
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -703,67 +703,107 @@ js_ReportUncaughtException(JSContext *cx
 {
     if (!cx->isExceptionPending())
         return true;
 
     RootedValue exn(cx);
     if (!cx->getPendingException(&exn))
         return false;
 
+    cx->clearPendingException();
+
+    ErrorReport err(cx);
+    if (!err.init(cx, exn)) {
+        cx->clearPendingException();
+        return false;
+    }
+
+    cx->setPendingException(exn);
+    CallErrorReporter(cx, err.message(), err.report());
+    cx->clearPendingException();
+    return true;
+}
+
+ErrorReport::ErrorReport(JSContext *cx)
+  : reportp(nullptr),
+    message_(nullptr),
+    ownedMessage(nullptr),
+    str(cx),
+    strChars(cx),
+    exnObject(cx)
+{
+}
+
+ErrorReport::~ErrorReport()
+{
+    if (!ownedMessage)
+        return;
+
+    js_free(ownedMessage);
+    if (ownedReport.messageArgs) {
+        /*
+         * js_ExpandErrorArguments owns its messageArgs only if it had to
+         * inflate the arguments (from regular |char *|s), which is always in
+         * our case.
+         */
+        size_t i = 0;
+        while (ownedReport.messageArgs[i])
+            js_free(const_cast<jschar*>(ownedReport.messageArgs[i++]));
+        js_free(ownedReport.messageArgs);
+    }
+    js_free(const_cast<jschar*>(ownedReport.ucmessage));
+}
+
+bool
+ErrorReport::init(JSContext *cx, HandleValue exn)
+{
+    MOZ_ASSERT(!cx->isExceptionPending());
+
     /*
      * Because ToString below could error and an exception object could become
-     * unrooted, we must root exnObject.  Later, if exnObject is non-null, we
-     * need to root other intermediates, so allocate an operand stack segment
-     * to protect all of these values.
+     * unrooted, we must root our exception object, if any.
      */
-    RootedObject exnObject(cx);
-    if (exn.isPrimitive()) {
-        exnObject = nullptr;
-    } else {
-        exnObject = exn.toObjectOrNull();
+    if (exn.isObject()) {
+        exnObject = &exn.toObject();
+        reportp = js_ErrorFromException(cx, exnObject);
     }
 
-    JS_ClearPendingException(cx);
-    JSErrorReport *reportp = exnObject ? js_ErrorFromException(cx, exnObject)
-                                       : nullptr;
-
     // Be careful not to invoke ToString if we've already successfully extracted
     // an error report, since the exception might be wrapped in a security
     // wrapper, and ToString-ing it might throw.
-    RootedString str(cx);
     if (reportp)
         str = ErrorReportToString(cx, reportp);
     else
         str = ToString<CanGC>(cx, exn);
 
-    JSErrorReport report;
-
     // If js_ErrorFromException didn't get us a JSErrorReport, then the object
     // was not an ErrorObject, security-wrapped or otherwise. However, it might
     // still quack like one. Give duck-typing a chance.  We start by looking for
     // "filename" (all lowercase), since that's where DOMExceptions store their
     // filename.  Then we check "fileName", which is where Errors store it.  We
     // have to do it in that order, because DOMExceptions have Error.prototype
     // on their proto chain, and hence also have a "fileName" property, but its
     // value is "".
     const char *filename_str = "filename";
-    JSAutoByteString filename;
-    AutoStableStringChars strChars(cx);
     if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
     {
         // Temporary value for pulling properties off of duck-typed objects.
         RootedValue val(cx);
 
         RootedString name(cx);
         if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
             name = val.toString();
+        else
+            cx->clearPendingException();
 
         RootedString msg(cx);
         if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
             msg = val.toString();
+        else
+            cx->clearPendingException();
 
         // If we have the right fields, override the ToString we performed on
         // the exception object above with something built out of its quacks
         // (i.e. as much of |NameQuack: MessageQuack| as we can make).
         //
         // It would be nice to use ErrorReportToString here, but we can't quite
         // do it - mostly because we'd need to figure out what JSExnType |name|
         // corresponds to, which may not be any JSExnType at all.
@@ -782,74 +822,115 @@ js_ReportUncaughtException(JSContext *cx
         } else if (msg) {
             str = msg;
         }
 
         if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
             JSString *tmp = ToString<CanGC>(cx, val);
             if (tmp)
                 filename.encodeLatin1(cx, tmp);
+            else
+                cx->clearPendingException();
+        } else {
+            cx->clearPendingException();
         }
 
         uint32_t lineno;
         if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
             !ToUint32(cx, val, &lineno))
         {
+            cx->clearPendingException();
             lineno = 0;
         }
 
         uint32_t column;
         if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
             !ToUint32(cx, val, &column))
         {
+            cx->clearPendingException();
             column = 0;
         }
 
-        reportp = &report;
-        PodZero(&report);
-        report.filename = filename.ptr();
-        report.lineno = (unsigned) lineno;
-        report.exnType = int16_t(JSEXN_NONE);
-        report.column = (unsigned) column;
+        reportp = &ownedReport;
+        PodZero(&ownedReport);
+        ownedReport.filename = filename.ptr();
+        ownedReport.lineno = lineno;
+        ownedReport.exnType = int16_t(JSEXN_NONE);
+        ownedReport.column = column;
         if (str) {
             // Note that using |str| for |ucmessage| here is kind of wrong,
             // because |str| is supposed to be of the format
             // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
             // correspond to |ErrorMessage|. But this is what we've historically
             // done for duck-typed error objects.
             //
             // If only this stuff could get specced one day...
             if (str->ensureFlat(cx) && strChars.initTwoByte(cx, str))
-                report.ucmessage = strChars.twoByteChars();
+                ownedReport.ucmessage = strChars.twoByteChars();
         }
     }
 
-    JSAutoByteString bytesStorage;
-    const char *bytes = nullptr;
     if (str)
-        bytes = bytesStorage.encodeLatin1(cx, str);
-    if (!bytes)
-        bytes = "unknown (can't convert to string)";
+        message_ = bytesStorage.encodeLatin1(cx, str);
+    if (!message_)
+        message_ = "unknown (can't convert to string)";
 
     if (!reportp) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_UNCAUGHT_EXCEPTION, bytes);
+        // This is basically an inlined version of
+        //
+        //   JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
+        //                        JSMSG_UNCAUGHT_EXCEPTION, message_);
+        //
+        // but without the reporting bits.  Instead it just puts all
+        // the stuff we care about in our ownedReport and message_.
+        populateUncaughtExceptionReport(cx, message_);
     } else {
         /* Flag the error as an exception. */
         reportp->flags |= JSREPORT_EXCEPTION;
-
-        /* Pass the exception object. */
-        JS_SetPendingException(cx, exn);
-        CallErrorReporter(cx, bytes, reportp);
     }
 
-    JS_ClearPendingException(cx);
     return true;
 }
 
+void
+ErrorReport::populateUncaughtExceptionReport(JSContext *cx, ...)
+{
+    va_list ap;
+    va_start(ap, cx);
+    populateUncaughtExceptionReportVA(cx, ap);
+    va_end(ap);
+}
+
+void
+ErrorReport::populateUncaughtExceptionReportVA(JSContext *cx, va_list ap)
+{
+    PodZero(&ownedReport);
+    ownedReport.flags = JSREPORT_ERROR;
+    ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION;
+    // XXXbz this assumes the stack we have right now is still
+    // related to our exception object.  It would be better if we
+    // could accept a passed-in stack of some sort instead.
+    NonBuiltinFrameIter iter(cx);
+    if (!iter.done()) {
+        ownedReport.filename = iter.scriptFilename();
+        ownedReport.lineno = iter.computeLine(&ownedReport.column);
+        ownedReport.originPrincipals = iter.originPrincipals();
+    }
+
+    if (!js_ExpandErrorArguments(cx, js_GetErrorMessage, nullptr,
+                                 JSMSG_UNCAUGHT_EXCEPTION, &ownedMessage,
+                                 &ownedReport, ArgumentsAreASCII, ap)) {
+        return;
+    }
+
+    reportp = &ownedReport;
+    message_ = ownedMessage;
+    ownsMessageAndReport = true;
+}
+
 JSObject *
 js_CopyErrorObject(JSContext *cx, Handle<ErrorObject*> err)
 {
     js::ScopedJSFreePtr<JSErrorReport> copyReport;
     if (JSErrorReport *errorReport = err->getErrorReport()) {
         copyReport = CopyErrorReport(cx, errorReport);
         if (!copyReport)
             return nullptr;
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -7,16 +7,17 @@
 #ifndef jsfriendapi_h
 #define jsfriendapi_h
 
 #include "mozilla/Casting.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TypedEnum.h"
 #include "mozilla/UniquePtr.h"
 
+#include "jsapi.h" // For JSAutoByteString.  See bug 1033916.
 #include "jsbytecode.h"
 #include "jspubtd.h"
 
 #include "js/CallArgs.h"
 #include "js/CallNonGenericMethod.h"
 #include "js/Class.h"
 
 /*
@@ -821,16 +822,22 @@ GetStringLength(JSString *s)
 }
 
 MOZ_ALWAYS_INLINE size_t
 GetFlatStringLength(JSFlatString *s)
 {
     return reinterpret_cast<shadow::String*>(s)->length;
 }
 
+MOZ_ALWAYS_INLINE size_t
+GetLinearStringLength(JSLinearString *s)
+{
+    return reinterpret_cast<shadow::String*>(s)->length;
+}
+
 MOZ_ALWAYS_INLINE bool
 LinearStringHasLatin1Chars(JSLinearString *s)
 {
     return reinterpret_cast<shadow::String *>(s)->flags & shadow::String::LATIN1_CHARS_BIT;
 }
 
 MOZ_ALWAYS_INLINE bool
 AtomHasLatin1Chars(JSAtom *atom)
@@ -1248,21 +1255,148 @@ typedef enum JSErrNum {
     JSErr_Limit
 } JSErrNum;
 
 extern JS_FRIEND_API(const JSErrorFormatString *)
 js_GetErrorMessage(void *userRef, const unsigned errorNumber);
 
 namespace js {
 
+// AutoStableStringChars is here so we can use it in ErrorReport.  It
+// should get moved out of here if we can manage it.  See bug 1040316.
+
+/*
+ * This class provides safe access to a string's chars across a GC. Once
+ * we allocate strings and chars in the nursery (bug 903519), this class
+ * will have to make a copy of the string's chars if they are allocated
+ * in the nursery, so it's best to avoid using this class unless you really
+ * need it. It's usually more efficient to use the latin1Chars/twoByteChars
+ * JSString methods and often the code can be rewritten so that only indexes
+ * instead of char pointers are used in parts of the code that can GC.
+ */
+class MOZ_STACK_CLASS AutoStableStringChars
+{
+    /* Ensure the string is kept alive while we're using its chars. */
+    JS::RootedString s_;
+    union {
+        const jschar *twoByteChars_;
+        const JS::Latin1Char *latin1Chars_;
+    };
+    enum State { Uninitialized, Latin1, TwoByte };
+    State state_;
+    bool ownsChars_;
+
+  public:
+    AutoStableStringChars(JSContext *cx)
+      : s_(cx), state_(Uninitialized), ownsChars_(false)
+    {};
+    ~AutoStableStringChars();
+
+    bool init(JSContext *cx, JSString *s);
+
+    /* Like init(), but Latin1 chars are inflated to TwoByte. */
+    bool initTwoByte(JSContext *cx, JSString *s);
+
+    bool isLatin1() const { return state_ == Latin1; }
+    bool isTwoByte() const { return state_ == TwoByte; }
+
+    const jschar *twoByteChars() const {
+        MOZ_ASSERT(state_ == TwoByte);
+        return twoByteChars_;
+    }
+
+    mozilla::Range<const JS::Latin1Char> latin1Range() const {
+        MOZ_ASSERT(state_ == Latin1);
+        return mozilla::Range<const JS::Latin1Char>(latin1Chars_,
+                                                    GetStringLength(s_));
+    }
+
+    mozilla::Range<const jschar> twoByteRange() const {
+        MOZ_ASSERT(state_ == TwoByte);
+        return mozilla::Range<const jschar>(twoByteChars_,
+                                            GetStringLength(s_));
+    }
+
+    /* If we own the chars, transfer ownership to the caller. */
+    bool maybeGiveOwnershipToCaller() {
+        MOZ_ASSERT(state_ != Uninitialized);
+        if (!ownsChars_)
+            return false;
+        state_ = Uninitialized;
+        ownsChars_ = false;
+        return true;
+    }
+
+  private:
+    AutoStableStringChars(const AutoStableStringChars &other) MOZ_DELETE;
+    void operator=(const AutoStableStringChars &other) MOZ_DELETE;
+};
+
 // Creates a string of the form |ErrorType: ErrorMessage| for a JSErrorReport,
 // which generally matches the toString() behavior of an ErrorObject.
 extern JS_FRIEND_API(JSString *)
 ErrorReportToString(JSContext *cx, JSErrorReport *reportp);
 
+struct MOZ_STACK_CLASS JS_FRIEND_API(ErrorReport)
+{
+    ErrorReport(JSContext *cx);
+    ~ErrorReport();
+
+    bool init(JSContext *cx, JS::HandleValue exn);
+
+    JSErrorReport *report()
+    {
+        return reportp;
+    }
+
+    const char *message()
+    {
+        return message_;
+    }
+
+  private:
+    // More or less an equivalent of JS_ReportErrorNumber/js_ReportErrorNumberVA
+    // but fills in an ErrorReport instead of reporting it.  Uses varargs to
+    // make it simpler to call js_ExpandErrorArguments.
+    void populateUncaughtExceptionReport(JSContext *cx, ...);
+    void populateUncaughtExceptionReportVA(JSContext *cx, va_list ap);
+
+    // We may have a provided JSErrorReport, so need a way to represent that.
+    JSErrorReport *reportp;
+
+    // And we may have a message.
+    const char *message_;
+
+    // Or we may need to synthesize a JSErrorReport one of our own.
+    JSErrorReport ownedReport;
+
+    // Or a message of our own.  If this is non-null, we need to clean up both
+    // it and ownedReport.
+    char *ownedMessage;
+
+    // And we have a string to maybe keep alive that has pointers into
+    // it from ownedReport.
+    JS::RootedString str;
+
+    // And keep its chars alive too.
+    AutoStableStringChars strChars;
+
+    // And we need to root our exception value.
+    JS::RootedObject exnObject;
+
+    // And possibly some byte storage for our message_.
+    JSAutoByteString bytesStorage;
+
+    // And for our filename.
+    JSAutoByteString filename;
+
+    // True if we need to free message_ and the stuff in ownedReport
+    bool ownsMessageAndReport;
+};
+
 } /* namespace js */
 
 
 /* Implemented in jsclone.cpp. */
 
 extern JS_FRIEND_API(uint64_t)
 js_GetSCOffset(JSStructuredCloneWriter* writer);
 
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -831,58 +831,62 @@ AutoStableStringChars::~AutoStableString
         else
             js_free(const_cast<jschar*>(twoByteChars_));
     }
 }
 
 bool
 AutoStableStringChars::init(JSContext *cx, JSString *s)
 {
-    s_ = s->ensureLinear(cx);
-    if (!s_)
+    RootedLinearString linearString(cx, s->ensureLinear(cx));
+    if (!linearString)
         return false;
 
     MOZ_ASSERT(state_ == Uninitialized);
 
-    if (s_->hasLatin1Chars()) {
+    if (linearString->hasLatin1Chars()) {
         state_ = Latin1;
-        latin1Chars_ = s_->rawLatin1Chars();
+        latin1Chars_ = linearString->rawLatin1Chars();
     } else {
         state_ = TwoByte;
-        twoByteChars_ = s_->rawTwoByteChars();
+        twoByteChars_ = linearString->rawTwoByteChars();
     }
 
+    s_ = linearString;
     return true;
 }
 
 bool
 AutoStableStringChars::initTwoByte(JSContext *cx, JSString *s)
 {
-    s_ = s->ensureLinear(cx);
-    if (!s_)
+    RootedLinearString linearString(cx, s->ensureLinear(cx));
+    if (!linearString)
         return false;
 
     MOZ_ASSERT(state_ == Uninitialized);
 
-    if (s_->hasTwoByteChars()) {
+    if (linearString->hasTwoByteChars()) {
         state_ = TwoByte;
-        twoByteChars_ = s_->rawTwoByteChars();
+        twoByteChars_ = linearString->rawTwoByteChars();
+        s_ = linearString;
         return true;
     }
 
-    jschar *chars = cx->pod_malloc<jschar>(s_->length() + 1);
+    jschar *chars = cx->pod_malloc<jschar>(linearString->length() + 1);
     if (!chars)
         return false;
 
-    CopyAndInflateChars(chars, s_->rawLatin1Chars(), s_->length());
-    chars[s_->length()] = 0;
+    CopyAndInflateChars(chars, linearString->rawLatin1Chars(),
+                        linearString->length());
+    chars[linearString->length()] = 0;
 
     state_ = TwoByte;
     ownsChars_ = true;
     twoByteChars_ = chars;
+    s_ = linearString;
     return true;
 }
 
 bool js::EnableLatin1Strings = true;
 
 #ifdef DEBUG
 void
 JSAtom::dump()
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -1015,81 +1015,16 @@ class ScopedThreadSafeStringInspector
         return mozilla::Range<const Latin1Char>(latin1Chars_, str_->length());
     }
     mozilla::Range<const jschar> twoByteRange() const {
         MOZ_ASSERT(state_ == TwoByte);
         return mozilla::Range<const jschar>(twoByteChars_, str_->length());
     }
 };
 
-/*
- * This class provides safe access to a string's chars across a GC. Once
- * we allocate strings and chars in the nursery (bug 903519), this class
- * will have to make a copy of the string's chars if they are allocated
- * in the nursery, so it's best to avoid using this class unless you really
- * need it. It's usually more efficient to use the latin1Chars/twoByteChars
- * JSString methods and often the code can be rewritten so that only indexes
- * instead of char pointers are used in parts of the code that can GC.
- */
-class MOZ_STACK_CLASS AutoStableStringChars
-{
-    /* Ensure the string is kept alive while we're using its chars. */
-    RootedLinearString s_;
-    union {
-        const jschar *twoByteChars_;
-        const JS::Latin1Char *latin1Chars_;
-    };
-    enum State { Uninitialized, Latin1, TwoByte };
-    State state_;
-    bool ownsChars_;
-
-  public:
-    AutoStableStringChars(JSContext *cx)
-      : s_(cx), state_(Uninitialized), ownsChars_(false)
-    {};
-    ~AutoStableStringChars();
-
-    bool init(JSContext *cx, JSString *s);
-
-    /* Like init(), but Latin1 chars are inflated to TwoByte. */
-    bool initTwoByte(JSContext *cx, JSString *s);
-
-    bool isLatin1() const { return state_ == Latin1; }
-    bool isTwoByte() const { return state_ == TwoByte; }
-
-    const jschar *twoByteChars() const {
-        MOZ_ASSERT(state_ == TwoByte);
-        return twoByteChars_;
-    }
-
-    mozilla::Range<const Latin1Char> latin1Range() const {
-        MOZ_ASSERT(state_ == Latin1);
-        return mozilla::Range<const Latin1Char>(latin1Chars_, s_->length());
-    }
-
-    mozilla::Range<const jschar> twoByteRange() const {
-        MOZ_ASSERT(state_ == TwoByte);
-        return mozilla::Range<const jschar>(twoByteChars_, s_->length());
-    }
-
-    /* If we own the chars, transfer ownership to the caller. */
-    bool maybeGiveOwnershipToCaller() {
-        MOZ_ASSERT(state_ != Uninitialized);
-        if (!ownsChars_)
-            return false;
-        state_ = Uninitialized;
-        ownsChars_ = false;
-        return true;
-    }
-
-  private:
-    AutoStableStringChars(const AutoStableStringChars &other) MOZ_DELETE;
-    void operator=(const AutoStableStringChars &other) MOZ_DELETE;
-};
-
 class StaticStrings
 {
   private:
     /* Bigger chars cannot be in a length-2 string. */
     static const size_t SMALL_CHAR_LIMIT    = 128U;
     static const size_t NUM_SMALL_CHARS     = 64U;
 
     JSAtom *length2StaticTable[NUM_SMALL_CHARS * NUM_SMALL_CHARS];
--- a/toolkit/components/ctypes/tests/chrome/test_ctypes.xul
+++ b/toolkit/components/ctypes/tests/chrome/test_ctypes.xul
@@ -75,16 +75,21 @@
 
       var worker = new ChromeWorker("ctypes_worker.js");
       worker.onmessage = function(event) {
         is(event.data, "Done!", "Wrong message!");
         cleanupLibs(dir);
         SimpleTest.finish();
       }
       worker.onerror = function(event) {
+        if (event.message == "InternalError: uncaught exception: 7.5 million years for that?" ||
+            event.message == "InternalError: uncaught exception: Just following orders, sir!") {
+          // We throw those on purpose in the worker, so ignore them.
+          return true;
+        }
         ok(false, "Worker had an error: " + event.message);
         worker.terminate();
         cleanupLibs(dir);
         SimpleTest.finish();
       }
 
       worker.postMessage({dir: dir.path, os: Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime).OS});
     }