Bug 933378 part 1. Introduce a TErrorResult class that will serve as a base class for various ErrorResult-like subclasses. No actual behavior changes so far. r=bkelly
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 15 Jul 2016 22:35:13 -0400
changeset 345248 d2cbe0d4b29aa2981e8a443651e6b966840a84f2
parent 345247 a97ebab940c6ba0f19348c38d17a2193ee75c9ab
child 345249 a3834c6ce6d7fac2787923d7efb1da443fe785eb
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs933378
milestone50.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 933378 part 1. Introduce a TErrorResult class that will serve as a base class for various ErrorResult-like subclasses. No actual behavior changes so far. r=bkelly
dom/bindings/BindingUtils.cpp
dom/bindings/ErrorResult.h
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -138,53 +138,61 @@ ThrowNoSetterArg(JSContext* aCx, prototy
 {
   nsPrintfCString errorMessage("%s attribute setter",
                                NamesOfInterfacesWithProtos(aProtoId));
   return ThrowErrorMessage(aCx, MSG_MISSING_ARGUMENTS, errorMessage.get());
 }
 
 } // namespace dom
 
-struct ErrorResult::Message {
-  Message() { MOZ_COUNT_CTOR(ErrorResult::Message); }
-  ~Message() { MOZ_COUNT_DTOR(ErrorResult::Message); }
+namespace binding_danger {
+
+template<typename CleanupPolicy>
+struct TErrorResult<CleanupPolicy>::Message {
+  Message() { MOZ_COUNT_CTOR(TErrorResult::Message); }
+  ~Message() { MOZ_COUNT_DTOR(TErrorResult::Message); }
 
   nsTArray<nsString> mArgs;
   dom::ErrNum mErrorNumber;
 
   bool HasCorrectNumberOfArguments()
   {
     return GetErrorArgCount(mErrorNumber) == mArgs.Length();
   }
 };
 
+template<typename CleanupPolicy>
 nsTArray<nsString>&
-ErrorResult::CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType)
+TErrorResult<CleanupPolicy>::CreateErrorMessageHelper(const dom::ErrNum errorNumber,
+                                                      nsresult errorType)
 {
   AssertInOwningThread();
   mResult = errorType;
 
   mMessage = new Message();
   mMessage->mErrorNumber = errorNumber;
   return mMessage->mArgs;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SerializeMessage(IPC::Message* aMsg) const
+TErrorResult<CleanupPolicy>::SerializeMessage(IPC::Message* aMsg) const
 {
   using namespace IPC;
   AssertInOwningThread();
   MOZ_ASSERT(mUnionState == HasMessage);
   MOZ_ASSERT(mMessage);
   WriteParam(aMsg, mMessage->mArgs);
   WriteParam(aMsg, mMessage->mErrorNumber);
 }
 
+template<typename CleanupPolicy>
 bool
-ErrorResult::DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter)
+TErrorResult<CleanupPolicy>::DeserializeMessage(const IPC::Message* aMsg,
+                                                PickleIterator* aIter)
 {
   using namespace IPC;
   AssertInOwningThread();
   nsAutoPtr<Message> readMessage(new Message());
   if (!ReadParam(aMsg, aIter, &readMessage->mArgs) ||
       !ReadParam(aMsg, aIter, &readMessage->mErrorNumber)) {
     return false;
   }
@@ -195,18 +203,19 @@ ErrorResult::DeserializeMessage(const IP
   MOZ_ASSERT(mUnionState == HasNothing);
   mMessage = readMessage.forget();
 #ifdef DEBUG
   mUnionState = HasMessage;
 #endif // DEBUG
   return true;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SetPendingExceptionWithMessage(JSContext* aCx)
+TErrorResult<CleanupPolicy>::SetPendingExceptionWithMessage(JSContext* aCx)
 {
   AssertInOwningThread();
   MOZ_ASSERT(mMessage, "SetPendingExceptionWithMessage() can be called only once");
   MOZ_ASSERT(mUnionState == HasMessage);
 
   Message* message = mMessage;
   MOZ_RELEASE_ASSERT(message->HasCorrectNumberOfArguments());
   const uint32_t argCount = message->mArgs.Length();
@@ -219,56 +228,59 @@ ErrorResult::SetPendingExceptionWithMess
   JS_ReportErrorNumberUCArray(aCx, dom::GetErrorMessage, nullptr,
                               static_cast<const unsigned>(message->mErrorNumber),
                               argCount > 0 ? args : nullptr);
 
   ClearMessage();
   mResult = NS_OK;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::ClearMessage()
+TErrorResult<CleanupPolicy>::ClearMessage()
 {
   AssertInOwningThread();
   MOZ_ASSERT(IsErrorWithMessage());
   delete mMessage;
   mMessage = nullptr;
 #ifdef DEBUG
   mUnionState = HasNothing;
 #endif // DEBUG
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
+TErrorResult<CleanupPolicy>::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
 {
   AssertInOwningThread();
   MOZ_ASSERT(mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to throw a JS exception?");
 
   ClearUnionData();
 
   // Make sure mJSException is initialized _before_ we try to root it.  But
   // don't set it to exn yet, because we don't want to do that until after we
   // root.
   mJSException.setUndefined();
-  if (!js::AddRawValueRoot(cx, &mJSException, "ErrorResult::mJSException")) {
+  if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) {
     // Don't use NS_ERROR_DOM_JS_EXCEPTION, because that indicates we have
     // in fact rooted mJSException.
     mResult = NS_ERROR_OUT_OF_MEMORY;
   } else {
     mJSException = exn;
     mResult = NS_ERROR_DOM_JS_EXCEPTION;
 #ifdef DEBUG
     mUnionState = HasJSException;
 #endif // DEBUG
   }
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SetPendingJSException(JSContext* cx)
+TErrorResult<CleanupPolicy>::SetPendingJSException(JSContext* cx)
 {
   AssertInOwningThread();
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to handle JS exceptions?");
   MOZ_ASSERT(mUnionState == HasJSException);
 
   JS::Rooted<JS::Value> exception(cx, mJSException);
   if (JS_WrapValue(cx, &exception)) {
@@ -280,39 +292,43 @@ ErrorResult::SetPendingJSException(JSCon
   js::RemoveRawValueRoot(cx, &mJSException);
 
   mResult = NS_OK;
 #ifdef DEBUG
   mUnionState = HasNothing;
 #endif // DEBUG
 }
 
-struct ErrorResult::DOMExceptionInfo {
+template<typename CleanupPolicy>
+struct TErrorResult<CleanupPolicy>::DOMExceptionInfo {
   DOMExceptionInfo(nsresult rv, const nsACString& message)
     : mMessage(message)
     , mRv(rv)
   {}
 
   nsCString mMessage;
   nsresult mRv;
 };
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SerializeDOMExceptionInfo(IPC::Message* aMsg) const
+TErrorResult<CleanupPolicy>::SerializeDOMExceptionInfo(IPC::Message* aMsg) const
 {
   using namespace IPC;
   AssertInOwningThread();
   MOZ_ASSERT(mDOMExceptionInfo);
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
   WriteParam(aMsg, mDOMExceptionInfo->mMessage);
   WriteParam(aMsg, mDOMExceptionInfo->mRv);
 }
 
+template<typename CleanupPolicy>
 bool
-ErrorResult::DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterator* aIter)
+TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(const IPC::Message* aMsg,
+                                                         PickleIterator* aIter)
 {
   using namespace IPC;
   AssertInOwningThread();
   nsCString message;
   nsresult rv;
   if (!ReadParam(aMsg, aIter, &message) ||
       !ReadParam(aMsg, aIter, &rv)) {
     return false;
@@ -322,58 +338,63 @@ ErrorResult::DeserializeDOMExceptionInfo
   MOZ_ASSERT(IsDOMException());
   mDOMExceptionInfo = new DOMExceptionInfo(rv, message);
 #ifdef DEBUG
   mUnionState = HasDOMExceptionInfo;
 #endif // DEBUG
   return true;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::ThrowDOMException(nsresult rv, const nsACString& message)
+TErrorResult<CleanupPolicy>::ThrowDOMException(nsresult rv,
+                                               const nsACString& message)
 {
   AssertInOwningThread();
   ClearUnionData();
 
   mResult = NS_ERROR_DOM_DOMEXCEPTION;
   mDOMExceptionInfo = new DOMExceptionInfo(rv, message);
 #ifdef DEBUG
   mUnionState = HasDOMExceptionInfo;
 #endif
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SetPendingDOMException(JSContext* cx)
+TErrorResult<CleanupPolicy>::SetPendingDOMException(JSContext* cx)
 {
   AssertInOwningThread();
   MOZ_ASSERT(mDOMExceptionInfo,
              "SetPendingDOMException() can be called only once");
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
 
   dom::Throw(cx, mDOMExceptionInfo->mRv, mDOMExceptionInfo->mMessage);
 
   ClearDOMExceptionInfo();
   mResult = NS_OK;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::ClearDOMExceptionInfo()
+TErrorResult<CleanupPolicy>::ClearDOMExceptionInfo()
 {
   AssertInOwningThread();
   MOZ_ASSERT(IsDOMException());
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo || !mDOMExceptionInfo);
   delete mDOMExceptionInfo;
   mDOMExceptionInfo = nullptr;
 #ifdef DEBUG
   mUnionState = HasNothing;
 #endif // DEBUG
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::ClearUnionData()
+TErrorResult<CleanupPolicy>::ClearUnionData()
 {
   AssertInOwningThread();
   if (IsJSException()) {
     JSContext* cx = nsContentUtils::RootingCx();
     MOZ_ASSERT(cx);
     mJSException.setUndefined();
     js::RemoveRawValueRoot(cx, &mJSException);
 #ifdef DEBUG
@@ -381,29 +402,31 @@ ErrorResult::ClearUnionData()
 #endif // DEBUG
   } else if (IsErrorWithMessage()) {
     ClearMessage();
   } else if (IsDOMException()) {
     ClearDOMExceptionInfo();
   }
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SetPendingGenericErrorException(JSContext* cx)
+TErrorResult<CleanupPolicy>::SetPendingGenericErrorException(JSContext* cx)
 {
   AssertInOwningThread();
   MOZ_ASSERT(!IsErrorWithMessage());
   MOZ_ASSERT(!IsJSException());
   MOZ_ASSERT(!IsDOMException());
   dom::Throw(cx, ErrorCode());
   mResult = NS_OK;
 }
 
-ErrorResult&
-ErrorResult::operator=(ErrorResult&& aRHS)
+template<typename CleanupPolicy>
+TErrorResult<CleanupPolicy>&
+TErrorResult<CleanupPolicy>::operator=(TErrorResult<CleanupPolicy>&& aRHS)
 {
   AssertInOwningThread();
   aRHS.AssertInOwningThread();
   // Clear out any union members we may have right now, before we
   // start writing to it.
   ClearUnionData();
 
 #ifdef DEBUG
@@ -412,17 +435,17 @@ ErrorResult::operator=(ErrorResult&& aRH
 #endif
   if (aRHS.IsErrorWithMessage()) {
     mMessage = aRHS.mMessage;
     aRHS.mMessage = nullptr;
   } else if (aRHS.IsJSException()) {
     JSContext* cx = nsContentUtils::RootingCx();
     MOZ_ASSERT(cx);
     mJSException.setUndefined();
-    if (!js::AddRawValueRoot(cx, &mJSException, "ErrorResult::mJSException")) {
+    if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::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;
@@ -438,18 +461,19 @@ ErrorResult::operator=(ErrorResult&& aRH
 
   // Note: It's important to do this last, since this affects the condition
   // checks above!
   mResult = aRHS.mResult;
   aRHS.mResult = NS_OK;
   return *this;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::CloneTo(ErrorResult& aRv) const
+TErrorResult<CleanupPolicy>::CloneTo(TErrorResult& aRv) const
 {
   AssertInOwningThread();
   aRv.AssertInOwningThread();
 
   aRv.ClearUnionData();
   aRv.mResult = mResult;
 #ifdef DEBUG
   aRv.mMightHaveUnreportedJSException = mMightHaveUnreportedJSException;
@@ -473,29 +497,31 @@ ErrorResult::CloneTo(ErrorResult& aRv) c
     aRv.mUnionState = HasJSException;
 #endif
     JSContext* cx = nsContentUtils::RootingCx();
     JS::Rooted<JS::Value> exception(cx, mJSException);
     aRv.ThrowJSException(cx, exception);
   }
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SuppressException()
+TErrorResult<CleanupPolicy>::SuppressException()
 {
   AssertInOwningThread();
   WouldReportJSException();
   ClearUnionData();
   // We don't use AssignErrorCode, because we want to override existing error
   // states, which AssignErrorCode is not allowed to do.
   mResult = NS_OK;
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::SetPendingException(JSContext* cx)
+TErrorResult<CleanupPolicy>::SetPendingException(JSContext* cx)
 {
   AssertInOwningThread();
   if (IsUncatchableException()) {
     // Nuke any existing exception on cx, to make sure we're uncatchable.
     JS_ClearPendingException(cx);
     // Don't do any reporting.  Just return, to create an
     // uncatchable exception.
     mResult = NS_OK;
@@ -517,44 +543,50 @@ ErrorResult::SetPendingException(JSConte
   }
   if (IsDOMException()) {
     SetPendingDOMException(cx);
     return;
   }
   SetPendingGenericErrorException(cx);
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::StealExceptionFromJSContext(JSContext* cx)
+TErrorResult<CleanupPolicy>::StealExceptionFromJSContext(JSContext* cx)
 {
   AssertInOwningThread();
   MOZ_ASSERT(mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to throw a JS exception?");
 
   JS::Rooted<JS::Value> exn(cx);
   if (!JS_GetPendingException(cx, &exn)) {
     ThrowUncatchableException();
     return;
   }
 
   ThrowJSException(cx, exn);
   JS_ClearPendingException(cx);
 }
 
+template<typename CleanupPolicy>
 void
-ErrorResult::NoteJSContextException(JSContext* aCx)
+TErrorResult<CleanupPolicy>::NoteJSContextException(JSContext* aCx)
 {
   AssertInOwningThread();
   if (JS_IsExceptionPending(aCx)) {
     mResult = NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
   } else {
     mResult = NS_ERROR_UNCATCHABLE_EXCEPTION;
   }
 }
 
+template class TErrorResult<JustAssertCleanupPolicy>;
+
+} // namespace binding_danger
+
 namespace dom {
 
 bool
 DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
                 const ConstantSpec* cs)
 {
   JS::Rooted<JS::Value> value(cx);
   for (; cs->name; ++cs) {
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -1,27 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 /**
- * A struct for tracking exceptions that need to be thrown to JS.
+ * A set of structs for tracking exceptions that need to be thrown to JS:
+ * ErrorResult and IgnoredErrorResult.
  *
- * Conceptually, an ErrorResult represents either success or an exception in the
+ * Conceptually, these structs represent either success or an exception in the
  * process of being thrown.  This means that a failing ErrorResult _must_ be
  * handled in one of the following ways before coming off the stack:
  *
  * 1) Suppressed via SuppressException().
  * 2) Converted to a pure nsresult return value via StealNSResult().
  * 3) Converted to an actual pending exception on a JSContext via
  *    MaybeSetPendingException.
  * 4) Converted to an exception JS::Value (probably to then reject a Promise
  *    with) via dom::ToJSValue.
+ *
+ * An IgnoredErrorResult will automatically do the first of those four things.
  */
 
 #ifndef mozilla_ErrorResult_h
 #define mozilla_ErrorResult_h
 
 #include <stdarg.h>
 
 #include "js/GCAnnotations.h"
@@ -83,83 +86,105 @@ struct StringArrayAppender
     }
     aArgs.AppendElement(aFirst);
     Append(aArgs, aCount - 1, Forward<Ts>(aOtherArgs)...);
   }
 };
 
 } // namespace dom
 
-class ErrorResult {
+class ErrorResult;
+
+namespace binding_danger {
+
+/**
+ * Templated implementation class for various ErrorResult-like things.  The
+ * instantiations differ only in terms of their cleanup policies (used in the
+ * destructor), which they can specify via the template argument.  Note that
+ * this means it's safe to reinterpret_cast between the instantiations unless
+ * you plan to invoke the destructor through such a cast pointer.
+ *
+ * A cleanup policy consists of two booleans: whether to assert that we've been
+ * reported or suppressed, and whether to then go ahead and suppress the
+ * exception.
+ */
+template<typename CleanupPolicy>
+class TErrorResult {
 public:
-  ErrorResult()
+  TErrorResult()
     : mResult(NS_OK)
 #ifdef DEBUG
     , mMightHaveUnreportedJSException(false)
     , mUnionState(HasNothing)
 #endif
   {
   }
 
-#ifdef DEBUG
-  ~ErrorResult() {
-    // Consumers should have called one of MaybeSetPendingException
-    // (possibly via ToJSValue), StealNSResult, and SuppressException
-    MOZ_ASSERT(!Failed());
-    MOZ_ASSERT(!mMightHaveUnreportedJSException);
-    MOZ_ASSERT(mUnionState == HasNothing);
-    NS_ASSERT_OWNINGTHREAD(ErrorResult);
+  ~TErrorResult() {
+    AssertInOwningThread();
+
+    if (CleanupPolicy::assertHandled) {
+      // Consumers should have called one of MaybeSetPendingException
+      // (possibly via ToJSValue), StealNSResult, and SuppressException
+      AssertReportedOrSuppressed();
+    }
+
+    if (CleanupPolicy::suppress) {
+      SuppressException();
+    }
+
+    // And now assert that we're in a good final state.
+    AssertReportedOrSuppressed();
   }
-#endif // DEBUG
 
-  ErrorResult(ErrorResult&& aRHS)
+  TErrorResult(TErrorResult&& 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()
+    : TErrorResult()
   {
     *this = Move(aRHS);
   }
-  ErrorResult& operator=(ErrorResult&& aRHS);
+  TErrorResult& operator=(TErrorResult&& aRHS);
 
-  explicit ErrorResult(nsresult aRv)
-    : ErrorResult()
+  explicit TErrorResult(nsresult aRv)
+    : TErrorResult()
   {
     AssignErrorCode(aRv);
   }
 
   void Throw(nsresult rv) {
     MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success");
     AssignErrorCode(rv);
   }
 
-  // Duplicate our current state on the given ErrorResult object.  Any existing
-  // errors or messages on the target will be suppressed before cloning.  Our
-  // own error state remains unchanged.
-  void CloneTo(ErrorResult& aRv) const;
+  // Duplicate our current state on the given TErrorResult object.  Any
+  // existing errors or messages on the target will be suppressed before
+  // cloning.  Our own error state remains unchanged.
+  void CloneTo(TErrorResult& aRv) const;
 
   // Use SuppressException when you want to suppress any exception that might be
-  // on the ErrorResult.  After this call, the ErrorResult will be back a "no
+  // on the TErrorResult.  After this call, the TErrorResult will be back a "no
   // exception thrown" state.
   void SuppressException();
 
-  // Use StealNSResult() when you want to safely convert the ErrorResult to an
-  // nsresult that you will then return to a caller.  This will
+  // Use StealNSResult() when you want to safely convert the TErrorResult to
+  // an nsresult that you will then return to a caller.  This will
   // SuppressException(), since there will no longer be a way to report it.
   nsresult StealNSResult() {
     nsresult rv = ErrorCode();
     SuppressException();
     return rv;
   }
 
-  // Use MaybeSetPendingException to convert an ErrorResult to a pending
+  // Use MaybeSetPendingException to convert a TErrorResult to a pending
   // exception on the given JSContext.  This is the normal "throw an exception"
   // codepath.
   //
-  // The return value is false if the ErrorResult represents success, true
+  // The return value is false if the TErrorResult represents success, true
   // otherwise.  This does mean that in JSAPI method implementations you can't
   // just use this as |return rv.MaybeSetPendingException(cx)| (though you could
   // |return !rv.MaybeSetPendingException(cx)|), but in practice pretty much any
   // consumer would want to do some more work on the success codepath.  So
   // instead the way you use this is:
   //
   //   if (rv.MaybeSetPendingException(cx)) {
   //     bail out here
@@ -170,31 +195,31 @@ public:
   // want to pay the price of a function call in some of the consumers of this
   // method in the common case.
   //
   // Note that a true return value does NOT mean there is now a pending
   // exception on aCx, due to uncatchable exceptions.  It should still be
   // considered equivalent to a JSAPI failure in terms of what callers should do
   // after true is returned.
   //
-  // After this call, the ErrorResult will no longer return true from Failed(),
+  // After this call, the TErrorResult will no longer return true from Failed(),
   // since the exception will have moved to the JSContext.
   bool MaybeSetPendingException(JSContext* cx)
   {
     WouldReportJSException();
     if (!Failed()) {
       return false;
     }
 
     SetPendingException(cx);
     return true;
   }
 
   // Use StealExceptionFromJSContext to convert a pending exception on a
-  // JSContext to an ErrorResult.  This function must be called only when a
+  // JSContext to a TErrorResult.  This function must be called only when a
   // JSAPI operation failed.  It assumes that lack of pending exception on the
   // JSContext means an uncatchable exception was thrown.
   //
   // Codepaths that might call this method must call MightThrowJSException even
   // if the relevant JSAPI calls do not fail.
   //
   // When this function returns, JS_IsExceptionPending(cx) will definitely be
   // false.
@@ -212,42 +237,42 @@ public:
   {
     ThrowErrorWithMessage<errorNumber>(NS_ERROR_RANGE_ERR,
                                        Forward<Ts>(messageArgs)...);
   }
 
   bool IsErrorWithMessage() const { return ErrorCode() == NS_ERROR_TYPE_ERR || ErrorCode() == NS_ERROR_RANGE_ERR; }
 
   // Facilities for throwing a preexisting JS exception value via this
-  // ErrorResult.  The contract is that any code which might end up calling
+  // TErrorResult.  The contract is that any code which might end up calling
   // ThrowJSException() or StealExceptionFromJSContext() must call
   // MightThrowJSException() even if no exception is being thrown.  Code that
-  // conditionally calls ToJSValue on this ErrorResult only if Failed() must
-  // first call WouldReportJSException even if this ErrorResult has not failed.
+  // conditionally calls ToJSValue on this TErrorResult only if Failed() must
+  // first call WouldReportJSException even if this TErrorResult has not failed.
   //
   // 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);
   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());
   bool IsDOMException() const { return ErrorCode() == NS_ERROR_DOM_DOMEXCEPTION; }
 
-  // Flag on the ErrorResult that whatever needs throwing has been
+  // Flag on the TErrorResult that whatever needs throwing has been
   // thrown on the JSContext already and we should not mess with it.
   // If nothing was thrown, this becomes an uncatchable exception.
   void NoteJSContextException(JSContext* aCx);
 
-  // Check whether the ErrorResult says to just throw whatever is on
+  // Check whether the TErrorResult says to just throw whatever is on
   // the JSContext already.
   bool IsJSContextException() {
     return ErrorCode() == NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
   }
 
   // Support for uncatchable exceptions.
   void ThrowUncatchableException() {
     Throw(NS_ERROR_UNCATCHABLE_EXCEPTION);
@@ -303,24 +328,25 @@ private:
   enum UnionState {
     HasMessage,
     HasDOMExceptionInfo,
     HasJSException,
     HasNothing
   };
 #endif // DEBUG
 
+  friend struct IPC::ParamTraits<TErrorResult>;
   friend struct IPC::ParamTraits<ErrorResult>;
   void SerializeMessage(IPC::Message* aMsg) const;
   bool DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter);
 
   void SerializeDOMExceptionInfo(IPC::Message* aMsg) const;
   bool DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterator* aIter);
 
-  // Helper method that creates a new Message for this ErrorResult,
+  // Helper method that creates a new Message for this TErrorResult,
   // 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__))
     static_assert(dom::ErrorFormatNumArgs[errorNumber] == sizeof...(messageArgs),
@@ -335,17 +361,17 @@ private:
                                      Forward<Ts>(messageArgs)...);
 #ifdef DEBUG
     mUnionState = HasMessage;
 #endif // DEBUG
   }
 
   MOZ_ALWAYS_INLINE void AssertInOwningThread() const {
 #ifdef DEBUG
-    NS_ASSERT_OWNINGTHREAD(ErrorResult);
+    NS_ASSERT_OWNINGTHREAD(TErrorResult);
 #endif
   }
 
   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()");
@@ -374,16 +400,22 @@ private:
   void SetPendingException(JSContext* cx);
 
   // Methods for setting various specific kinds of pending exceptions.
   void SetPendingExceptionWithMessage(JSContext* cx);
   void SetPendingJSException(JSContext* cx);
   void SetPendingDOMException(JSContext* cx);
   void SetPendingGenericErrorException(JSContext* cx);
 
+  MOZ_ALWAYS_INLINE void AssertReportedOrSuppressed()
+  {
+    MOZ_ASSERT(!Failed());
+    MOZ_ASSERT(!mMightHaveUnreportedJSException);
+    MOZ_ASSERT(mUnionState == HasNothing);
+  }
 
   // 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;
@@ -408,22 +440,66 @@ private:
   bool mMightHaveUnreportedJSException;
 
   // Used to keep track of what's stored in our union right now.  Note
   // that this may be set to HasNothing even if our mResult suggests
   // we should have something, if we have already cleaned up the
   // something.
   UnionState mUnionState;
 
-  // The thread that created this ErrorResult
+  // The thread that created this TErrorResult
   NS_DECL_OWNINGTHREAD;
 #endif
 
   // Not to be implemented, to make sure people always pass this by
   // reference, not by value.
+  TErrorResult(const TErrorResult&) = delete;
+  void operator=(const TErrorResult&) = delete;
+};
+
+struct JustAssertCleanupPolicy {
+  static const bool assertHandled = true;
+  static const bool suppress = false;
+};
+
+} // namespace binding_danger
+
+// A class people should normally use on the stack when they plan to actually
+// do something with the exception.
+class ErrorResult : public binding_danger::TErrorResult<binding_danger::JustAssertCleanupPolicy>
+{
+  typedef binding_danger::TErrorResult<binding_danger::JustAssertCleanupPolicy> BaseErrorResult;
+
+public:
+  ErrorResult()
+    : BaseErrorResult()
+  {}
+
+  ErrorResult(ErrorResult&& aRHS)
+    : BaseErrorResult(Move(aRHS))
+  {}
+
+  explicit ErrorResult(nsresult aRv)
+    : BaseErrorResult(aRv)
+  {}
+
+  void operator=(nsresult rv)
+  {
+    BaseErrorResult::operator=(rv);
+  }
+
+  ErrorResult& operator=(ErrorResult&& aRHS)
+  {
+    BaseErrorResult::operator=(Move(aRHS));
+    return *this;
+  }
+
+private:
+  // 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 class for use when an ErrorResult should just automatically be ignored.
 class IgnoredErrorResult : public ErrorResult
 {
 public: