From 2c39961d4e9ebd2d066d012ddc8129f751384bac Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Fri, 29 Aug 2025 18:14:49 +0100 Subject: [PATCH] lib: fix DOMException subclass support --- lib/internal/per_context/domexception.js | 6 +-- test/parallel/test-domexception-subclass.js | 55 +++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-domexception-subclass.js diff --git a/lib/internal/per_context/domexception.js b/lib/internal/per_context/domexception.js index 1bc46616556612..d8a2e7df88a697 100644 --- a/lib/internal/per_context/domexception.js +++ b/lib/internal/per_context/domexception.js @@ -60,7 +60,6 @@ const disusedNamesSet = new SafeSet() .add('NoDataAllowedError') .add('ValidationError'); -let DOMExceptionPrototype; // The DOMException WebIDL interface defines that: // - ObjectGetPrototypeOf(DOMException) === Function. // - ObjectGetPrototypeOf(DOMException.prototype) === Error.prototype. @@ -75,7 +74,8 @@ class DOMException { // internal slot. // eslint-disable-next-line no-restricted-syntax const self = new Error(); - ObjectSetPrototypeOf(self, DOMExceptionPrototype); + // Use `new.target.prototype` to support DOMException subclasses. + ObjectSetPrototypeOf(self, new.target.prototype); self[transfer_mode_private_symbol] = kCloneable; if (options && typeof options === 'object') { @@ -158,7 +158,7 @@ class DOMException { } } -DOMExceptionPrototype = DOMException.prototype; +const DOMExceptionPrototype = DOMException.prototype; ObjectSetPrototypeOf(DOMExceptionPrototype, ErrorPrototype); ObjectDefineProperties(DOMExceptionPrototype, { [SymbolToStringTag]: { __proto__: null, configurable: true, value: 'DOMException' }, diff --git a/test/parallel/test-domexception-subclass.js b/test/parallel/test-domexception-subclass.js new file mode 100644 index 00000000000000..a9498d95a13d5c --- /dev/null +++ b/test/parallel/test-domexception-subclass.js @@ -0,0 +1,55 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +class MyDOMException extends DOMException { + ownProp; + #reason; + + constructor() { + super('my message', 'NotFoundError'); + this.ownProp = 'bar'; + this.#reason = 'hello'; + } + + get reason() { + return this.#reason; + } +} + +const myException = new MyDOMException(); +// Verifies the prototype chain +assert(myException instanceof MyDOMException); +assert(myException instanceof DOMException); +assert(myException instanceof Error); +// Verifies [[ErrorData]] +assert(Error.isError(myException)); + +// Verifies subclass properties +assert(Object.hasOwn(myException, 'ownProp')); +assert(!Object.hasOwn(myException, 'reason')); +assert.strictEqual(myException.reason, 'hello'); + +// Verifies error properties +assert.strictEqual(myException.name, 'NotFoundError'); +assert.strictEqual(myException.code, 8); +assert.strictEqual(myException.message, 'my message'); +assert.strictEqual(typeof myException.stack, 'string'); + +// Verify structuredClone only copies known error properties. +const cloned = structuredClone(myException); +assert(!(cloned instanceof MyDOMException)); +assert(cloned instanceof DOMException); +assert(cloned instanceof Error); +assert(Error.isError(cloned)); + +// Verify custom properties +assert(!Object.hasOwn(cloned, 'ownProp')); +assert.strictEqual(cloned.reason, undefined); + +// Verify cloned error properties +assert.strictEqual(cloned.name, 'NotFoundError'); +assert.strictEqual(cloned.code, 8); +assert.strictEqual(cloned.message, 'my message'); +assert.strictEqual(cloned.stack, myException.stack);