Bug 905392 - Need way to throw web-console-visible exception-message from JS-implemented webidl object. r=bz a=akeybl
authorPeter Van der Beken <peterv@propagandism.org>
Tue, 03 Sep 2013 14:01:53 +0200
changeset 154019 92fe3e4ad43eca2c7a4dfe3e27607c55216ec28d
parent 154018 444b4437151cb17243259806d9c0161c243c8835
child 154020 1d2a918f0f9dad01f52d530ba8b5415d9c7ceeaa
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, akeybl
bugs905392
milestone25.0a2
Bug 905392 - Need way to throw web-console-visible exception-message from JS-implemented webidl object. r=bz a=akeybl
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
dom/bindings/Codegen.py
dom/bindings/ErrorResult.h
dom/media/PeerConnection.js
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -22,16 +22,18 @@
 #include "nsIDOMGlobalPropertyInitializer.h"
 #include "nsIXPConnect.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XPCQuickStubs.h"
 #include "XrayWrapper.h"
 #include "nsPrintfCString.h"
 
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMErrorBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/HTMLSharedObjectElement.h"
 #include "mozilla/dom/HTMLEmbedElementBinding.h"
 #include "mozilla/dom/HTMLAppletElementBinding.h"
 
 namespace mozilla {
 namespace dom {
@@ -182,16 +184,51 @@ ErrorResult::ReportJSException(JSContext
     JS_SetPendingException(cx, mJSException);
   }
   // If JS_WrapValue failed, not much we can do about it...  No matter
   // what, go ahead and unroot mJSException.
   JS_RemoveValueRoot(cx, &mJSException);
 }
 
 void
+ErrorResult::ReportJSExceptionFromJSImplementation(JSContext* aCx)
+{
+  MOZ_ASSERT(!mMightHaveUnreportedJSException,
+             "Why didn't you tell us you planned to handle JS exceptions?");
+
+  dom::DOMError* domError;
+  nsresult rv = dom::UnwrapObject<dom::DOMError>(aCx, &mJSException.toObject(),
+                                                 domError);
+  if (NS_FAILED(rv)) {
+    // Unwrapping really shouldn't fail here, if mExceptionHandling is set to
+    // eRethrowContentExceptions then the CallSetup destructor only stores an
+    // exception if it unwraps to DOMError. If we reach this then either
+    // mExceptionHandling wasn't set to eRethrowContentExceptions and we
+    // shouldn't be calling ReportJSExceptionFromJSImplementation or something
+    // went really wrong.
+    NS_RUNTIMEABORT("We stored a non-DOMError exception!");
+  }
+
+  nsString message;
+  domError->GetMessage(message);
+
+  JSErrorReport errorReport;
+  memset(&errorReport, 0, sizeof(JSErrorReport));
+  errorReport.errorNumber = JSMSG_USER_DEFINED_ERROR;
+  errorReport.ucmessage = message.get();
+  errorReport.exnType = JSEXN_ERR;
+  JS_ThrowReportedError(aCx, nullptr, &errorReport);
+  JS_RemoveValueRoot(aCx, &mJSException);
+  
+  // We no longer have a useful exception but we do want to signal that an error
+  // occured.
+  mResult = NS_ERROR_FAILURE;
+}
+
+void
 ErrorResult::StealJSException(JSContext* cx,
                               JS::MutableHandle<JS::Value> value)
 {
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Must call WouldReportJSException unconditionally in all codepaths that might call StealJSException");
   MOZ_ASSERT(IsJSException(), "No exception to steal");
 
   value.set(mJSException);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -83,24 +83,29 @@ Throw(JSContext* cx, nsresult rv)
   }
   return false;
 }
 
 template<bool mainThread>
 inline bool
 ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv,
                              const char* ifaceName,
