Bug 1213289 part 3. Add a way to throw a DOMException with a custom message on ErrorResult. r=bkelly
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 09 Oct 2015 16:48:10 -0400
changeset 302338 0b320a1a2efe0b205321a10fe849e6a5585921ae
parent 302337 d0598ce9e7cadcb6322752190d2b52a2d600c836
child 302339 6c919172ced31342d5a90a9c1ab33f3976d2a299
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1213289
milestone44.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 1213289 part 3. Add a way to throw a DOMException with a custom message on ErrorResult. r=bkelly
dom/base/domerr.msg
dom/bindings/BindingUtils.cpp
dom/bindings/ErrorIPCUtils.h
dom/bindings/ErrorResult.h
xpcom/base/ErrorList.h
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -142,8 +142,9 @@ DOM4_MSG_DEF(UnknownError, "The operatio
 DOM4_MSG_DEF(FileHandleInactiveError, "A request was placed against a file handle which is currently not active, or which is finished.", NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR)
 DOM4_MSG_DEF(ReadOnlyError, "A mutation operation was attempted in a READ_ONLY file handle.", NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR)
 
 DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a file storage that did not allow mutations.", NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR)
 DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to FileHandle.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR)
 DOM4_MSG_DEF(QuotaExceededError, "The current file handle exceeded its quota limitations.", NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR)
 
 DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception")
