diff --git a/lib/web/cache/cache.js b/lib/web/cache/cache.js index 45c6fec3467..1c1a5911242 100644 --- a/lib/web/cache/cache.js +++ b/lib/web/cache/cache.js @@ -37,6 +37,7 @@ class Cache { webidl.illegalConstructor() } + webidl.util.markAsUncloneable(this) this.#relevantRequestResponseList = arguments[1] } diff --git a/lib/web/cache/cachestorage.js b/lib/web/cache/cachestorage.js index cc773b94b49..55dba352e99 100644 --- a/lib/web/cache/cachestorage.js +++ b/lib/web/cache/cachestorage.js @@ -16,6 +16,8 @@ class CacheStorage { if (arguments[0] !== kConstruct) { webidl.illegalConstructor() } + + webidl.util.markAsUncloneable(this) } async match (request, options = {}) { diff --git a/lib/web/eventsource/eventsource.js b/lib/web/eventsource/eventsource.js index 51634c1779f..5a488ffce27 100644 --- a/lib/web/eventsource/eventsource.js +++ b/lib/web/eventsource/eventsource.js @@ -105,6 +105,8 @@ class EventSource extends EventTarget { // 1. Let ev be a new EventSource object. super() + webidl.util.markAsUncloneable(this) + const prefix = 'EventSource constructor' webidl.argumentLengthCheck(arguments, 1, prefix) diff --git a/lib/web/fetch/formdata.js b/lib/web/fetch/formdata.js index 94a84b03ab3..544e4125519 100644 --- a/lib/web/fetch/formdata.js +++ b/lib/web/fetch/formdata.js @@ -14,6 +14,8 @@ const File = globalThis.File ?? NativeFile // https://xhr.spec.whatwg.org/#formdata class FormData { constructor (form) { + webidl.util.markAsUncloneable(this) + if (form !== undefined) { throw webidl.errors.conversionFailed({ prefix: 'FormData constructor', diff --git a/lib/web/fetch/headers.js b/lib/web/fetch/headers.js index 816aceacce4..a68daf4a5d4 100644 --- a/lib/web/fetch/headers.js +++ b/lib/web/fetch/headers.js @@ -359,6 +359,8 @@ class Headers { #headersList constructor (init = undefined) { + webidl.util.markAsUncloneable(this) + if (init === kConstruct) { return } diff --git a/lib/web/fetch/request.js b/lib/web/fetch/request.js index 542ea7fb28a..ee3ce488774 100644 --- a/lib/web/fetch/request.js +++ b/lib/web/fetch/request.js @@ -82,6 +82,7 @@ let patchMethodWarning = false class Request { // https://fetch.spec.whatwg.org/#dom-request constructor (input, init = {}) { + webidl.util.markAsUncloneable(this) if (input === kConstruct) { return } diff --git a/lib/web/fetch/response.js b/lib/web/fetch/response.js index 155dbadd1ad..3b6af35fbed 100644 --- a/lib/web/fetch/response.js +++ b/lib/web/fetch/response.js @@ -110,6 +110,7 @@ class Response { // https://fetch.spec.whatwg.org/#dom-response constructor (body = null, init = {}) { + webidl.util.markAsUncloneable(this) if (body === kConstruct) { return } diff --git a/lib/web/fetch/webidl.js b/lib/web/fetch/webidl.js index 13cafae6f1b..cd5cb14454c 100644 --- a/lib/web/fetch/webidl.js +++ b/lib/web/fetch/webidl.js @@ -1,6 +1,7 @@ 'use strict' const { types, inspect } = require('node:util') +const { markAsUncloneable } = require('node:worker_threads') const { toUSVString } = require('../../core/util') /** @type {import('../../../types/webidl').Webidl} */ @@ -86,6 +87,7 @@ webidl.util.Type = function (V) { } } +webidl.util.markAsUncloneable = markAsUncloneable || (() => {}) // https://webidl.spec.whatwg.org/#abstract-opdef-converttoint webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) { let upperBound diff --git a/lib/web/websocket/events.js b/lib/web/websocket/events.js index 760b7297359..f899c21d42b 100644 --- a/lib/web/websocket/events.js +++ b/lib/web/websocket/events.js @@ -14,6 +14,7 @@ class MessageEvent extends Event { constructor (type, eventInitDict = {}) { if (type === kConstruct) { super(arguments[1], arguments[2]) + webidl.util.markAsUncloneable(this) return } @@ -26,6 +27,7 @@ class MessageEvent extends Event { super(type, eventInitDict) this.#eventInit = eventInitDict + webidl.util.markAsUncloneable(this) } get data () { @@ -112,6 +114,7 @@ class CloseEvent extends Event { super(type, eventInitDict) this.#eventInit = eventInitDict + webidl.util.markAsUncloneable(this) } get wasClean () { @@ -142,6 +145,7 @@ class ErrorEvent extends Event { webidl.argumentLengthCheck(arguments, 1, prefix) super(type, eventInitDict) + webidl.util.markAsUncloneable(this) type = webidl.converters.DOMString(type, prefix, 'type') eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {}) diff --git a/lib/web/websocket/websocket.js b/lib/web/websocket/websocket.js index 109d7be2e2f..e4053024756 100644 --- a/lib/web/websocket/websocket.js +++ b/lib/web/websocket/websocket.js @@ -51,6 +51,8 @@ class WebSocket extends EventTarget { constructor (url, protocols = []) { super() + webidl.util.markAsUncloneable(this) + const prefix = 'WebSocket constructor' webidl.argumentLengthCheck(arguments, 1, prefix) diff --git a/test/node-platform-objects.js b/test/node-platform-objects.js new file mode 100644 index 00000000000..19e2b01a507 --- /dev/null +++ b/test/node-platform-objects.js @@ -0,0 +1,33 @@ +'use strict' + +const { tspl } = require('@matteo.collina/tspl') +const { test } = require('node:test') +const { markAsUncloneable } = require('node:worker_threads') +const { Response, Request, FormData, Headers, ErrorEvent, MessageEvent, CloseEvent, EventSource, WebSocket } = require('..') +const { CacheStorage } = require('../lib/web/cache/cachestorage') +const { Cache } = require('../lib/web/cache/cache') +const { kConstruct } = require('../lib/core/symbols') + +test('unserializable web instances should be uncloneable if node exposes the api', (t) => { + if (markAsUncloneable !== undefined) { + t = tspl(t, { plan: 11 }) + const uncloneables = [ + { Uncloneable: Response, brand: 'Response' }, + { Uncloneable: Request, value: 'http://localhost', brand: 'Request' }, + { Uncloneable: FormData, brand: 'FormData' }, + { Uncloneable: MessageEvent, value: 'dummy event', brand: 'MessageEvent' }, + { Uncloneable: CloseEvent, value: 'dummy event', brand: 'CloseEvent' }, + { Uncloneable: ErrorEvent, value: 'dummy event', brand: 'ErrorEvent' }, + { Uncloneable: EventSource, value: 'http://localhost', brand: 'EventSource' }, + { Uncloneable: Headers, brand: 'Headers' }, + { Uncloneable: WebSocket, value: 'http://localhost', brand: 'WebSocket' }, + { Uncloneable: Cache, value: kConstruct, brand: 'Cache' }, + { Uncloneable: CacheStorage, value: kConstruct, brand: 'CacheStorage' } + ] + uncloneables.forEach((platformEntity) => { + t.throws(() => structuredClone(new platformEntity.Uncloneable(platformEntity.value)), + DOMException, + `Cloning ${platformEntity.brand} should throw DOMException`) + }) + } +}) diff --git a/types/webidl.d.ts b/types/webidl.d.ts index 8a23a85bf01..fd83b68af90 100644 --- a/types/webidl.d.ts +++ b/types/webidl.d.ts @@ -67,6 +67,12 @@ interface WebidlUtil { * Stringifies {@param V} */ Stringify (V: any): string + + /** + * Mark a value as uncloneable for Node.js. + * This is only effective in some newer Node.js versions. + */ + markAsUncloneable (V: any): void } interface WebidlConverters {