-                             const char* memberName)
+                             const char* memberName,
+                             bool reportJSContentExceptions = false)
 {
   if (rv.IsTypeError()) {
     rv.ReportTypeError(cx);
     return false;
   }
   if (rv.IsJSException()) {
-    rv.ReportJSException(cx);
+    if (reportJSContentExceptions) {
+      rv.ReportJSExceptionFromJSImplementation(cx);
+    } else {
+      rv.ReportJSException(cx);
+    }
     return false;
   }
   if (rv.IsNotEnoughArgsError()) {
     rv.ReportNotEnoughArgsError(cx, ifaceName, memberName);
   }
   return Throw<mainThread>(cx, rv.ErrorCode());
 }
 
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -1,15 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMErrorBinding.h"
 #include "jsfriendapi.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsCxPusher.h"
 #include "nsIScriptSecurityManager.h"
@@ -35,18 +38,20 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 CallbackObject::CallSetup::CallSetup(JS::Handle<JSObject*> aCallback,
                                      ErrorResult& aRv,
-                                     ExceptionHandling aExceptionHandling)
+                                     ExceptionHandling aExceptionHandling,
+                                     JSCompartment* aCompartment)
   : mCx(nullptr)
+  , mCompartment(aCompartment)
   , mErrorResult(aRv)
   , mExceptionHandling(aExceptionHandling)
 {
   // We need to produce a useful JSContext here.  Ideally one that the callback
   // is in some sense associated with, so that we can sort of treat it as a
   // "script entry point".  Though once we actually have script entry points,
   // we'll need to do the script entry point bits once we have an actual
   // callable.
@@ -125,35 +130,65 @@ CallbackObject::CallSetup::CallSetup(JS:
 
   // Enter the compartment of our callback, so we can actually work with it.
   mAc.construct(cx, aCallback);
 
   // And now we're ready to go.
   mCx = cx;
 
   // Make sure the JS engine doesn't report exceptions we want to re-throw
-  if (mExceptionHandling == eRethrowExceptions) {
+  if (mExceptionHandling == eRethrowContentExceptions ||
+      mExceptionHandling == eRethrowExceptions) {
     mSavedJSContextOptions = JS_GetOptions(cx);
     JS_SetOptions(cx, mSavedJSContextOptions | JSOPTION_DONT_REPORT_UNCAUGHT);
   }
 }
 
+bool
+CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
+{
+  if (mExceptionHandling == eRethrowExceptions) {
+    return true;
+  }
+
+  MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions);
+
+  // For eRethrowContentExceptions we only want to throw an exception if the
+  // object that was thrown is a DOMError object in the caller compartment
+  // (which we stored in mCompartment).
+
+  if (!aException.isObject()) {
+    return false;
+  }
+
+  JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
+  obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false);
+  if (js::GetObjectCompartment(obj) != mCompartment) {
+    return false;
+  }
+
+  DOMError* domError;
+  return NS_SUCCEEDED(UnwrapObject<DOMError>(mCx, obj, domError));
+}
+
 CallbackObject::CallSetup::~CallSetup()
 {
   // First things first: if we have a JSContext, report any pending
   // errors on it, unless we were told to re-throw them.
   if (mCx) {
     bool dealtWithPendingException = false;
-    if (mExceptionHandling == eRethrowExceptions) {
+    if (mExceptionHandling == eRethrowContentExceptions ||
+        mExceptionHandling == eRethrowExceptions) {
       // Restore the old context options
       JS_SetOptions(mCx, mSavedJSContextOptions);
       mErrorResult.MightThrowJSException();
       if (JS_IsExceptionPending(mCx)) {
         JS::Rooted<JS::Value> exn(mCx);
-        if (JS_GetPendingException(mCx, exn.address())) {
+        if (JS_GetPendingException(mCx, exn.address()) &&
+            ShouldRethrowException(exn)) {
           mErrorResult.ThrowJSException(mCx, exn);
           JS_ClearPendingException(mCx);
           dealtWithPendingException = true;
         }
       }
     }
 
     if (!dealtWithPendingException) {
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -75,17 +75,23 @@ public:
   JS::Handle<JSObject*> CallbackPreserveColor() const
   {
     // Calling fromMarkedLocation() is safe because we trace our mCallback, and
     // because the value of mCallback cannot change after if has been set.
     return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
   }
 
   enum ExceptionHandling {
+    // Report any exception and don't throw it to the caller code.
     eReportExceptions,
+    // Throw an exception to the caller code if the thrown exception is a
+    // binding object for a DOMError from the caller's scope, otherwise report
+    // it.
+    eRethrowContentExceptions,
+    // Throw any exception to the caller code.
     eRethrowExceptions
   };
 
 protected:
   explicit CallbackObject(CallbackObject* aCallbackObject)
   {
     Init(aCallbackObject->mCallback);
   }
@@ -118,33 +124,42 @@ protected:
   {
     /**
      * A class that performs whatever setup we need to safely make a
      * call while this class is on the stack, After the constructor
      * returns, the call is safe to make if GetContext() returns
      * non-null.
      */
   public:
+    // If aExceptionHandling == eRethrowContentExceptions then aCompartment
+    // needs to be set to the caller's compartment.
     CallSetup(JS::Handle<JSObject*> aCallable, ErrorResult& aRv,
-              ExceptionHandling aExceptionHandling);
+              ExceptionHandling aExceptionHandling,
+              JSCompartment* aCompartment = nullptr);
     ~CallSetup();
 
     JSContext* GetContext() const
     {
       return mCx;
     }
 
   private:
     // We better not get copy-constructed
     CallSetup(const CallSetup&) MOZ_DELETE;
 
+    bool ShouldRethrowException(JS::Handle<JS::Value> aException);
+
     // Members which can go away whenever
     JSContext* mCx;
     nsCOMPtr<nsIScriptContext> mCtx;
 
+    // Caller's compartment. This will only have a sensible value if
+    // mExceptionHandling == eRethrowContentExceptions.
+    JSCompartment* mCompartment;
+
     // And now members whose construction/destruction order we need to control.
 
     // Put our nsAutoMicrotask first, so it gets destroyed after everything else
     // is gone
     nsAutoMicroTask mMt;
 
     nsCxPusher mCxPusher;
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -4648,16 +4648,28 @@ if (global.Failed()) {
         # For JS-implemented interfaces we do not want to base the
         # needsCx decision on the types involved, just on our extended
         # attributes.
         needsCx = needCx(returnType, arguments, self.extendedAttributes,
                          descriptor, not descriptor.interface.isJSImplemented())
         if needsCx and not (static and descriptor.workers):
             argsPre.append("cx")
 
+        needsUnwrap = False
+        if isConstructor:
+            needsUnwrap = True
+            unwrappedVar = "obj"
+        elif descriptor.interface.isJSImplemented():
+            needsUnwrap = True
+            # We cannot assign into obj because it's a Handle, not a
+            # MutableHandle, so we need a separate Rooted.
+            cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;"))
+            unwrappedVar = "unwrappedObj.ref()"
+            argsPre.append("js::GetObjectCompartment(unwrappedObj.empty() ? obj : unwrappedObj.ref())")
+
         if idlNode.isMethod() and idlNode.isLegacycaller():
             # If we can have legacycaller with identifier, we can't
             # just use the idlNode to determine whether we're
             # generating code for the legacycaller or not.
             assert idlNode.isIdentifierLess()
             # Pass in our thisVal
             argsPre.append("args.thisv()")
 
@@ -4672,36 +4684,46 @@ if (global.Failed()) {
 
         cgThings.extend([CGArgumentConverter(arguments[i], i, self.descriptor,
                                              argDescription % { "index": i + 1 },
                                              invalidEnumValueFatal=not setter,
                                              allowTreatNonCallableAsNull=setter,
                                              lenientFloatCode=lenientFloatCode) for
                          i in range(argConversionStartsAt, self.argCount)])
 
-        if isConstructor:
-            # If we're called via an xray, we need to enter the underlying
-            # object's compartment and then wrap up all of our arguments into
-            # that compartment as needed.  This is all happening after we've
-            # already done the conversions from JS values to WebIDL (C++)
-            # values, so we only need to worry about cases where there are 'any'
-            # or 'object' types, or other things that we represent as actual
-            # JSAPI types, present.  Effectively, we're emulating a
-            # CrossCompartmentWrapper, but working with the C++ types, not the
-            # original list of JS::Values.
-            cgThings.append(CGGeneric("Maybe<JSAutoCompartment> ac;"))
-            xraySteps = [
-                CGGeneric("obj = js::CheckedUnwrap(obj);\n"
-                          "if (!obj) {\n"
-                          "  return false;\n"
-                          "}\n"
-                          "ac.construct(cx, obj);") ]
-            xraySteps.extend(
-                wrapArgIntoCurrentCompartment(arg, argname, isMember=False)
-                for (arg, argname) in self.getArguments())
+        if needsUnwrap:
+            # Something depends on having the unwrapped object, so unwrap it now.
+            xraySteps = []
+            if not isConstructor:
+                xraySteps.append(
+                    CGGeneric("unwrappedObj.construct(cx, obj);"))
+
+            # XXXkhuey we should be able to MOZ_ASSERT that ${obj} is
+            # not null.
+            xraySteps.append(
+                CGGeneric(string.Template("""${obj} = js::CheckedUnwrap(${obj});
+if (!${obj}) {
+  return false;
+}""").substitute({ 'obj' : unwrappedVar })))
+            if isConstructor:
+                # If we're called via an xray, we need to enter the underlying
+                # object's compartment and then wrap up all of our arguments into
+                # that compartment as needed.  This is all happening after we've
+                # already done the conversions from JS values to WebIDL (C++)
+                # values, so we only need to worry about cases where there are 'any'
+                # or 'object' types, or other things that we represent as actual
+                # JSAPI types, present.  Effectively, we're emulating a
+                # CrossCompartmentWrapper, but working with the C++ types, not the
+                # original list of JS::Values.
+                cgThings.append(CGGeneric("Maybe<JSAutoCompartment> ac;"))
+                xraySteps.append(CGGeneric("ac.construct(cx, obj);"))
+                xraySteps.extend(
+                    wrapArgIntoCurrentCompartment(arg, argname, isMember=False)
+                    for (arg, argname) in self.getArguments())
+
             cgThings.append(
                 CGIfWrapper(CGList(xraySteps, "\n"),
                             "xpc::WrapperFactory::IsXrayWrapper(obj)"))
 
         cgThings.append(CGCallGenerator(
                     self.getErrorReport() if self.isFallible() else None,
                     self.getArguments(), argsPre, returnType,
                     self.extendedAttributes, descriptor, nativeMethodName,
@@ -4736,20 +4758,24 @@ if (global.Failed()) {
         except MethodNotCreatorError, err:
             assert not isCreator
             raise TypeError("%s being returned from non-creator method or property %s.%s" %
                             (err.typename,
                              self.descriptor.interface.identifier.name,
                              self.idlNode.identifier.name))
 
     def getErrorReport(self):
-        return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");'
+        jsImplemented = ""
+        if self.descriptor.interface.isJSImplemented():
+            jsImplemented = ", true"
+        return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s"%s);'
                          % (toStringBool(not self.descriptor.workers),
                             self.descriptor.interface.identifier.name,
-                            self.idlNode.identifier.name))
+                            self.idlNode.identifier.name,
+                            jsImplemented))
 
     def define(self):
         return (self.cgRoot.define() + "\n" + self.wrap_return_value())
 
 class CGSwitch(CGList):
     """
     A class to generate code for a switch statement.
 
@@ -9492,49 +9518,78 @@ class CGExampleRoot(CGThing):
 
     def define(self):
         return self.root.define()
 
 
 def jsImplName(name):
     return name + "JSImpl"
 
-class CGJSImplMethod(CGNativeMember):
+class CGJSImplMember(CGNativeMember):
+    """
+    Base class for generating code for the members of the implementation class
+    for a JS-implemented WebIDL interface.
+    """
+    def __init__(self, descriptorProvider, member, name, signature,
+                 extendedAttrs, breakAfter=True, passCxAsNeeded=True,
+                 visibility="public", jsObjectsArePtr=False,
+                 variadicIsSequence=False):
+        CGNativeMember.__init__(self, descriptorProvider, member, name,
+                                signature, extendedAttrs, breakAfter=breakAfter,
+                                passCxAsNeeded=passCxAsNeeded,
+                                visibility=visibility,
+                                jsObjectsArePtr=jsObjectsArePtr,
+                                variadicIsSequence=variadicIsSequence)
+        self.body = self.getImpl()
+
+    def getArgs(self, returnType, argList):
+        args = CGNativeMember.getArgs(self, returnType, argList)
+        args.insert(0, Argument("JSCompartment*", "aCompartment"))
+        return args
+
+class CGJSImplMethod(CGJSImplMember):
+    """
+    Class for generating code for the methods for a JS-implemented WebIDL
+    interface.
+    """
     def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
-        CGNativeMember.__init__(self, descriptor, method,
+        self.signature = signature
+        self.descriptor = descriptor
+        self.isConstructor = isConstructor
+        CGJSImplMember.__init__(self, descriptor, method,
                                 CGSpecializedMethod.makeNativeName(descriptor,
                                                                    method),
                                 signature,
                                 descriptor.getExtendedAttributes(method),
                                 breakAfter=breakAfter,
                                 variadicIsSequence=True,
                                 passCxAsNeeded=False)
-        self.signature = signature
-        self.descriptor = descriptor
-        if isConstructor:
-            self.body = self.getConstructorImpl()
-        else:
-            self.body = self.getImpl()
+
+    def getArgs(self, returnType, argList):
+        if self.isConstructor:
+            # Skip the JSCompartment bits for constructors; it's handled
+            # manually in getImpl.
+            return CGNativeMember.getArgs(self, returnType, argList)
+        return CGJSImplMember.getArgs(self, returnType, argList)
 
     def getImpl(self):
-        callbackArgs = [arg.name for arg in self.getArgs(self.signature[0], self.signature[1])]
-        return 'return mImpl->%s(%s);' % (self.name, ", ".join(callbackArgs))
-
-    def getConstructorImpl(self):
+        args = self.getArgs(self.signature[0], self.signature[1])
+        if not self.isConstructor:
+            return 'return mImpl->%s(%s);' % (self.name, ", ".join(arg.name for arg in args))
+
         assert self.descriptor.interface.isJSImplemented()
         if self.name != 'Constructor':
             raise TypeError("Named constructors are not supported for JS implemented WebIDL. See bug 851287.")
         if len(self.signature[1]) != 0:
-            args = self.getArgs(self.signature[0], self.signature[1])
             # The first two arguments to the constructor implementation are not
             # arguments to the WebIDL constructor, so don't pass them to __Init()
             assert args[0].argType == 'const GlobalObject&'
             assert args[1].argType == 'JSContext*'
-            args = args[2:]
-            constructorArgs = [arg.name for arg in args]
+            constructorArgs = [arg.name for arg in args[2:]]
+            constructorArgs.insert(0, "js::GetObjectCompartment(scopeObj)")
             initCall = """
 // Wrap the object before calling __Init so that __DOM_IMPL__ is available.
 nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(window);
 JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject());
 JS::Rooted<JS::Value> wrappedVal(cx);
 if (!WrapNewBindingObject(cx, scopeObj, impl, &wrappedVal)) {
   MOZ_ASSERT(JS_IsExceptionPending(cx));
   aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -9571,42 +9626,48 @@ def genConstructorBody(descriptor, initC
 
 # We're always fallible
 def callbackGetterName(attr):
     return "Get" + MakeNativeName(attr.identifier.name)
 
 def callbackSetterName(attr):
     return "Set" + MakeNativeName(attr.identifier.name)
 
-class CGJSImplGetter(CGNativeMember):
+class CGJSImplGetter(CGJSImplMember):
+    """
+    Class for generating code for the getters of attributes for a JS-implemented
+    WebIDL interface.
+    """
     def __init__(self, descriptor, attr):
-        CGNativeMember.__init__(self, descriptor, attr,
+        CGJSImplMember.__init__(self, descriptor, attr,
                                 CGSpecializedGetter.makeNativeName(descriptor,
                                                                    attr),
                                 (attr.type, []),
                                 descriptor.getExtendedAttributes(attr,
                                                                  getter=True),
                                 passCxAsNeeded=False)
-        self.body = self.getImpl()
 
     def getImpl(self):
         callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])]
         return 'return mImpl->%s(%s);' % (callbackGetterName(self.member), ", ".join(callbackArgs))
 
-class CGJSImplSetter(CGNativeMember):
+class CGJSImplSetter(CGJSImplMember):
+    """
+    Class for generating code for the setters of attributes for a JS-implemented
+    WebIDL interface.
+    """
     def __init__(self, descriptor, attr):
-        CGNativeMember.__init__(self, descriptor, attr,
+        CGJSImplMember.__init__(self, descriptor, attr,
                                 CGSpecializedSetter.makeNativeName(descriptor,
                                                                    attr),
                                 (BuiltinTypes[IDLBuiltinType.Types.void],
                                  [FakeArgument(attr.type, attr)]),
                                 descriptor.getExtendedAttributes(attr,
                                                                  setter=True),
                                 passCxAsNeeded=False)
-        self.body = self.getImpl()
 
     def getImpl(self):
         callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void],
                                                          [FakeArgument(self.member.type, self.member)])]
         return 'mImpl->%s(%s);' % (callbackSetterName(self.member), ", ".join(callbackArgs))
 
 class CGJSImplClass(CGBindingImplClass):
     def __init__(self, descriptor):
@@ -9912,21 +9973,23 @@ class FakeMember():
         # Claim to be a [Creator] so we can avoid the "mark this
         # resultNotAddRefed" comments CGNativeMember codegen would
         # otherwise stick in.
         if name == "Creator":
             return True
         return None
 
 class CallbackMember(CGNativeMember):
-    def __init__(self, sig, name, descriptorProvider, needThisHandling):
+    def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
         """
         needThisHandling is True if we need to be able to accept a specified
         thisObj, False otherwise.
         """
+        assert not rethrowContentException or not needThisHandling
+
         self.retvalType = sig[0]
         self.originalSig = sig
         args = sig[1]
         self.argCount = len(args)
         if self.argCount > 0:
             # Check for variadic arguments
             lastArg = args[self.argCount-1]
             if lastArg.variadic:
@@ -9934,16 +9997,17 @@ class CallbackMember(CGNativeMember):
                     "(%d - 1) + %s.Length()" % (self.argCount,
                                                 lastArg.identifier.name))
             else:
                 self.argCountStr = "%d" % self.argCount
         self.needThisHandling = needThisHandling
         # If needThisHandling, we generate ourselves as private and the caller
         # will handle generating public versions that handle the "this" stuff.
         visibility = "private" if needThisHandling else "public"
+        self.rethrowContentException = rethrowContentException
         # We don't care, for callback codegen, whether our original member was
         # a method or attribute or whatnot.  Just always pass FakeMember()
         # here.
         CGNativeMember.__init__(self, descriptorProvider, FakeMember(),
                                 name, (self.retvalType, args),
                                 extendedAttrs={},
                                 passCxAsNeeded=False,
                                 visibility=visibility,
@@ -10092,34 +10156,47 @@ class CallbackMember(CGNativeMember):
             default = " " + default
         return default
 
     def getArgs(self, returnType, argList):
         args = CGNativeMember.getArgs(self, returnType, argList)
         if not self.needThisHandling:
             # Since we don't need this handling, we're the actual method that
             # will be called, so we need an aRethrowExceptions argument.
-            return args + [Argument("ExceptionHandling", "aExceptionHandling",
-                                    "eReportExceptions")]
+            if self.rethrowContentException:
+                args.insert(0, Argument("JSCompartment*", "aCompartment"))
+            else:
+                args.append(Argument("ExceptionHandling", "aExceptionHandling",
+                                     "eReportExceptions"))
+            return args
         # We want to allow the caller to pass in a "this" object, as
         # well as a JSContext.
         return [Argument("JSContext*", "cx"),
                 Argument("JS::Handle<JSObject*>", "aThisObj")] + args
 
     def getCallSetup(self):
         if self.needThisHandling:
             # It's been done for us already
             return ""
+        callSetup = "CallSetup s(CallbackPreserveColor(), aRv"
+        if self.rethrowContentException:
+            # getArgs doesn't add the aExceptionHandling argument but does add
+            # aCompartment for us.
+            callSetup += ", eRethrowContentExceptions, aCompartment"
+        else:
+            callSetup += ", aExceptionHandling"
+        callSetup += ");"
         return string.Template(
-            "CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n"
+            "${callSetup}\n"
             "JSContext* cx = s.GetContext();\n"
             "if (!cx) {\n"
             "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
             "  return${errorReturn};\n"
             "}\n").substitute({
+                "callSetup": callSetup,
                 "errorReturn" : self.getDefaultRetval(),
                 })
 
     def getArgcDecl(self):
         return CGGeneric("unsigned argc = %s;" % self.argCountStr);
 
     @staticmethod
     def ensureASCIIName(idlObject):
@@ -10132,19 +10209,19 @@ class CallbackMember(CGNativeMember):
         if re.match('"', idlObject.identifier.name):
             raise SyntaxError("Callback %s name '%s' contains "
                               "double-quote character.  We can't handle "
                               "that.  %s" %
                               (type, idlObject.identifier.name,
                                idlObject.location))
 
 class CallbackMethod(CallbackMember):
-    def __init__(self, sig, name, descriptorProvider, needThisHandling):
+    def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
         CallbackMember.__init__(self, sig, name, descriptorProvider,
-                                needThisHandling)
+                                needThisHandling, rethrowContentException)
     def getRvalDecl(self):
         return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
             "thisObj": self.getThisObj(),
             "getCallable": self.getCallableDecl()
@@ -10172,20 +10249,20 @@ class CallCallback(CallbackMethod):
 
     def getCallableDecl(self):
         return "JS::Rooted<JS::Value> callable(cx, JS::ObjectValue(*mCallback));\n"
 
 class CallbackOperationBase(CallbackMethod):
     """
     Common class for implementing various callback operations.
     """
-    def __init__(self, signature, jsName, nativeName, descriptor, singleOperation):
+    def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False):
         self.singleOperation = singleOperation
         self.methodName = jsName
-        CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation)
+        CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException)
 
     def getThisObj(self):
         if not self.singleOperation:
             return "mCallback"
         # This relies on getCallableDecl declaring a boolean
         # isCallable in the case when we're a single-operation
         # interface.
         return "isCallable ? aThisObj.get() : mCallback"