+DOM_MSG_DEF(NS_ERROR_DOM_DOMEXCEPTION, "A DOMException was thrown")
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -31,16 +31,17 @@
 #include "XrayWrapper.h"
 #include "nsPrintfCString.h"
 #include "prprf.h"
 #include "nsGlobalWindow.h"
 
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/DOMErrorBinding.h"
+#include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ElementBinding.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"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
@@ -137,16 +138,20 @@ ThrowMethodFailed(JSContext* cx, ErrorRe
   if (rv.IsErrorWithMessage()) {
     rv.ReportErrorWithMessage(cx);
     return false;
   }
   if (rv.IsJSException()) {
     rv.ReportJSException(cx);
     return false;
   }
+  if (rv.IsDOMException()) {
+    rv.ReportDOMException(cx);
+    return false;
+  }
   rv.ReportGenericError(cx);
   return false;
 }
 
 bool
 ThrowNoSetterArg(JSContext* aCx, prototypes::ID aProtoId)
 {
   nsPrintfCString errorMessage("%s attribute setter",
@@ -290,34 +295,112 @@ ErrorResult::StealJSException(JSContext*
              "Must call WouldReportJSException unconditionally in all codepaths that might call StealJSException");
   MOZ_ASSERT(IsJSException(), "No exception to steal");
 
   value.set(mJSException);
   js::RemoveRawValueRoot(cx, &mJSException);
   mResult = NS_OK;
 }
 
+struct ErrorResult::DOMExceptionInfo {
+  DOMExceptionInfo(nsresult rv, const nsACString& message)
+    : mMessage(message)
+    , mRv(rv)
+  {}
+
+  nsCString mMessage;
+  nsresult mRv;
+};
+
+void
+ErrorResult::SerializeDOMExceptionInfo(IPC::Message* aMsg) const
+{
+  using namespace IPC;
+  MOZ_ASSERT(mDOMExceptionInfo);
+  MOZ_ASSERT(mHasDOMExceptionInfo);
+  WriteParam(aMsg, mDOMExceptionInfo->mMessage);
+  WriteParam(aMsg, mDOMExceptionInfo->mRv);
+}
+
+bool
+ErrorResult::DeserializeDOMExceptionInfo(const IPC::Message* aMsg, void** aIter)
+{
+  using namespace IPC;
+  nsCString message;
+  nsresult rv;
+  if (!ReadParam(aMsg, aIter, &message) ||
+      !ReadParam(aMsg, aIter, &rv)) {
+    return false;
+  }
+
+  MOZ_ASSERT(!mHasDOMExceptionInfo);
+  MOZ_ASSERT(IsDOMException());
+  mDOMExceptionInfo = new DOMExceptionInfo(rv, message);
+#ifdef DEBUG
+  mHasDOMExceptionInfo = true;
+#endif
+  return true;
+}
+
+void
+ErrorResult::ThrowDOMException(nsresult rv, const nsACString& message)
+{
+  ClearUnionData();
+
+  mResult = NS_ERROR_DOM_DOMEXCEPTION;
+  mDOMExceptionInfo = new DOMExceptionInfo(rv, message);
+#ifdef DEBUG
+  mHasDOMExceptionInfo = true;
+#endif
+}
+
+void
+ErrorResult::ReportDOMException(JSContext* cx)
+{
+  MOZ_ASSERT(mDOMExceptionInfo, "ReportDOMException() can be called only once");
+  MOZ_ASSERT(mHasDOMExceptionInfo);
+
+  dom::Throw(cx, mDOMExceptionInfo->mRv, mDOMExceptionInfo->mMessage);
+
+  ClearDOMExceptionInfo();
+}
+
+void
+ErrorResult::ClearDOMExceptionInfo()
+{
+  MOZ_ASSERT(IsDOMException());
+  MOZ_ASSERT(mHasDOMExceptionInfo || !mDOMExceptionInfo);
+  delete mDOMExceptionInfo;
+  mDOMExceptionInfo = nullptr;
+#ifdef DEBUG
+  mHasDOMExceptionInfo = false;
+#endif
+}
+
 void
 ErrorResult::ClearUnionData()
 {
   if (IsJSException()) {
     JSContext* cx = nsContentUtils::GetDefaultJSContextForThread();
     MOZ_ASSERT(cx);
     mJSException.setUndefined();
     js::RemoveRawValueRoot(cx, &mJSException);
   } else if (IsErrorWithMessage()) {
     ClearMessage();
+  } else if (IsDOMException()) {
+    ClearDOMExceptionInfo();
   }
 }
 
 void
 ErrorResult::ReportGenericError(JSContext* cx)
 {
   MOZ_ASSERT(!IsErrorWithMessage());
   MOZ_ASSERT(!IsJSException());
+  MOZ_ASSERT(!IsDOMException());
   dom::Throw(cx, ErrorCode());
 }
 
 ErrorResult&
 ErrorResult::operator=(ErrorResult&& aRHS)
 {
   // Clear out any union members we may have right now, before we
   // start writing to it.
@@ -339,21 +422,29 @@ ErrorResult::operator=(ErrorResult&& aRH
     MOZ_ASSERT(cx);
     mJSException.setUndefined();
     if (!js::AddRawValueRoot(cx, &mJSException, "ErrorResult::mJSException")) {
       MOZ_CRASH("Could not root mJSException, we're about to OOM");
     }
     mJSException = aRHS.mJSException;
     aRHS.mJSException.setUndefined();
     js::RemoveRawValueRoot(cx, &aRHS.mJSException);
+  } else if (aRHS.IsDOMException()) {
+    mDOMExceptionInfo = aRHS.mDOMExceptionInfo;
+    aRHS.mDOMExceptionInfo = nullptr;
+#ifdef DEBUG
+    mHasDOMExceptionInfo = aRHS.mHasDOMExceptionInfo;
+    aRHS.mHasDOMExceptionInfo = false;
+#endif // DEBUG
   } else {
     // Null out the union on both sides for hygiene purposes.
     mMessage = aRHS.mMessage = nullptr;
 #ifdef DEBUG
     mHasMessage = aRHS.mHasMessage = false;
+    mHasDOMExceptionInfo = aRHS.mHasDOMExceptionInfo = false;
 #endif
   }
   // Note: It's important to do this last, since this affects the condition
   // checks above!
   mResult = aRHS.mResult;
   aRHS.mResult = NS_OK;
   return *this;
 }
--- a/dom/bindings/ErrorIPCUtils.h
+++ b/dom/bindings/ErrorIPCUtils.h
@@ -37,33 +37,47 @@ struct ParamTraits<mozilla::ErrorResult>
         || aParam.mMightHaveUnreportedJSException
 #endif
         ) {
       MOZ_CRASH("Cannot encode an ErrorResult representing a Javascript exception");
     }
 
     WriteParam(aMsg, aParam.mResult);
     WriteParam(aMsg, aParam.IsErrorWithMessage());
+    WriteParam(aMsg, aParam.IsDOMException());
     if (aParam.IsErrorWithMessage()) {
       aParam.SerializeMessage(aMsg);
+    } else if (aParam.IsDOMException()) {
+      aParam.SerializeDOMExceptionInfo(aMsg);
     }
   }
 
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
     paramType readValue;
     if (!ReadParam(aMsg, aIter, &readValue.mResult)) {
       return false;
     }
     bool hasMessage = false;
     if (!ReadParam(aMsg, aIter, &hasMessage)) {
       return false;
     }
