diff options
Diffstat (limited to 'dom/base/DOMException.cpp')
-rw-r--r-- | dom/base/DOMException.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp new file mode 100644 index 0000000000..d3e2fc42d3 --- /dev/null +++ b/dom/base/DOMException.cpp @@ -0,0 +1,448 @@ +/* -*- 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/. */ + +#include "mozilla/dom/DOMException.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/dom/Exceptions.h" +#include "nsContentUtils.h" +#include "nsCOMPtr.h" +#include "mozilla/dom/Document.h" +#include "nsIException.h" +#include "xpcprivate.h" + +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/ErrorResult.h" + +#include "js/TypeDecls.h" +#include "js/StructuredClone.h" + +using namespace mozilla; +using namespace mozilla::dom; + +enum DOM4ErrorTypeCodeMap { + /* DOM4 errors from + http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#domexception */ + IndexSizeError = DOMException_Binding::INDEX_SIZE_ERR, + HierarchyRequestError = DOMException_Binding::HIERARCHY_REQUEST_ERR, + WrongDocumentError = DOMException_Binding::WRONG_DOCUMENT_ERR, + InvalidCharacterError = DOMException_Binding::INVALID_CHARACTER_ERR, + NoModificationAllowedError = + DOMException_Binding::NO_MODIFICATION_ALLOWED_ERR, + NotFoundError = DOMException_Binding::NOT_FOUND_ERR, + NotSupportedError = DOMException_Binding::NOT_SUPPORTED_ERR, + // Can't remove until setNamedItem is removed + InUseAttributeError = DOMException_Binding::INUSE_ATTRIBUTE_ERR, + InvalidStateError = DOMException_Binding::INVALID_STATE_ERR, + SyntaxError = DOMException_Binding::SYNTAX_ERR, + InvalidModificationError = DOMException_Binding::INVALID_MODIFICATION_ERR, + NamespaceError = DOMException_Binding::NAMESPACE_ERR, + InvalidAccessError = DOMException_Binding::INVALID_ACCESS_ERR, + TypeMismatchError = DOMException_Binding::TYPE_MISMATCH_ERR, + SecurityError = DOMException_Binding::SECURITY_ERR, + NetworkError = DOMException_Binding::NETWORK_ERR, + AbortError = DOMException_Binding::ABORT_ERR, + URLMismatchError = DOMException_Binding::URL_MISMATCH_ERR, + QuotaExceededError = DOMException_Binding::QUOTA_EXCEEDED_ERR, + TimeoutError = DOMException_Binding::TIMEOUT_ERR, + InvalidNodeTypeError = DOMException_Binding::INVALID_NODE_TYPE_ERR, + DataCloneError = DOMException_Binding::DATA_CLONE_ERR, + EncodingError = 0, + + /* IndexedDB errors + http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#exceptions */ + UnknownError = 0, + ConstraintError = 0, + DataError = 0, + TransactionInactiveError = 0, + ReadOnlyError = 0, + VersionError = 0, + + /* File API errors http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException */ + NotReadableError = 0, + + /* FileHandle API errors */ + FileHandleInactiveError = 0, + + /* WebCrypto errors + https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-DataError + */ + OperationError = 0, + + /* Push API errors */ + NotAllowedError = 0, +}; + +#define DOM4_MSG_DEF(name, message, nsresult) \ + {(nsresult), name, #name, message}, +#define DOM_MSG_DEF(val, message) \ + {(val), NS_ERROR_GET_CODE(val), #val, message}, + +static constexpr struct ResultStruct { + nsresult mNSResult; + uint16_t mCode; + const char* mName; + const char* mMessage; +} sDOMErrorMsgMap[] = { +#include "domerr.msg" +}; + +#undef DOM4_MSG_DEF +#undef DOM_MSG_DEF + +static void NSResultToNameAndMessage(nsresult aNSResult, nsCString& aName, + nsCString& aMessage, uint16_t* aCode) { + aName.Truncate(); + aMessage.Truncate(); + *aCode = 0; + for (uint32_t idx = 0; idx < ArrayLength(sDOMErrorMsgMap); idx++) { + if (aNSResult == sDOMErrorMsgMap[idx].mNSResult) { + aName.Rebind(sDOMErrorMsgMap[idx].mName, + strlen(sDOMErrorMsgMap[idx].mName)); + aMessage.Rebind(sDOMErrorMsgMap[idx].mMessage, + strlen(sDOMErrorMsgMap[idx].mMessage)); + *aCode = sDOMErrorMsgMap[idx].mCode; + return; + } + } + + NS_WARNING("Huh, someone is throwing non-DOM errors using the DOM module!"); +} + +nsresult NS_GetNameAndMessageForDOMNSResult(nsresult aNSResult, + nsACString& aName, + nsACString& aMessage, + uint16_t* aCode) { + nsCString name; + nsCString message; + uint16_t code = 0; + NSResultToNameAndMessage(aNSResult, name, message, &code); + + if (!name.IsEmpty() && !message.IsEmpty()) { + aName = name; + aMessage = message; + if (aCode) { + *aCode = code; + } + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +namespace mozilla::dom { + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Exception) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(Exception) + NS_INTERFACE_MAP_ENTRY(nsIException) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Exception) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Exception) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(Exception, + (mLocation, mData), + (mThrownJSVal)) + +Exception::Exception(const nsACString& aMessage, nsresult aResult, + const nsACString& aName, nsIStackFrame* aLocation, + nsISupports* aData) + : mMessage(aMessage), + mResult(aResult), + mName(aName), + mData(aData), + mHoldingJSVal(false) { + if (aLocation) { + mLocation = aLocation; + } else { + mLocation = GetCurrentJSStack(); + // it is legal for there to be no active JS stack, if C++ code + // is operating on a JS-implemented interface pointer without + // having been called in turn by JS. This happens in the JS + // component loader. + } +} + +Exception::Exception(nsCString&& aMessage, nsresult aResult, nsCString&& aName) + : mMessage(std::move(aMessage)), + mResult(aResult), + mName(std::move(aName)), + mHoldingJSVal(false) {} + +Exception::~Exception() { + if (mHoldingJSVal) { + MOZ_ASSERT(NS_IsMainThread()); + + mozilla::DropJSObjects(this); + } +} + +bool Exception::StealJSVal(JS::Value* aVp) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mHoldingJSVal) { + *aVp = mThrownJSVal; + + mozilla::DropJSObjects(this); + mHoldingJSVal = false; + return true; + } + + return false; +} + +void Exception::StowJSVal(JS::Value& aVp) { + MOZ_ASSERT(NS_IsMainThread()); + + mThrownJSVal = aVp; + if (!mHoldingJSVal) { + mozilla::HoldJSObjects(this); + mHoldingJSVal = true; + } +} + +void Exception::GetName(nsAString& aName) { + if (!mName.IsEmpty()) { + CopyUTF8toUTF16(mName, aName); + } else { + aName.Truncate(); + + const char* name = nullptr; + nsXPCException::NameAndFormatForNSResult(mResult, &name, nullptr); + + if (name) { + CopyUTF8toUTF16(mozilla::MakeStringSpan(name), aName); + } + } +} + +void Exception::GetFilename(JSContext* aCx, nsAString& aFilename) { + if (mLocation) { + mLocation->GetFilename(aCx, aFilename); + return; + } + + aFilename.Truncate(); +} + +void Exception::ToString(JSContext* aCx, nsACString& _retval) { + static const char defaultMsg[] = "<no message>"; + static const char defaultLocation[] = "<unknown>"; + static const char format[] = "[Exception... \"%s\" nsresult: \"0x%" PRIx32 + " (%s)\" location: \"%s\" data: %s]"; + + nsCString location; + + if (mLocation) { + // we need to free this if it does not fail + mLocation->ToString(aCx, location); + } + + if (location.IsEmpty()) { + location.Assign(defaultLocation); + } + + const char* msg = mMessage.IsEmpty() ? nullptr : mMessage.get(); + + const char* resultName = mName.IsEmpty() ? nullptr : mName.get(); + if (!resultName && !nsXPCException::NameAndFormatForNSResult( + mResult, &resultName, (!msg) ? &msg : nullptr)) { + if (!msg) { + msg = defaultMsg; + } + resultName = "<unknown>"; + } + const char* data = mData ? "yes" : "no"; + + _retval.Truncate(); + _retval.AppendPrintf(format, msg, static_cast<uint32_t>(mResult), resultName, + location.get(), data); +} + +JSObject* Exception::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return Exception_Binding::Wrap(cx, this, aGivenProto); +} + +void Exception::GetMessageMoz(nsString& retval) { + CopyUTF8toUTF16(mMessage, retval); +} + +uint32_t Exception::Result() const { return (uint32_t)mResult; } + +uint32_t Exception::SourceId(JSContext* aCx) const { + if (mLocation) { + return mLocation->GetSourceId(aCx); + } + + return 0; +} + +uint32_t Exception::LineNumber(JSContext* aCx) const { + if (mLocation) { + return mLocation->GetLineNumber(aCx); + } + + return 0; +} + +uint32_t Exception::ColumnNumber() const { return 0; } + +already_AddRefed<nsIStackFrame> Exception::GetLocation() const { + nsCOMPtr<nsIStackFrame> location = mLocation; + return location.forget(); +} + +nsISupports* Exception::GetData() const { return mData; } + +void Exception::GetStack(JSContext* aCx, nsAString& aStack) const { + if (mLocation) { + mLocation->GetFormattedStack(aCx, aStack); + } +} + +void Exception::Stringify(JSContext* aCx, nsString& retval) { + nsCString str; + ToString(aCx, str); + CopyUTF8toUTF16(str, retval); +} + +DOMException::DOMException(nsresult aRv, const nsACString& aMessage, + const nsACString& aName, uint16_t aCode, + nsIStackFrame* aLocation) + : Exception(aMessage, aRv, aName, aLocation, nullptr), mCode(aCode) {} +DOMException::DOMException(nsresult aRv, nsCString&& aMessage, + nsCString&& aName, uint16_t aCode) + : Exception(std::move(aMessage), aRv, std::move(aName)), mCode(aCode) {} + +void DOMException::ToString(JSContext* aCx, nsACString& aReturn) { + aReturn.Truncate(); + + static const char defaultMsg[] = "<no message>"; + static const char defaultLocation[] = "<unknown>"; + static const char defaultName[] = "<unknown>"; + static const char format[] = + "[Exception... \"%s\" code: \"%d\" nsresult: \"0x%" PRIx32 + " (%s)\" location: \"%s\"]"; + + nsAutoCString location; + + if (location.IsEmpty()) { + location = defaultLocation; + } + + const char* msg = !mMessage.IsEmpty() ? mMessage.get() : defaultMsg; + const char* resultName = !mName.IsEmpty() ? mName.get() : defaultName; + + aReturn.AppendPrintf(format, msg, mCode, static_cast<uint32_t>(mResult), + resultName, location.get()); +} + +void DOMException::GetName(nsString& retval) { CopyUTF8toUTF16(mName, retval); } + +already_AddRefed<DOMException> DOMException::Constructor( + GlobalObject& /* unused */, const nsAString& aMessage, + const Optional<nsAString>& aName) { + nsresult exceptionResult = NS_OK; + uint16_t exceptionCode = 0; + nsCString name("Error"_ns); + + if (aName.WasPassed()) { + CopyUTF16toUTF8(aName.Value(), name); + for (uint32_t idx = 0; idx < ArrayLength(sDOMErrorMsgMap); idx++) { + if (name.EqualsASCII(sDOMErrorMsgMap[idx].mName)) { + exceptionResult = sDOMErrorMsgMap[idx].mNSResult; + exceptionCode = sDOMErrorMsgMap[idx].mCode; + break; + } + } + } + + RefPtr<DOMException> retval = new DOMException( + exceptionResult, NS_ConvertUTF16toUTF8(aMessage), name, exceptionCode); + return retval.forget(); +} + +JSObject* DOMException::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return DOMException_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed<DOMException> DOMException::Create(nsresult aRv) { + nsCString name; + nsCString message; + uint16_t code; + NSResultToNameAndMessage(aRv, name, message, &code); + RefPtr<DOMException> inst = new DOMException(aRv, message, name, code); + return inst.forget(); +} + +/* static */ +already_AddRefed<DOMException> DOMException::Create( + nsresult aRv, const nsACString& aMessage) { + nsCString name; + nsCString message; + uint16_t code; + NSResultToNameAndMessage(aRv, name, message, &code); + RefPtr<DOMException> inst = new DOMException(aRv, aMessage, name, code); + return inst.forget(); +} + +static bool ReadAsCString(JSContext* aCx, JSStructuredCloneReader* aReader, + nsCString& aString) { + JS::Rooted<JSString*> jsMessage(aCx); + if (!JS_ReadString(aReader, &jsMessage)) { + return false; + } + return AssignJSString(aCx, aString, jsMessage); +} + +already_AddRefed<DOMException> DOMException::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + uint32_t reserved; + nsresult rv; + nsCString message; + nsCString name; + uint16_t code; + + if (!JS_ReadBytes(aReader, &reserved, 4) || !JS_ReadBytes(aReader, &rv, 4) || + !ReadAsCString(aCx, aReader, message) || + !ReadAsCString(aCx, aReader, name) || !JS_ReadBytes(aReader, &code, 2)) { + return nullptr; + }; + + return do_AddRef( + new DOMException(rv, std::move(message), std::move(name), code)); +} + +bool DOMException::WriteStructuredClone( + JSContext* aCx, JSStructuredCloneWriter* aWriter) const { + JS::Rooted<JS::Value> messageValue(aCx); + JS::Rooted<JS::Value> nameValue(aCx); + if (!NonVoidByteStringToJsval(aCx, mMessage, &messageValue) || + !NonVoidByteStringToJsval(aCx, mName, &nameValue)) { + return false; + } + + JS::Rooted<JSString*> message(aCx, messageValue.toString()); + JS::Rooted<JSString*> name(aCx, nameValue.toString()); + + static_assert(sizeof(nsresult) == 4); + + // A reserved field. Use this to indicate stack serialization support etc. + uint32_t reserved = 0; + return JS_WriteBytes(aWriter, &reserved, 4) && + JS_WriteBytes(aWriter, &mResult, 4) && + JS_WriteString(aWriter, message) && JS_WriteString(aWriter, name) && + JS_WriteBytes(aWriter, &mCode, 2); +}; + +} // namespace mozilla::dom |