@@ -10215,27 +10292,29 @@ class CallbackOperation(CallbackOperatio
     """
     Codegen actual WebIDL operations on callback interfaces.
     """
     def __init__(self, method, signature, descriptor):
         self.ensureASCIIName(method)
         jsName = method.identifier.name
         CallbackOperationBase.__init__(self, signature,
                                        jsName, MakeNativeName(jsName),
-                                       descriptor, descriptor.interface.isSingleOperationInterface())
+                                       descriptor, descriptor.interface.isSingleOperationInterface(),
+                                       rethrowContentException=descriptor.interface.isJSImplemented())
 
 class CallbackGetter(CallbackMember):
     def __init__(self, attr, descriptor):
         self.ensureASCIIName(attr)
         self.attrName = attr.identifier.name
         CallbackMember.__init__(self,
                                 (attr.type, []),
                                 callbackGetterName(attr),
                                 descriptor,
-                                needThisHandling=False)
+                                needThisHandling=False,
+                                rethrowContentException=descriptor.interface.isJSImplemented())
 
     def getRvalDecl(self):
         return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
             "attrName": self.attrName
@@ -10250,17 +10329,18 @@ class CallbackSetter(CallbackMember):
     def __init__(self, attr, descriptor):
         self.ensureASCIIName(attr)
         self.attrName = attr.identifier.name
         CallbackMember.__init__(self,
                                 (BuiltinTypes[IDLBuiltinType.Types.void],
                                  [FakeArgument(attr.type, attr)]),
                                 callbackSetterName(attr),
                                 descriptor,
-                                needThisHandling=False)
+                                needThisHandling=False,
+                                rethrowContentException=descriptor.interface.isJSImplemented())
 
     def getRvalDecl(self):
         # We don't need an rval
         return ""
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
@@ -10279,17 +10359,17 @@ class CallbackSetter(CallbackMember):
 
 class CGJSImplInitOperation(CallbackOperationBase):
     """
     Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL.
     """
     def __init__(self, sig, descriptor):
         assert sig in descriptor.interface.ctor().signatures()
         CallbackOperationBase.__init__(self, (BuiltinTypes[IDLBuiltinType.Types.void], sig[1]),
-                                       "__init", "__Init", descriptor, False)
+                                       "__init", "__Init", descriptor, False, True)
 
 class GlobalGenRoots():
     """
     Roots for global codegen.
 
     To generate code, call the method associated with the target, and then
     call the appropriate define/declare method.
     """
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -62,21 +62,24 @@ public:
   void ThrowTypeError(const dom::ErrNum errorNumber, ...);
   void ReportTypeError(JSContext* cx);
   void ClearMessage();
   bool IsTypeError() const { return ErrorCode() == NS_ERROR_TYPE_ERR; }
 
   // Facilities for throwing a preexisting JS exception value via this
   // ErrorResult.  The contract is that any code which might end up calling
   // ThrowJSException() must call MightThrowJSException() even if no exception