+    bool hasDOMExceptionInfo = false;
+    if (!ReadParam(aMsg, aIter, &hasDOMExceptionInfo)) {
+      return false;
+    }
+    if (hasMessage && hasDOMExceptionInfo) {
+      // Shouldn't have both!
+      return false;
+    }
     if (hasMessage && !readValue.DeserializeMessage(aMsg, aIter)) {
       return false;
+    } else if (hasDOMExceptionInfo &&
+               !readValue.DeserializeDOMExceptionInfo(aMsg, aIter)) {
+      return false;
     }
     *aResult = Move(readValue);
     return true;
   }
 };
 
 } // namespace IPC
 
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -71,30 +71,33 @@ struct StringArrayAppender
     Append(aArgs, aCount - 1, aOtherArgs...);
   }
 };
 
 } // namespace dom
 
 class ErrorResult {
 public:
-  ErrorResult() {
-    mResult = NS_OK;
-
+  ErrorResult()
+    : mResult(NS_OK)
 #ifdef DEBUG
-    mMightHaveUnreportedJSException = false;
-    mHasMessage = false;
+    , mMightHaveUnreportedJSException(false)
+    , mHasMessage(false)
+    , mHasDOMExceptionInfo(false)
 #endif
+  {
   }
 
 #ifdef DEBUG
   ~ErrorResult() {
     MOZ_ASSERT_IF(IsErrorWithMessage(), !mMessage);
+    MOZ_ASSERT_IF(IsDOMException(), !mDOMExceptionInfo);
     MOZ_ASSERT(!mMightHaveUnreportedJSException);
     MOZ_ASSERT(!mHasMessage);
+    MOZ_ASSERT(!mHasDOMExceptionInfo);
   }
 #endif
 
   ErrorResult(ErrorResult&& aRHS)
     // Initialize mResult and whatever else we need to default-initialize, so
     // the ClearUnionData call in our operator= will do the right thing
     // (nothing).
     : ErrorResult()
@@ -152,16 +155,25 @@ public:
   //
   // The exn argument to ThrowJSException can be in any compartment.  It does
   // not have to be in the compartment of cx.  If someone later uses it, they
   // will wrap it into whatever compartment they're working in, as needed.
   void ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
   void ReportJSException(JSContext* cx);
   bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; }
 
+  // Facilities for throwing a DOMException.  If an empty message string is
+  // passed to ThrowDOMException, the default message string for the given
+  // nsresult will be used.  The passed-in string must be UTF-8.  The nsresult
+  // passed in must be one we create DOMExceptions for; otherwise you may get an
+  // XPConnect Exception.
+  void ThrowDOMException(nsresult rv, const nsACString& message = EmptyCString());
+  void ReportDOMException(JSContext* cx);
+  bool IsDOMException() const { return ErrorCode() == NS_ERROR_DOM_DOMEXCEPTION; }
+
   // Report a generic error.  This should only be used if we're not
   // some more specific exception type.
   void ReportGenericError(JSContext* cx);
 
   // Support for uncatchable exceptions.
   void ThrowUncatchableException() {
     Throw(NS_ERROR_UNCATCHABLE_EXCEPTION);
   }
@@ -216,16 +228,19 @@ protected:
     return mResult;
   }
 
 private:
   friend struct IPC::ParamTraits<ErrorResult>;
   void SerializeMessage(IPC::Message* aMsg) const;
   bool DeserializeMessage(const IPC::Message* aMsg, void** aIter);
 
