Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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
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
UnknownError = 0,
ConstraintError = 0,
DataError = 0,
TransactionInactiveError = 0,
ReadOnlyError = 0,
VersionError = 0,
NotReadableError = 0,
/* FileHandle API errors */
FileHandleInactiveError = 0,
/* WebCrypto errors
*/
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 < std::size(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, nsACString& 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 < std::size(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