diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 4f6cbbf7325a58..886fde9b5df5a3 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -40,7 +40,7 @@ const { const { DOMException, -} = internalBinding('messaging'); +} = internalBinding('errors'); const { clearTimeout, diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 309f6bb3105341..b4955145c2f05d 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -60,6 +60,12 @@ const { URIError, } = primordials; +const { + makeTransferable, + kClone, + kDeserialize, +} = require('internal/worker/js_transferable'); + const kIsNodeError = Symbol('kIsNodeError'); const isWindows = process.platform === 'win32'; @@ -828,6 +834,29 @@ class AbortError extends Error { super(message, options); this.code = 'ABORT_ERR'; this.name = 'AbortError'; + // eslint-disable-next-line no-constructor-return + return makeTransferable(this); + } + + [kClone]() { + const name = this.name; + const message = this.message; + const stack = this.stack; + const code = this.code; + const cause = this.cause; + + return { + data: { name, message, stack, code, cause }, + deserializeInfo: 'internal/errors:AbortError', + }; + } + + [kDeserialize]({ name, message, stack, code, cause }) { + this.name = name; + this.message = message; + this.stack = stack; + this.code = code; + this.cause = cause; } } module.exports = { diff --git a/lib/internal/per_context/domexception.js b/lib/internal/per_context/domexception.js deleted file mode 100644 index 06bb3fa3053cd5..00000000000000 --- a/lib/internal/per_context/domexception.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; - -const { - Error, - ObjectDefineProperties, - ObjectDefineProperty, - SafeWeakMap, - SafeMap, - SymbolToStringTag, - TypeError, -} = primordials; - -class ERR_INVALID_THIS extends TypeError { - constructor(type) { - super('Value of "this" must be of ' + type); - } - - get code() { return 'ERR_INVALID_THIS'; } -} - -let internalsMap; -let nameToCodeMap; -let isInitialized = false; - -// We need to instantiate the maps lazily because they render -// the snapshot non-rehashable. -// https://bugs.chromium.org/p/v8/issues/detail?id=6593 -function ensureInitialized() { - if (isInitialized) { - return; - } - internalsMap = new SafeWeakMap(); - nameToCodeMap = new SafeMap(); - forEachCode((name, codeName, value) => { - nameToCodeMap.set(name, value); - }); - isInitialized = true; -} - -class DOMException extends Error { - constructor(message = '', name = 'Error') { - ensureInitialized(); - super(); - internalsMap.set(this, { - message: `${message}`, - name: `${name}` - }); - } - - get name() { - ensureInitialized(); - const internals = internalsMap.get(this); - if (internals === undefined) { - throw new ERR_INVALID_THIS('DOMException'); - } - return internals.name; - } - - get message() { - ensureInitialized(); - const internals = internalsMap.get(this); - if (internals === undefined) { - throw new ERR_INVALID_THIS('DOMException'); - } - return internals.message; - } - - get code() { - ensureInitialized(); - const internals = internalsMap.get(this); - if (internals === undefined) { - throw new ERR_INVALID_THIS('DOMException'); - } - const code = nameToCodeMap.get(internals.name); - return code === undefined ? 0 : code; - } -} - -ObjectDefineProperties(DOMException.prototype, { - [SymbolToStringTag]: { configurable: true, value: 'DOMException' }, - name: { enumerable: true, configurable: true }, - message: { enumerable: true, configurable: true }, - code: { enumerable: true, configurable: true } -}); - -function forEachCode(fn) { - fn('IndexSizeError', 'INDEX_SIZE_ERR', 1); - fn('DOMStringSizeError', 'DOMSTRING_SIZE_ERR', 2); - fn('HierarchyRequestError', 'HIERARCHY_REQUEST_ERR', 3); - fn('WrongDocumentError', 'WRONG_DOCUMENT_ERR', 4); - fn('InvalidCharacterError', 'INVALID_CHARACTER_ERR', 5); - fn('NoDataAllowedError', 'NO_DATA_ALLOWED_ERR', 6); - fn('NoModificationAllowedError', 'NO_MODIFICATION_ALLOWED_ERR', 7); - fn('NotFoundError', 'NOT_FOUND_ERR', 8); - fn('NotSupportedError', 'NOT_SUPPORTED_ERR', 9); - fn('InUseAttributeError', 'INUSE_ATTRIBUTE_ERR', 10); - fn('InvalidStateError', 'INVALID_STATE_ERR', 11); - fn('SyntaxError', 'SYNTAX_ERR', 12); - fn('InvalidModificationError', 'INVALID_MODIFICATION_ERR', 13); - fn('NamespaceError', 'NAMESPACE_ERR', 14); - fn('InvalidAccessError', 'INVALID_ACCESS_ERR', 15); - fn('ValidationError', 'VALIDATION_ERR', 16); - fn('TypeMismatchError', 'TYPE_MISMATCH_ERR', 17); - fn('SecurityError', 'SECURITY_ERR', 18); - fn('NetworkError', 'NETWORK_ERR', 19); - fn('AbortError', 'ABORT_ERR', 20); - fn('URLMismatchError', 'URL_MISMATCH_ERR', 21); - fn('QuotaExceededError', 'QUOTA_EXCEEDED_ERR', 22); - fn('TimeoutError', 'TIMEOUT_ERR', 23); - fn('InvalidNodeTypeError', 'INVALID_NODE_TYPE_ERR', 24); - fn('DataCloneError', 'DATA_CLONE_ERR', 25); - // There are some more error names, but since they don't have codes assigned, - // we don't need to care about them. -} - -forEachCode((name, codeName, value) => { - const desc = { enumerable: true, value }; - ObjectDefineProperty(DOMException, codeName, desc); - ObjectDefineProperty(DOMException.prototype, codeName, desc); -}); - -exports.DOMException = DOMException; diff --git a/lib/internal/util.js b/lib/internal/util.js index e0f26f35717219..cbc47ba5589d46 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -468,12 +468,12 @@ function createDeferredPromise() { let _DOMException; const lazyDOMExceptionClass = () => { - _DOMException ??= internalBinding('messaging').DOMException; + _DOMException ??= internalBinding('errors').DOMException; return _DOMException; }; const lazyDOMException = hideStackFrames((message, name) => { - _DOMException ??= internalBinding('messaging').DOMException; + _DOMException ??= internalBinding('errors').DOMException; return new _DOMException(message, name); }); diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index c9b988d6fee0f7..6ec02cef680476 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -40,7 +40,7 @@ const { const { DOMException, -} = internalBinding('messaging'); +} = internalBinding('errors'); const { isArrayBufferView, diff --git a/lib/internal/webstreams/transfer.js b/lib/internal/webstreams/transfer.js index 985d7e86738f35..d65d44841501ce 100644 --- a/lib/internal/webstreams/transfer.js +++ b/lib/internal/webstreams/transfer.js @@ -1,9 +1,7 @@ 'use strict'; const { - ObjectDefineProperties, PromiseResolve, - ReflectConstruct, } = primordials; const { @@ -13,7 +11,7 @@ const { const { DOMException, -} = internalBinding('messaging'); +} = internalBinding('errors'); const { ReadableStream, @@ -34,72 +32,6 @@ const { const assert = require('internal/assert'); -const { - makeTransferable, - kClone, - kDeserialize, -} = require('internal/worker/js_transferable'); - -// This class is a bit of a hack. The Node.js implementation of -// DOMException is not transferable/cloneable. This provides us -// with a variant that is. Unfortunately, it means playing around -// a bit with the message, name, and code properties and the -// prototype. We can revisit this if DOMException is ever made -// properly cloneable. -class CloneableDOMException extends DOMException { - constructor(message, name) { - super(message, name); - this[kDeserialize]({ - message: this.message, - name: this.name, - code: this.code, - }); - // eslint-disable-next-line no-constructor-return - return makeTransferable(this); - } - - [kClone]() { - return { - data: { - message: this.message, - name: this.name, - code: this.code, - }, - deserializeInfo: - 'internal/webstreams/transfer:InternalCloneableDOMException' - }; - } - - [kDeserialize]({ message, name, code }) { - ObjectDefineProperties(this, { - message: { - configurable: true, - enumerable: true, - get() { return message; }, - }, - name: { - configurable: true, - enumerable: true, - get() { return name; }, - }, - code: { - configurable: true, - enumerable: true, - get() { return code; }, - }, - }); - } -} - -function InternalCloneableDOMException() { - return makeTransferable( - ReflectConstruct( - CloneableDOMException, - [], - DOMException)); -} -InternalCloneableDOMException[kDeserialize] = () => {}; - class CrossRealmTransformReadableSource { constructor(port) { this[kState] = { @@ -133,7 +65,7 @@ class CrossRealmTransformReadableSource { }; port.onmessageerror = () => { - const error = new CloneableDOMException( + const error = new DOMException( 'Internal transferred ReadableStream error', 'DataCloneError'); port.postMessage({ type: 'error', value: error }); @@ -156,10 +88,6 @@ class CrossRealmTransformReadableSource { try { this[kState].port.postMessage({ type: 'error', value: reason }); } catch (error) { - if (error instanceof DOMException) { - // eslint-disable-next-line no-ex-assign - error = new CloneableDOMException(error.message, error.name); - } this[kState].port.postMessage({ type: 'error', value: error }); throw error; } finally { @@ -200,7 +128,7 @@ class CrossRealmTransformWritableSink { } }; port.onmessageerror = () => { - const error = new CloneableDOMException( + const error = new DOMException( 'Internal transferred ReadableStream error', 'DataCloneError'); port.postMessage({ type: 'error', value: error }); @@ -229,10 +157,6 @@ class CrossRealmTransformWritableSink { try { this[kState].port.postMessage({ type: 'chunk', value: chunk }); } catch (error) { - if (error instanceof DOMException) { - // eslint-disable-next-line no-ex-assign - error = new CloneableDOMException(error.message, error.name); - } this[kState].port.postMessage({ type: 'error', value: error }); this[kState].port.close(); throw error; @@ -248,10 +172,6 @@ class CrossRealmTransformWritableSink { try { this[kState].port.postMessage({ type: 'error', value: reason }); } catch (error) { - if (error instanceof DOMException) { - // eslint-disable-next-line no-ex-assign - error = new CloneableDOMException(error.message, error.name); - } this[kState].port.postMessage({ type: 'error', value: error }); throw error; } finally { @@ -294,6 +214,4 @@ module.exports = { newCrossRealmWritableSink, CrossRealmTransformWritableSink, CrossRealmTransformReadableSource, - CloneableDOMException, - InternalCloneableDOMException, }; diff --git a/lib/internal/webstreams/transformstream.js b/lib/internal/webstreams/transformstream.js index b4e690daa98c4a..cacb989d4edf25 100644 --- a/lib/internal/webstreams/transformstream.js +++ b/lib/internal/webstreams/transformstream.js @@ -22,7 +22,7 @@ const { const { DOMException, -} = internalBinding('messaging'); +} = internalBinding('errors'); const { createDeferredPromise, diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index dba7560c549a9a..7d6f4662fdca35 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -28,7 +28,7 @@ const { const { DOMException, -} = internalBinding('messaging'); +} = internalBinding('errors'); const { createDeferredPromise, diff --git a/lib/internal/worker/io.js b/lib/internal/worker/io.js index 5d03f43ef6df62..c037c287c6e557 100644 --- a/lib/internal/worker/io.js +++ b/lib/internal/worker/io.js @@ -32,8 +32,12 @@ const { receiveMessageOnPort: receiveMessageOnPort_, stopMessagePort, checkMessagePort, - DOMException, } = internalBinding('messaging'); + +const { + DOMException, +} = internalBinding('errors'); + const { getEnvMessagePort } = internalBinding('worker'); diff --git a/src/api/environment.cc b/src/api/environment.cc index 1ec1e8bc8c1896..9acfac8f3cdd6e 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -669,7 +669,6 @@ Maybe InitializePrimordials(Local context) { } static const char* context_files[] = {"internal/per_context/primordials", - "internal/per_context/domexception", "internal/per_context/messageport", nullptr}; diff --git a/src/env.h b/src/env.h index 5cf789f9bb7ee9..65995dd14b77fb 100644 --- a/src/env.h +++ b/src/env.h @@ -231,6 +231,7 @@ constexpr size_t kFsStatsBufferLength = V(dns_soa_string, "SOA") \ V(dns_srv_string, "SRV") \ V(dns_txt_string, "TXT") \ + V(domexception_string, "DOMException") \ V(done_string, "done") \ V(duration_string, "duration") \ V(ecdh_string, "ECDH") \ @@ -462,6 +463,7 @@ constexpr size_t kFsStatsBufferLength = V(blocklist_constructor_template, v8::FunctionTemplate) \ V(compiled_fn_entry_template, v8::ObjectTemplate) \ V(dir_instance_template, v8::ObjectTemplate) \ + V(domexception_constructor_template, v8::FunctionTemplate) \ V(fd_constructor_template, v8::ObjectTemplate) \ V(fdclose_constructor_template, v8::ObjectTemplate) \ V(filehandlereadwrap_template, v8::ObjectTemplate) \ diff --git a/src/node_errors.cc b/src/node_errors.cc index be4baa80fd1b80..7125125bcd2f88 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -1,8 +1,10 @@ #include #include +#include "base_object-inl.h" #include "debug_utils-inl.h" #include "node_errors.h" +#include "memory_tracker-inl.h" #include "node_external_reference.h" #include "node_internals.h" #include "node_process-inl.h" @@ -18,6 +20,8 @@ using v8::Context; using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Global; using v8::HandleScope; using v8::Int32; using v8::Isolate; @@ -873,6 +877,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(SetEnhanceStackForFatalException); registry->Register(NoSideEffectsToString); registry->Register(TriggerUncaughtException); + DOMException::RegisterExternalReferences(registry); } void Initialize(Local target, @@ -889,6 +894,7 @@ void Initialize(Local target, env->SetMethodNoSideEffect( target, "noSideEffectsToString", NoSideEffectsToString); env->SetMethod(target, "triggerUncaughtException", TriggerUncaughtException); + DOMException::Initialize(env, target); } void DecorateErrorStack(Environment* env, @@ -1044,6 +1050,304 @@ void TriggerUncaughtException(Isolate* isolate, const v8::TryCatch& try_catch) { } // namespace errors +namespace { +// This is annoying, but v8 does not give a nice way of getting a good +// stack property so we have to create an error object and capture a +// stack that way. +Global MakeErrorStack( + Environment* env, + const Local& message, + const Local& name) { + Local message_str; + Local name_str; + if (!message->ToDetailString(env->context()).ToLocal(&message_str) || + !name->ToDetailString(env->context()).ToLocal(&name_str)) { + return Global(); + } + Local err = Exception::Error(message_str).As(); + + if (!err->Set(env->context(), env->name_string(), name_str).FromJust()) { + return Global(); + } + + Local stack; + if (!err->Get(env->context(), env->stack_string()).ToLocal(&stack)) { + return Global(); + } + + return Global(env->isolate(), stack); +} +} // namespace + +bool DOMException::HasInstance(Environment* env, v8::Local value) { + return GetConstructorTemplate(env)->HasInstance(value); +} + +Local DOMException::GetConstructorTemplate(Environment* env) { + Local tmpl = env->domexception_constructor_template(); + if (tmpl.IsEmpty()) { + Local proto = v8::FunctionTemplate::New(env->isolate()); + proto->RemovePrototype(); + proto->SetIntrinsicDataProperty( + FIXED_ONE_BYTE_STRING(env->isolate(), "prototype"), + v8::kErrorPrototype); + proto->Inherit(BaseObject::GetConstructorTemplate(env)); + + tmpl = env->NewFunctionTemplate(New); + tmpl->Inherit(proto); + tmpl->InstanceTemplate()->SetInternalFieldCount( + BaseObject::kInternalFieldCount); + tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "DOMException")); + + tmpl->PrototypeTemplate()->SetAccessorProperty( + env->name_string(), + FunctionTemplate::New(env->isolate(), GetName), + Local(), + v8::PropertyAttribute::ReadOnly); + tmpl->PrototypeTemplate()->SetAccessorProperty( + env->message_string(), + FunctionTemplate::New(env->isolate(), GetMessage), + Local(), + v8::PropertyAttribute::ReadOnly); + tmpl->PrototypeTemplate()->SetAccessorProperty( + env->stack_string(), + FunctionTemplate::New(env->isolate(), GetStack), + Local(), + v8::PropertyAttribute::ReadOnly); + tmpl->PrototypeTemplate()->SetAccessorProperty( + env->code_string(), + FunctionTemplate::New(env->isolate(), GetCode), + Local(), + v8::PropertyAttribute::ReadOnly); + + env->set_domexception_constructor_template(tmpl); + } + return tmpl; +} + +void DOMException::Initialize(Environment* env, v8::Local target) { + env->SetConstructorFunction( + target, + "DOMException", + GetConstructorTemplate(env), + Environment::SetConstructorFunctionFlag::NONE); +} + +void DOMException::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(New); + registry->Register(GetMessage); + registry->Register(GetName); + registry->Register(GetStack); + registry->Register(GetCode); +} + +BaseObjectPtr DOMException::Create( + Environment* env, + const TransferData& transferData) { + HandleScope scope(env->isolate()); + + Local ctor; + if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor)) + return BaseObjectPtr(); + + Local obj; + if (!ctor->NewInstance(env->context()).ToLocal(&obj)) + return BaseObjectPtr(); + + return MakeBaseObject(env, obj, transferData); +} + +BaseObjectPtr DOMException::Create( + Environment* env, + const std::string& message, + const std::string& name) { + HandleScope scope(env->isolate()); + + Local ctor; + if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor)) + return BaseObjectPtr(); + + Local obj; + if (!ctor->NewInstance(env->context()).ToLocal(&obj)) + return BaseObjectPtr(); + + return MakeBaseObject(env, obj, message, name); +} + +BaseObjectPtr DOMException::Create( + Environment* env, + Local message, + const std::string& name) { + HandleScope scope(env->isolate()); + + Local ctor; + if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor)) + return BaseObjectPtr(); + + Local obj; + if (!ctor->NewInstance(env->context()).ToLocal(&obj)) + return BaseObjectPtr(); + + return MakeBaseObject( + env, + obj, + message, + String::NewFromUtf8(env->isolate(), name.c_str()).ToLocalChecked()); +} + +void DOMException::New(const FunctionCallbackInfo& args) { + CHECK(args.IsConstructCall()); + + new DOMException( + Environment::GetCurrent(args), + args.This(), + args[0], + args[1]); +} + +DOMException::DOMException( + Environment* env, + Local object, + Local message, + Local name) + : BaseObject(env, object), + message(env->isolate(), message), + name(env->isolate(), name), + stack(env->isolate(), MakeErrorStack(env, message, name)) {} + +DOMException::DOMException( + Environment* env, + Local object, + const std::string& message, + const std::string& name) + : BaseObject(env, object) { + Local message_value = + String::NewFromUtf8(env->isolate(), message.c_str()).ToLocalChecked(); + Local name_value = + String::NewFromUtf8(env->isolate(), name.c_str()).ToLocalChecked(); + this->message.Reset(env->isolate(), message_value); + this->name.Reset(env->isolate(), name_value); + this->stack = MakeErrorStack(env, message_value, name_value); +} + +DOMException::DOMException( + Environment* env, + Local object, + const TransferData& transferData) + : BaseObject(env, object), + message( + env->isolate(), + String::NewFromUtf8( + env->isolate(), + transferData.get_message().c_str()).ToLocalChecked()), + name( + env->isolate(), + String::NewFromUtf8( + env->isolate(), + transferData.get_name().c_str()).ToLocalChecked()), + stack( + env->isolate(), + String::NewFromUtf8( + env->isolate(), + transferData.get_stack().c_str()).ToLocalChecked()) {} + +void DOMException::GetName(const FunctionCallbackInfo& args) { + DOMException* ex; + ASSIGN_OR_RETURN_UNWRAP(&ex, args.Holder()); + args.GetReturnValue().Set(ex->name); +} + +void DOMException::GetMessage(const FunctionCallbackInfo& args) { + DOMException* ex; + ASSIGN_OR_RETURN_UNWRAP(&ex, args.Holder()); + args.GetReturnValue().Set(ex->message); +} + +void DOMException::GetStack(const FunctionCallbackInfo& args) { + DOMException* ex; + ASSIGN_OR_RETURN_UNWRAP(&ex, args.Holder()); + args.GetReturnValue().Set(ex->stack); +} + +#define DOMEXCEPTION_CODES(V) \ + V(IndexSize, 1) \ + V(DOMStringSize, 2) \ + V(HierarchyRequest, 3) \ + V(WrongDocument, 4) \ + V(InvalidCharacter, 5) \ + V(NoDataAllowed, 6) \ + V(NoModificationAllowed, 7) \ + V(NotFound, 8) \ + V(NotSupported, 9) \ + V(InUseAttribute, 10) \ + V(InvalidState, 11) \ + V(Syntax, 12) \ + V(InvalidModification, 13) \ + V(Namespace, 14) \ + V(InvalidAccess, 15) \ + V(Validation, 16) \ + V(TypeMismatch, 17) \ + V(Security, 18) \ + V(Network, 19) \ + V(Abort, 20) \ + V(URLMismatch, 21) \ + V(QuotaExceeded, 22) \ + V(Timeout, 23) \ + V(InvalidNodeType, 24) \ + V(DataClone, 25) + +void DOMException::GetCode(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + DOMException* ex; + ASSIGN_OR_RETURN_UNWRAP(&ex, args.Holder()); + + Local name = ex->name.Get(env->isolate()); + Utf8Value val(env->isolate(), name); + int ret = 0; + +#define V(name, code) if (strcmp(#name "Error", *val) == 0) ret = code; + DOMEXCEPTION_CODES(V) + args.GetReturnValue().Set(ret); +#undef V +} +#undef DOMEXCEPTION_CODES + +void DOMException::MemoryInfo(node::MemoryTracker* tracker) const { + tracker->TrackField("message", message); + tracker->TrackField("name", name); + tracker->TrackField("stack", stack); +} + +std::unique_ptr DOMException::CloneForMessaging() const { + return std::make_unique(env(), *this); +} + +DOMException::TransferData::TransferData( + Environment* env, + const DOMException& exception) { + Utf8Value message(env->isolate(), exception.message.Get(env->isolate())); + Utf8Value name(env->isolate(), exception.name.Get(env->isolate())); + Utf8Value stack(env->isolate(), exception.stack.Get(env->isolate())); + this->message = *message; + this->name = *name; + this->stack = *stack; +} + +BaseObjectPtr DOMException::TransferData::Deserialize( + Environment* env, + v8::Local context, + std::unique_ptr self) { + return DOMException::Create(env, *this); +} + +void DOMException::TransferData::MemoryInfo( + node::MemoryTracker* tracker) const { + tracker->TrackField("message", message); + tracker->TrackField("name", name); + tracker->TrackField("stack", stack); +} + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize) diff --git a/src/node_errors.h b/src/node_errors.h index f540b3e2a37de4..608ae88d5d6763 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -1,10 +1,16 @@ #ifndef SRC_NODE_ERRORS_H_ #define SRC_NODE_ERRORS_H_ +#include "v8-debug.h" +#include "v8-function-callback.h" #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include "base_object.h" #include "debug_utils-inl.h" #include "env.h" +#include "memory_tracker.h" +#include "node_external_reference.h" +#include "node_worker.h" #include "v8.h" // Use ostringstream to print exact-width integer types @@ -270,6 +276,97 @@ void DecorateErrorStack(Environment* env, const errors::TryCatchScope& try_catch); } // namespace errors +class DOMException : public BaseObject { + public: + class TransferData; + + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); + + static v8::Local GetConstructorTemplate( + Environment* env); + + static bool HasInstance(Environment* env, v8::Local value); + + static void Initialize(Environment* env, v8::Local object); + + static BaseObjectPtr Create( + Environment* env, + const TransferData& transferData); + + static BaseObjectPtr Create( + Environment* env, + const std::string& message, + const std::string& name = "DOMException"); + + static BaseObjectPtr Create( + Environment* env, + v8::Local message, + const std::string& name = "DOMException"); + + static void New(const v8::FunctionCallbackInfo& args); + static void GetMessage(const v8::FunctionCallbackInfo& args); + static void GetName(const v8::FunctionCallbackInfo& args); + static void GetStack(const v8::FunctionCallbackInfo& args); + static void GetCode(const v8::FunctionCallbackInfo& args); + + DOMException( + Environment* env, + v8::Local object, + v8::Local message, + v8::Local name); + + DOMException( + Environment* env, + v8::Local object, + const std::string& message, + const std::string& name = "DOMException"); + + DOMException( + Environment* env, + v8::Local object, + const TransferData& transferData); + + void MemoryInfo(node::MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(DOMException); + SET_SELF_SIZE(DOMException); + + inline TransferMode GetTransferMode() const override { + return TransferMode::kCloneable; + } + + std::unique_ptr CloneForMessaging() const override; + + class TransferData : public worker::TransferData { + public: + explicit TransferData(Environment* env, const DOMException& ex); + + BaseObjectPtr Deserialize( + Environment* env, + v8::Local context, + std::unique_ptr self) override; + + SET_MEMORY_INFO_NAME(DOMException::TransferData); + SET_SELF_SIZE(TransferData); + void MemoryInfo(node::MemoryTracker* tracker) const override; + + const std::string& get_message() const { return message; } + const std::string& get_name() const { return name; } + const std::string& get_stack() const { return stack; } + + private: + std::string message; + std::string name; + std::string stack; + }; + + private: + v8::Global message; + v8::Global name; + v8::Global stack; + + friend class TransferData; +}; + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node_messaging.cc b/src/node_messaging.cc index a1f28d4746d07f..05728e1f6cba56 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -246,33 +246,11 @@ MaybeLocal GetEmitMessageFunction(Local context) { return emit_message_val.As(); } -MaybeLocal GetDOMException(Local context) { - Isolate* isolate = context->GetIsolate(); - Local per_context_bindings; - Local domexception_ctor_val; - if (!GetPerContextExports(context).ToLocal(&per_context_bindings) || - !per_context_bindings->Get(context, - FIXED_ONE_BYTE_STRING(isolate, "DOMException")) - .ToLocal(&domexception_ctor_val)) { - return MaybeLocal(); - } - CHECK(domexception_ctor_val->IsFunction()); - Local domexception_ctor = domexception_ctor_val.As(); - return domexception_ctor; -} - void ThrowDataCloneException(Local context, Local message) { - Isolate* isolate = context->GetIsolate(); - Local argv[] = {message, - FIXED_ONE_BYTE_STRING(isolate, "DataCloneError")}; - Local exception; - Local domexception_ctor; - if (!GetDOMException(context).ToLocal(&domexception_ctor) || - !domexception_ctor->NewInstance(context, arraysize(argv), argv) - .ToLocal(&exception)) { - return; - } - isolate->ThrowException(exception); + Environment* env = Environment::GetCurrent(context); + BaseObjectPtr exception = + DOMException::Create(env, message, "DataCloneError"); + env->isolate()->ThrowException(exception->object()); } // This tells V8 how to serialize objects that it does not understand @@ -1483,15 +1461,6 @@ static void InitMessaging(Local target, env->SetMethod(target, "setDeserializerCreateObjectFunction", SetDeserializerCreateObjectFunction); env->SetMethod(target, "broadcastChannel", BroadcastChannel); - - { - Local domexception = GetDOMException(context).ToLocalChecked(); - target - ->Set(context, - FIXED_ONE_BYTE_STRING(env->isolate(), "DOMException"), - domexception) - .Check(); - } } static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { diff --git a/test/parallel/test-errors-cloneabledomexception.js b/test/parallel/test-errors-cloneabledomexception.js new file mode 100644 index 00000000000000..5eedcfa8960ed5 --- /dev/null +++ b/test/parallel/test-errors-cloneabledomexception.js @@ -0,0 +1,41 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); + +const { + strictEqual, + notStrictEqual, + ok, +} = require('assert'); +const { AbortError } = require('internal/errors'); + +const exception = new DOMException('foo', 'AbortError'); +strictEqual(exception.name, 'AbortError'); +strictEqual(exception.message, 'foo'); +strictEqual(exception.code, 20); + +const mc = new MessageChannel(); +mc.port1.onmessage = common.mustCall(({ data }) => { + ok(data instanceof DOMException); + strictEqual(data.name, 'AbortError'); + strictEqual(data.message, 'foo'); + strictEqual(data.code, 20); + strictEqual(data.stack, exception.stack); + mc.port1.close(); +}); +mc.port2.postMessage(exception); + +// Let's make sure AbortError is cloneable also +const abort = new AbortError(); +const mc2 = new MessageChannel(); +mc2.port1.onmessage = common.mustCall(({ data }) => { + ok(data instanceof AbortError); + ok(data instanceof Error); + notStrictEqual(data, abort); + strictEqual(data.name, abort.name); + strictEqual(data.message, abort.message); + strictEqual(data.code, abort.code); + mc2.port1.close(); +}); +mc2.port2.postMessage(abort); diff --git a/test/parallel/test-worker-message-port-move.js b/test/parallel/test-worker-message-port-move.js index 44efd2e6a6b94f..75709fd263fbcf 100644 --- a/test/parallel/test-worker-message-port-move.js +++ b/test/parallel/test-worker-message-port-move.js @@ -15,7 +15,8 @@ Object.assign(context, { global: context, assert, MessagePort, - MessageChannel + MessageChannel, + Error, }); vm.runInContext('(' + function() { @@ -51,7 +52,7 @@ vm.runInContext('(' + function() { port.postMessage(global); } catch (e) { assert.strictEqual(e.constructor.name, 'DOMException'); - assert(e instanceof Object); + assert.strictEqual(typeof e, 'object'); assert(e instanceof Error); threw = true; } diff --git a/test/wpt/README.md b/test/wpt/README.md index b8ded27ef37d78..113707b66de3cc 100644 --- a/test/wpt/README.md +++ b/test/wpt/README.md @@ -57,7 +57,7 @@ runner.setFlags(['--expose-internals']); // Set a script that will be executed in the worker before running the tests. runner.setInitScript(` const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); + const { DOMException } = internalBinding('errors'); global.DOMException = DOMException; `); diff --git a/test/wpt/test-performance-timeline.js b/test/wpt/test-performance-timeline.js index 12f1abd3a52a32..a9ad26a6fcd033 100644 --- a/test/wpt/test-performance-timeline.js +++ b/test/wpt/test-performance-timeline.js @@ -22,7 +22,7 @@ runner.setInitScript(` global.performance = performance; const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); + const { DOMException } = internalBinding('errors'); global.DOMException = DOMException; `); diff --git a/test/wpt/test-streams.js b/test/wpt/test-streams.js index 987676d8c49125..216926f88916c9 100644 --- a/test/wpt/test-streams.js +++ b/test/wpt/test-streams.js @@ -27,7 +27,7 @@ runner.setInitScript(` } = require('stream/web'); const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); + const { DOMException } = internalBinding('errors'); global.DOMException = DOMException; Object.defineProperties(global, { diff --git a/test/wpt/test-user-timing.js b/test/wpt/test-user-timing.js index 36d13297ba57cc..e9f5f2d4605d89 100644 --- a/test/wpt/test-user-timing.js +++ b/test/wpt/test-user-timing.js @@ -20,7 +20,7 @@ runner.setInitScript(` global.performance = performance; const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); + const { DOMException } = internalBinding('errors'); global.DOMException = DOMException; `); diff --git a/test/wpt/test-webcrypto.js b/test/wpt/test-webcrypto.js index e8707a464f434c..42eb88e0faccce 100644 --- a/test/wpt/test-webcrypto.js +++ b/test/wpt/test-webcrypto.js @@ -19,7 +19,7 @@ runner.setInitScript(` crypto, } = require('internal/crypto/webcrypto'); const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); + const { DOMException } = internalBinding('errors'); global.DOMException = DOMException; Object.defineProperties(global, {