+  void SerializeDOMExceptionInfo(IPC::Message* aMsg) const;
+  bool DeserializeDOMExceptionInfo(const IPC::Message* aMsg, void** aIter);
+
   // Helper method that creates a new Message for this ErrorResult,
   // and returns the arguments array from that Message.
   nsTArray<nsString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType);
 
   template<dom::ErrNum errorNumber, typename... Ts>
   void ThrowErrorWithMessage(nsresult errorType, Ts... messageArgs)
   {
 #if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
@@ -244,48 +259,68 @@ private:
   }
 
   void AssignErrorCode(nsresult aRv) {
     MOZ_ASSERT(aRv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()");
     MOZ_ASSERT(aRv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()");
     MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message");
     MOZ_ASSERT(aRv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()");
     MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions");
+    MOZ_ASSERT(aRv != NS_ERROR_DOM_DOMEXCEPTION, "Use ThrowDOMException()");
+    MOZ_ASSERT(!IsDOMException(), "Don't overwrite DOM exceptions");
     MOZ_ASSERT(aRv != NS_ERROR_XPC_NOT_ENOUGH_ARGS, "May need to bring back ThrowNotEnoughArgsError");
     mResult = aRv;
   }
 
   void ClearMessage();
+  void ClearDOMExceptionInfo();
 
-  // ClearUnionData will try to clear the data in our mMessage/mJSException
-  // union.  After this the union may be in an uninitialized state
-  // (e.g. mMessage may be pointing to deleted memory) and the caller must
-  // either reinitialize it or change mResult to something that will not involve
-  // us touching the union anymore.
+  // ClearUnionData will try to clear the data in our
+  // mMessage/mJSException/mDOMExceptionInfo union.  After this the union may be
+  // in an uninitialized state (e.g. mMessage or mDOMExceptionInfo may be
+  // pointing to deleted memory) and the caller must either reinitialize it or
+  // change mResult to something that will not involve us touching the union
+  // anymore.
   void ClearUnionData();
 
+  // Special values of mResult:
+  // NS_ERROR_TYPE_ERR -- ThrowTypeError() called on us.
+  // NS_ERROR_RANGE_ERR -- ThrowRangeError() called on us.
+  // NS_ERROR_DOM_JS_EXCEPTION -- ThrowJSException() called on us.
+  // NS_ERROR_UNCATCHABLE_EXCEPTION -- ThrowUncatchableException called on us.
+  // NS_ERROR_DOM_DOMEXCEPTION -- ThrowDOMException() called on us.
   nsresult mResult;
+
   struct Message;
-  // mMessage is set by ThrowErrorWithMessage and cleared (and deallocated) by
+  struct DOMExceptionInfo;
+  // mMessage is set by ThrowErrorWithMessage and reported (and deallocated) by
   // ReportErrorWithMessage.
-  // mJSException is set (and rooted) by ThrowJSException and unrooted
-  // by ReportJSException.
+  // mJSException is set (and rooted) by ThrowJSException and reported
+  // (and unrooted) by ReportJSException.
+  // mDOMExceptionInfo is set by ThrowDOMException and reported
+  // (and deallocated) by ReportDOMException.
   union {
     Message* mMessage; // valid when IsErrorWithMessage()
     JS::Value mJSException; // valid when IsJSException()
+    DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException()
   };
 
 #ifdef DEBUG
   // Used to keep track of codepaths that might throw JS exceptions,
   // for assertion purposes.
   bool mMightHaveUnreportedJSException;
   // Used to keep track of whether mMessage has ever been assigned to.
   // We need to check this in order to ensure that not attempting to
   // delete mMessage in DeserializeMessage doesn't leak memory.
   bool mHasMessage;
+  // Used to keep track of whether mDOMExceptionInfo has ever been assigned
+  // to.  We need to check this in order to ensure that not attempting to
+  // delete mDOMExceptionInfo in DeserializeDOMExceptionInfo doesn't leak
+  // memory.
+  bool mHasDOMExceptionInfo;
 #endif
 
   // Not to be implemented, to make sure people always pass this by
   // reference, not by value.
   ErrorResult(const ErrorResult&) = delete;
   void operator=(const ErrorResult&) = delete;
 };
 
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -538,16 +538,20 @@
   ERROR(NS_ERROR_DOM_BAD_URI,                      FAILURE(1012)),
   ERROR(NS_ERROR_DOM_RETVAL_UNDEFINED,             FAILURE(1013)),
   ERROR(NS_ERROR_DOM_QUOTA_REACHED,                FAILURE(1014)),
   ERROR(NS_ERROR_DOM_JS_EXCEPTION,                 FAILURE(1015)),
 
   /* A way to represent uncatchable exceptions */
   ERROR(NS_ERROR_UNCATCHABLE_EXCEPTION,            FAILURE(1016)),
 
+  /* An nsresult value to use in ErrorResult to indicate that we want to throw
+     a DOMException */
+  ERROR(NS_ERROR_DOM_DOMEXCEPTION,                 FAILURE(1017)),
+
   /* May be used to indicate when e.g. setting a property value didn't
    * actually change the value, like for obj.foo = "bar"; obj.foo = "bar";
    * the second assignment throws NS_SUCCESS_DOM_NO_OPERATION.
    */
   ERROR(NS_SUCCESS_DOM_NO_OPERATION,               SUCCESS(1)),
 
   /*
    * A success code that indicates that evaluating a string of JS went