-  // is being thrown.  Code that would call ReportJSException or
+  // is being thrown.  Code that would call ReportJSException* or
   // StealJSException as needed must first call WouldReportJSException even if
   // this ErrorResult has not failed.
   void ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
   void ReportJSException(JSContext* cx);
+  // Used to implement throwing exceptions from the JS implementation of
+  // bindings to callers of the binding.
+  void ReportJSExceptionFromJSImplementation(JSContext* aCx);
   bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; }
 
   void ThrowNotEnoughArgsError() { mResult = NS_ERROR_XPC_NOT_ENOUGH_ARGS; }
   void ReportNotEnoughArgsError(JSContext* cx,
                                 const char* ifaceName,
                                 const char* memberName);
   bool IsNotEnoughArgsError() const { return ErrorCode() == NS_ERROR_XPC_NOT_ENOUGH_ARGS; }
 
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -354,30 +354,29 @@ RTCPeerConnection.prototype = {
    *                   { url:"turn:turn.example.org",
    *                     username:"jib", credential:"mypass"} ] }
    *
    * WebIDL normalizes structure for us, so we test well-formed stun/turn urls,
    * but not validity of servers themselves, before passing along to C++.
    * ErrorMsg is passed in to detail which array-entry failed, if any.
    */
   _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
+    var errorCtor = this._win.DOMError;
     function nicerNewURI(uriStr, errorMsg) {
       let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
       try {
         return ios.newURI(uriStr, null, null);
       } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
-        throw new Components.Exception(errorMsg + " - malformed URI: " + uriStr,
-                                       Cr.NS_ERROR_MALFORMED_URI);
+        throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr);
       }
     }
     function mustValidateServer(server) {
       let url = nicerNewURI(server.url, errorMsg);
       if (!(url.scheme in { stun:1, stuns:1, turn:1, turns:1 })) {
-        throw new Components.Exception(errorMsg + " - improper scheme: " + url.scheme,
-                                       Cr.NS_ERROR_MALFORMED_URI);
+        throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme);
       }
     }
     if (rtcConfig.iceServers) {
       let len = rtcConfig.iceServers.length;
       for (let i=0; i < len; i++) {
         mustValidateServer (rtcConfig.iceServers[i], errorMsg);
       }
     }