/* -*- 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[] = ""; static const char defaultLocation[] = ""; 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 = ""; } const char* data = mData ? "yes" : "no"; _retval.Truncate(); _retval.AppendPrintf(format, msg, static_cast(mResult), resultName, location.get(), data); } JSObject* Exception::WrapObject(JSContext* cx, JS::Handle 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 Exception::GetLocation() const { nsCOMPtr 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[] = ""; static const char defaultLocation[] = ""; static const char defaultName[] = ""; 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(mResult), resultName, location.get()); } void DOMException::GetName(nsString& retval) { CopyUTF8toUTF16(mName, retval); } already_AddRefed DOMException::Constructor( GlobalObject& /* unused */, const nsAString& aMessage, const Optional& 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 retval = new DOMException( exceptionResult, NS_ConvertUTF16toUTF8(aMessage), name, exceptionCode); return retval.forget(); } JSObject* DOMException::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return DOMException_Binding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed DOMException::Create(nsresult aRv) { nsCString name; nsCString message; uint16_t code; NSResultToNameAndMessage(aRv, name, message, &code); RefPtr inst = new DOMException(aRv, message, name, code); return inst.forget(); } /* static */ already_AddRefed DOMException::Create( nsresult aRv, const nsACString& aMessage) { nsCString name; nsCString message; uint16_t code; NSResultToNameAndMessage(aRv, name, message, &code); RefPtr inst = new DOMException(aRv, aMessage, name, code); return inst.forget(); } static bool ReadAsCString(JSContext* aCx, JSStructuredCloneReader* aReader, nsCString& aString) { JS::Rooted jsMessage(aCx); if (!JS_ReadString(aReader, &jsMessage)) { return false; } return AssignJSString(aCx, aString, jsMessage); } already_AddRefed 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 messageValue(aCx); JS::Rooted nameValue(aCx); if (!NonVoidByteStringToJsval(aCx, mMessage, &messageValue) || !NonVoidByteStringToJsval(aCx, mName, &nameValue)) { return false; } JS::Rooted message(aCx, messageValue.toString()); JS::Rooted 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