DOMException.cpp (13868B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/DOMException.h" 8 9 #include "js/StructuredClone.h" 10 #include "js/TypeDecls.h" 11 #include "mozilla/ErrorResult.h" 12 #include "mozilla/HoldDropJSObjects.h" 13 #include "mozilla/dom/DOMExceptionBinding.h" 14 #include "mozilla/dom/Document.h" 15 #include "mozilla/dom/Exceptions.h" 16 #include "nsCOMPtr.h" 17 #include "nsContentUtils.h" 18 #include "nsIException.h" 19 #include "xpcprivate.h" 20 21 using namespace mozilla; 22 using namespace mozilla::dom; 23 24 enum DOM4ErrorTypeCodeMap { 25 /* DOM4 errors from 26 http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#domexception */ 27 IndexSizeError = DOMException_Binding::INDEX_SIZE_ERR, 28 HierarchyRequestError = DOMException_Binding::HIERARCHY_REQUEST_ERR, 29 WrongDocumentError = DOMException_Binding::WRONG_DOCUMENT_ERR, 30 InvalidCharacterError = DOMException_Binding::INVALID_CHARACTER_ERR, 31 NoModificationAllowedError = 32 DOMException_Binding::NO_MODIFICATION_ALLOWED_ERR, 33 NotFoundError = DOMException_Binding::NOT_FOUND_ERR, 34 NotSupportedError = DOMException_Binding::NOT_SUPPORTED_ERR, 35 // Can't remove until setNamedItem is removed 36 InUseAttributeError = DOMException_Binding::INUSE_ATTRIBUTE_ERR, 37 InvalidStateError = DOMException_Binding::INVALID_STATE_ERR, 38 SyntaxError = DOMException_Binding::SYNTAX_ERR, 39 InvalidModificationError = DOMException_Binding::INVALID_MODIFICATION_ERR, 40 NamespaceError = DOMException_Binding::NAMESPACE_ERR, 41 InvalidAccessError = DOMException_Binding::INVALID_ACCESS_ERR, 42 TypeMismatchError = DOMException_Binding::TYPE_MISMATCH_ERR, 43 SecurityError = DOMException_Binding::SECURITY_ERR, 44 NetworkError = DOMException_Binding::NETWORK_ERR, 45 AbortError = DOMException_Binding::ABORT_ERR, 46 URLMismatchError = DOMException_Binding::URL_MISMATCH_ERR, 47 QuotaExceededError = DOMException_Binding::QUOTA_EXCEEDED_ERR, 48 TimeoutError = DOMException_Binding::TIMEOUT_ERR, 49 InvalidNodeTypeError = DOMException_Binding::INVALID_NODE_TYPE_ERR, 50 DataCloneError = DOMException_Binding::DATA_CLONE_ERR, 51 EncodingError = 0, 52 53 /* IndexedDB errors 54 http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#exceptions */ 55 UnknownError = 0, 56 ConstraintError = 0, 57 DataError = 0, 58 TransactionInactiveError = 0, 59 ReadOnlyError = 0, 60 VersionError = 0, 61 62 /* File API errors http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException */ 63 NotReadableError = 0, 64 65 /* FileHandle API errors */ 66 FileHandleInactiveError = 0, 67 68 /* WebCrypto errors 69 https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-DataError 70 */ 71 OperationError = 0, 72 73 /* Push API errors */ 74 NotAllowedError = 0, 75 }; 76 77 #define DOM4_MSG_DEF(name, message, nsresult) \ 78 {(nsresult), name, #name, message}, 79 #define DOM_MSG_DEF(val, message) \ 80 {(val), NS_ERROR_GET_CODE(val), #val, message}, 81 82 static constexpr struct ResultStruct { 83 nsresult mNSResult; 84 uint16_t mCode; 85 const char* mName; 86 const char* mMessage; 87 } sDOMErrorMsgMap[] = { 88 #include "domerr.msg" 89 }; 90 91 #undef DOM4_MSG_DEF 92 #undef DOM_MSG_DEF 93 94 static void NSResultToNameAndMessage(nsresult aNSResult, nsCString& aName, 95 nsCString& aMessage, uint16_t* aCode) { 96 aName.Truncate(); 97 aMessage.Truncate(); 98 *aCode = 0; 99 for (uint32_t idx = 0; idx < std::size(sDOMErrorMsgMap); idx++) { 100 if (aNSResult == sDOMErrorMsgMap[idx].mNSResult) { 101 aName.Rebind(sDOMErrorMsgMap[idx].mName, 102 strlen(sDOMErrorMsgMap[idx].mName)); 103 aMessage.Rebind(sDOMErrorMsgMap[idx].mMessage, 104 strlen(sDOMErrorMsgMap[idx].mMessage)); 105 *aCode = sDOMErrorMsgMap[idx].mCode; 106 return; 107 } 108 } 109 110 NS_WARNING("Huh, someone is throwing non-DOM errors using the DOM module!"); 111 } 112 113 nsresult NS_GetNameAndMessageForDOMNSResult(nsresult aNSResult, 114 nsACString& aName, 115 nsACString& aMessage, 116 uint16_t* aCode) { 117 nsCString name; 118 nsCString message; 119 uint16_t code = 0; 120 NSResultToNameAndMessage(aNSResult, name, message, &code); 121 122 if (!name.IsEmpty() && !message.IsEmpty()) { 123 aName = name; 124 aMessage = message; 125 if (aCode) { 126 *aCode = code; 127 } 128 return NS_OK; 129 } 130 131 return NS_ERROR_NOT_AVAILABLE; 132 } 133 134 namespace mozilla::dom { 135 136 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Exception) 137 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 138 NS_INTERFACE_MAP_ENTRY(Exception) 139 NS_INTERFACE_MAP_ENTRY(nsIException) 140 NS_INTERFACE_MAP_ENTRY(nsISupports) 141 NS_INTERFACE_MAP_END 142 143 NS_IMPL_CYCLE_COLLECTING_ADDREF(Exception) 144 NS_IMPL_CYCLE_COLLECTING_RELEASE(Exception) 145 146 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(Exception, 147 (mLocation, mData), 148 (mThrownJSVal)) 149 150 Exception::Exception(const nsACString& aMessage, nsresult aResult, 151 const nsACString& aName, nsIStackFrame* aLocation, 152 nsISupports* aData) 153 : mMessage(aMessage), 154 mResult(aResult), 155 mName(aName), 156 mData(aData), 157 mHoldingJSVal(false) { 158 if (aLocation) { 159 mLocation = aLocation; 160 } else { 161 mLocation = GetCurrentJSStack(); 162 // it is legal for there to be no active JS stack, if C++ code 163 // is operating on a JS-implemented interface pointer without 164 // having been called in turn by JS. This happens in the JS 165 // component loader. 166 } 167 } 168 169 Exception::Exception(nsCString&& aMessage, nsresult aResult, nsCString&& aName) 170 : mMessage(std::move(aMessage)), 171 mResult(aResult), 172 mName(std::move(aName)), 173 mHoldingJSVal(false) {} 174 175 Exception::~Exception() { 176 if (mHoldingJSVal) { 177 MOZ_ASSERT(NS_IsMainThread()); 178 179 mozilla::DropJSObjects(this); 180 } 181 } 182 183 bool Exception::StealJSVal(JS::Value* aVp) { 184 MOZ_ASSERT(NS_IsMainThread()); 185 186 if (mHoldingJSVal) { 187 *aVp = mThrownJSVal; 188 189 mozilla::DropJSObjects(this); 190 mHoldingJSVal = false; 191 return true; 192 } 193 194 return false; 195 } 196 197 void Exception::StowJSVal(JS::Value& aVp) { 198 MOZ_ASSERT(NS_IsMainThread()); 199 200 mThrownJSVal = aVp; 201 if (!mHoldingJSVal) { 202 mozilla::HoldJSObjects(this); 203 mHoldingJSVal = true; 204 } 205 } 206 207 void Exception::GetName(nsAString& aName) { 208 if (!mName.IsEmpty()) { 209 CopyUTF8toUTF16(mName, aName); 210 } else { 211 aName.Truncate(); 212 213 const char* name = nullptr; 214 nsXPCException::NameAndFormatForNSResult(mResult, &name, nullptr); 215 216 if (name) { 217 CopyUTF8toUTF16(mozilla::MakeStringSpan(name), aName); 218 } 219 } 220 } 221 222 void Exception::GetFilename(JSContext* aCx, nsACString& aFilename) { 223 if (mLocation) { 224 mLocation->GetFilename(aCx, aFilename); 225 return; 226 } 227 228 aFilename.Truncate(); 229 } 230 231 void Exception::ToString(JSContext* aCx, nsACString& _retval) { 232 static const char defaultMsg[] = "<no message>"; 233 static const char defaultLocation[] = "<unknown>"; 234 static const char format[] = "[Exception... \"%s\" nsresult: \"0x%" PRIx32 235 " (%s)\" location: \"%s\" data: %s]"; 236 237 nsCString location; 238 239 if (mLocation) { 240 // we need to free this if it does not fail 241 mLocation->ToString(aCx, location); 242 } 243 244 if (location.IsEmpty()) { 245 location.Assign(defaultLocation); 246 } 247 248 const char* msg = mMessage.IsEmpty() ? nullptr : mMessage.get(); 249 250 const char* resultName = mName.IsEmpty() ? nullptr : mName.get(); 251 if (!resultName && !nsXPCException::NameAndFormatForNSResult( 252 mResult, &resultName, (!msg) ? &msg : nullptr)) { 253 if (!msg) { 254 msg = defaultMsg; 255 } 256 resultName = "<unknown>"; 257 } 258 const char* data = mData ? "yes" : "no"; 259 260 _retval.Truncate(); 261 _retval.AppendPrintf(format, msg, static_cast<uint32_t>(mResult), resultName, 262 location.get(), data); 263 } 264 265 JSObject* Exception::WrapObject(JSContext* cx, 266 JS::Handle<JSObject*> aGivenProto) { 267 return Exception_Binding::Wrap(cx, this, aGivenProto); 268 } 269 270 void Exception::GetMessageMoz(nsString& retval) { 271 CopyUTF8toUTF16(mMessage, retval); 272 } 273 274 uint32_t Exception::Result() const { return (uint32_t)mResult; } 275 276 uint32_t Exception::SourceId(JSContext* aCx) const { 277 if (mLocation) { 278 return mLocation->GetSourceId(aCx); 279 } 280 281 return 0; 282 } 283 284 uint32_t Exception::LineNumber(JSContext* aCx) const { 285 if (mLocation) { 286 return mLocation->GetLineNumber(aCx); 287 } 288 289 return 0; 290 } 291 292 uint32_t Exception::ColumnNumber(JSContext* aCx) const { 293 if (mLocation) { 294 return mLocation->GetColumnNumber(aCx); 295 } 296 297 return 0; 298 } 299 300 already_AddRefed<nsIStackFrame> Exception::GetLocation() const { 301 nsCOMPtr<nsIStackFrame> location = mLocation; 302 return location.forget(); 303 } 304 305 nsISupports* Exception::GetData() const { return mData; } 306 307 void Exception::GetStack(JSContext* aCx, nsAString& aStack) const { 308 if (mLocation) { 309 mLocation->GetFormattedStack(aCx, aStack); 310 } 311 } 312 313 void Exception::Stringify(JSContext* aCx, nsString& retval) { 314 nsCString str; 315 ToString(aCx, str); 316 CopyUTF8toUTF16(str, retval); 317 } 318 319 DOMException::DOMException(nsresult aRv, const nsACString& aMessage, 320 const nsACString& aName, uint16_t aCode, 321 nsIStackFrame* aLocation) 322 : Exception(aMessage, aRv, aName, aLocation, nullptr), mCode(aCode) {} 323 DOMException::DOMException(nsresult aRv, nsCString&& aMessage, 324 nsCString&& aName, uint16_t aCode) 325 : Exception(std::move(aMessage), aRv, std::move(aName)), mCode(aCode) {} 326 327 void DOMException::ToString(JSContext* aCx, nsACString& aReturn) { 328 aReturn.Truncate(); 329 330 static const char defaultMsg[] = "<no message>"; 331 static const char defaultLocation[] = "<unknown>"; 332 static const char defaultName[] = "<unknown>"; 333 static const char format[] = 334 "[Exception... \"%s\" code: \"%d\" nsresult: \"0x%" PRIx32 335 " (%s)\" location: \"%s\"]"; 336 337 nsAutoCString location; 338 339 if (location.IsEmpty()) { 340 location = defaultLocation; 341 } 342 343 const char* msg = !mMessage.IsEmpty() ? mMessage.get() : defaultMsg; 344 const char* resultName = !mName.IsEmpty() ? mName.get() : defaultName; 345 346 aReturn.AppendPrintf(format, msg, mCode, static_cast<uint32_t>(mResult), 347 resultName, location.get()); 348 } 349 350 void DOMException::GetName(nsString& retval) { CopyUTF8toUTF16(mName, retval); } 351 352 already_AddRefed<DOMException> DOMException::Constructor( 353 GlobalObject& /* unused */, const nsAString& aMessage, 354 const Optional<nsAString>& aName) { 355 nsresult exceptionResult = NS_OK; 356 uint16_t exceptionCode = 0; 357 nsCString name("Error"_ns); 358 359 if (aName.WasPassed()) { 360 CopyUTF16toUTF8(aName.Value(), name); 361 for (uint32_t idx = 0; idx < std::size(sDOMErrorMsgMap); idx++) { 362 if (name.EqualsASCII(sDOMErrorMsgMap[idx].mName)) { 363 exceptionResult = sDOMErrorMsgMap[idx].mNSResult; 364 exceptionCode = sDOMErrorMsgMap[idx].mCode; 365 break; 366 } 367 } 368 } 369 370 RefPtr<DOMException> retval = new DOMException( 371 exceptionResult, NS_ConvertUTF16toUTF8(aMessage), name, exceptionCode); 372 return retval.forget(); 373 } 374 375 JSObject* DOMException::WrapObject(JSContext* aCx, 376 JS::Handle<JSObject*> aGivenProto) { 377 return DOMException_Binding::Wrap(aCx, this, aGivenProto); 378 } 379 380 /* static */ 381 already_AddRefed<DOMException> DOMException::Create(nsresult aRv) { 382 nsCString name; 383 nsCString message; 384 uint16_t code; 385 NSResultToNameAndMessage(aRv, name, message, &code); 386 RefPtr<DOMException> inst = new DOMException(aRv, message, name, code); 387 return inst.forget(); 388 } 389 390 /* static */ 391 already_AddRefed<DOMException> DOMException::Create( 392 nsresult aRv, const nsACString& aMessage) { 393 nsCString name; 394 nsCString message; 395 uint16_t code; 396 NSResultToNameAndMessage(aRv, name, message, &code); 397 RefPtr<DOMException> inst = new DOMException(aRv, aMessage, name, code); 398 return inst.forget(); 399 } 400 401 static bool ReadAsCString(JSContext* aCx, JSStructuredCloneReader* aReader, 402 nsCString& aString) { 403 JS::Rooted<JSString*> jsMessage(aCx); 404 if (!JS_ReadString(aReader, &jsMessage)) { 405 return false; 406 } 407 return AssignJSString(aCx, aString, jsMessage); 408 } 409 410 already_AddRefed<DOMException> DOMException::ReadStructuredClone( 411 JSContext* aCx, nsIGlobalObject* aGlobal, 412 JSStructuredCloneReader* aReader) { 413 uint32_t reserved; 414 nsresult rv; 415 nsCString message; 416 nsCString name; 417 uint16_t code; 418 419 if (!JS_ReadBytes(aReader, &reserved, 4) || !JS_ReadBytes(aReader, &rv, 4) || 420 !ReadAsCString(aCx, aReader, message) || 421 !ReadAsCString(aCx, aReader, name) || !JS_ReadBytes(aReader, &code, 2)) { 422 return nullptr; 423 }; 424 425 return do_AddRef( 426 new DOMException(rv, std::move(message), std::move(name), code)); 427 } 428 429 bool DOMException::WriteStructuredClone( 430 JSContext* aCx, JSStructuredCloneWriter* aWriter) const { 431 JS::Rooted<JS::Value> messageValue(aCx); 432 JS::Rooted<JS::Value> nameValue(aCx); 433 if (!NonVoidByteStringToJsval(aCx, mMessage, &messageValue) || 434 !NonVoidByteStringToJsval(aCx, mName, &nameValue)) { 435 return false; 436 } 437 438 JS::Rooted<JSString*> message(aCx, messageValue.toString()); 439 JS::Rooted<JSString*> name(aCx, nameValue.toString()); 440 441 static_assert(sizeof(nsresult) == 4); 442 443 // A reserved field. Use this to indicate stack serialization support etc. 444 uint32_t reserved = 0; 445 return JS_WriteBytes(aWriter, &reserved, 4) && 446 JS_WriteBytes(aWriter, &mResult, 4) && 447 JS_WriteString(aWriter, message) && JS_WriteString(aWriter, name) && 448 JS_WriteBytes(aWriter, &mCode, 2); 449 }; 450 451 } // namespace mozilla::dom