Bug 1213289 part 3. Add a way to throw a DOMException with a custom message on ErrorResult. r=bkelly

This commit is contained in:
Boris Zbarsky 2015-10-09 16:48:10 -04:00
parent 267ff39c13
commit 70e8c1f364
5 changed files with 158 additions and 13 deletions

View File

@ -147,3 +147,4 @@ DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to F
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")

View File

@ -36,6 +36,7 @@
#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"
@ -142,6 +143,10 @@ ThrowMethodFailed(JSContext* cx, ErrorResult& rv)
rv.ReportJSException(cx);
return false;
}
if (rv.IsDOMException()) {
rv.ReportDOMException(cx);
return false;
}
rv.ReportGenericError(cx);
return false;
}
@ -295,6 +300,81 @@ ErrorResult::StealJSException(JSContext* cx,
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()
{
@ -305,6 +385,8 @@ ErrorResult::ClearUnionData()
js::RemoveRawValueRoot(cx, &mJSException);
} else if (IsErrorWithMessage()) {
ClearMessage();
} else if (IsDOMException()) {
ClearDOMExceptionInfo();
}
}
@ -313,6 +395,7 @@ ErrorResult::ReportGenericError(JSContext* cx)
{
MOZ_ASSERT(!IsErrorWithMessage());
MOZ_ASSERT(!IsJSException());
MOZ_ASSERT(!IsDOMException());
dom::Throw(cx, ErrorCode());
}
@ -344,11 +427,19 @@ ErrorResult::operator=(ErrorResult&& aRHS)
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

View File

@ -42,8 +42,11 @@ struct ParamTraits<mozilla::ErrorResult>
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);
}
}
@ -57,8 +60,19 @@ struct ParamTraits<mozilla::ErrorResult>
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;

View File

@ -76,20 +76,23 @@ struct StringArrayAppender
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
@ -157,6 +160,15 @@ public:
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);
@ -221,6 +233,9 @@ private:
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);
@ -249,28 +264,43 @@ private:
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
@ -281,6 +311,11 @@ private:
// 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

View File

@ -543,6 +543,10 @@
/* 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.