diff options
-rw-r--r-- | cli/dts/lib.deno.shared_globals.d.ts | 2 | ||||
-rw-r--r-- | cli/dts/lib.deno.worker.d.ts | 7 | ||||
-rw-r--r-- | cli/tests/unit/structured_clone_test.ts | 19 | ||||
-rw-r--r-- | core/bindings.rs | 12 | ||||
-rw-r--r-- | core/serialize_deserialize_test.js | 14 | ||||
-rw-r--r-- | extensions/web/13_message_port.js | 49 | ||||
-rw-r--r-- | extensions/web/lib.deno_web.d.ts | 17 | ||||
-rw-r--r-- | runtime/js/11_workers.js | 11 | ||||
-rw-r--r-- | runtime/js/99_main.js | 12 |
9 files changed, 107 insertions, 36 deletions
diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts index 849f9f835..26e3d1d5b 100644 --- a/cli/dts/lib.deno.shared_globals.d.ts +++ b/cli/dts/lib.deno.shared_globals.d.ts @@ -411,7 +411,7 @@ declare class Worker extends EventTarget { options?: WorkerOptions, ); postMessage(message: any, transfer: Transferable[]): void; - postMessage(message: any, options?: PostMessageOptions): void; + postMessage(message: any, options?: StructuredSerializeOptions): void; addEventListener<K extends keyof WorkerEventMap>( type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, diff --git a/cli/dts/lib.deno.worker.d.ts b/cli/dts/lib.deno.worker.d.ts index d35828135..34865b251 100644 --- a/cli/dts/lib.deno.worker.d.ts +++ b/cli/dts/lib.deno.worker.d.ts @@ -70,7 +70,7 @@ declare class DedicatedWorkerGlobalScope extends WorkerGlobalScope { | null; close(): void; postMessage(message: any, transfer: Transferable[]): void; - postMessage(message: any, options?: PostMessageOptions): void; + postMessage(message: any, options?: StructuredSerializeOptions): void; addEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>( type: K, listener: ( @@ -108,7 +108,10 @@ declare var onmessageerror: | null; declare function close(): void; declare function postMessage(message: any, transfer: Transferable[]): void; -declare function postMessage(message: any, options?: PostMessageOptions): void; +declare function postMessage( + message: any, + options?: StructuredSerializeOptions, +): void; declare var navigator: WorkerNavigator; declare var onerror: | ((this: DedicatedWorkerGlobalScope, ev: ErrorEvent) => any) diff --git a/cli/tests/unit/structured_clone_test.ts b/cli/tests/unit/structured_clone_test.ts new file mode 100644 index 000000000..f25276165 --- /dev/null +++ b/cli/tests/unit/structured_clone_test.ts @@ -0,0 +1,19 @@ +import { assert, assertEquals } from "./test_util.ts"; + +// Basic tests for the structured clone algorithm. Mainly tests TypeScript +// typings. Actual functionality is tested in WPT. + +Deno.test("self.structuredClone", async () => { + const arrayOriginal = ["hello world"]; + const channelOriginal = new MessageChannel(); + const [arrayCloned, portTransferred] = self + .structuredClone([arrayOriginal, channelOriginal.port2], { + transfer: [channelOriginal.port2], + }); + assert(arrayOriginal !== arrayCloned); // not the same identity + assertEquals(arrayCloned, arrayOriginal); // but same value + channelOriginal.port1.postMessage("1"); + await new Promise((resolve) => portTransferred.onmessage = () => resolve(1)); + channelOriginal.port1.close(); + portTransferred.close(); +}); diff --git a/core/bindings.rs b/core/bindings.rs index af8560c6a..935397480 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -26,6 +26,8 @@ use v8::HandleScope; use v8::Local; use v8::MapFnTo; use v8::SharedArrayBuffer; +use v8::ValueDeserializerHelper; +use v8::ValueSerializerHelper; lazy_static::lazy_static! { pub static ref EXTERNAL_REFERENCES: v8::ExternalReferences = @@ -827,6 +829,7 @@ fn serialize( let serialize_deserialize = Box::new(SerializeDeserialize { host_objects }); let mut value_serializer = v8::ValueSerializer::new(scope, serialize_deserialize); + value_serializer.write_header(); match value_serializer.write_value(scope.get_current_context(), value) { Some(true) => { let vector = value_serializer.release(); @@ -884,6 +887,15 @@ fn deserialize( let serialize_deserialize = Box::new(SerializeDeserialize { host_objects }); let mut value_deserializer = v8::ValueDeserializer::new(scope, serialize_deserialize, &zero_copy); + let parsed_header = value_deserializer + .read_header(scope.get_current_context()) + .unwrap_or_default(); + if !parsed_header { + let msg = v8::String::new(scope, "could not deserialize value").unwrap(); + let exception = v8::Exception::range_error(scope, msg); + scope.throw_exception(exception); + return; + } let value = value_deserializer.read_value(scope.get_current_context()); match value { diff --git a/core/serialize_deserialize_test.js b/core/serialize_deserialize_test.js index 6368d56db..b71bed0c9 100644 --- a/core/serialize_deserialize_test.js +++ b/core/serialize_deserialize_test.js @@ -19,7 +19,7 @@ function assertArrayEquals(a1, a2) { function main() { const emptyString = ""; - const emptyStringSerialized = [34, 0]; + const emptyStringSerialized = [255, 13, 34, 0]; assertArrayEquals(Deno.core.serialize(emptyString), emptyStringSerialized); assert( Deno.core.deserialize(new Uint8Array(emptyStringSerialized)) === @@ -29,7 +29,7 @@ function main() { const primitiveValueArray = ["test", "a", null, undefined]; // deno-fmt-ignore const primitiveValueArraySerialized = [ - 65, 4, 34, 4, 116, 101, 115, 116, + 255, 13, 65, 4, 34, 4, 116, 101, 115, 116, 34, 1, 97, 48, 95, 36, 0, 4, ]; assertArrayEquals( @@ -48,11 +48,11 @@ function main() { circularObject.test = circularObject; // deno-fmt-ignore const circularObjectSerialized = [ - 111, 34, 4, 116, 101, 115, 116, 94, - 0, 34, 5, 116, 101, 115, 116, 50, - 34, 2, 100, 100, 34, 5, 116, 101, - 115, 116, 51, 34, 2, 97, 97, 123, - 3, + 255, 13, 111, 34, 4, 116, 101, 115, + 116, 94, 0, 34, 5, 116, 101, 115, + 116, 50, 34, 2, 100, 100, 34, 5, + 116, 101, 115, 116, 51, 34, 2, 97, + 97, 123, 3, ]; assertArrayEquals( diff --git a/extensions/web/13_message_port.js b/extensions/web/13_message_port.js index d111b5e01..d5014fdb9 100644 --- a/extensions/web/13_message_port.js +++ b/extensions/web/13_message_port.js @@ -89,7 +89,7 @@ /** * @param {any} message - * @param {object[] | PostMessageOptions} transferOrOptions + * @param {object[] | StructuredSerializeOptions} transferOrOptions */ postMessage(message, transferOrOptions = {}) { webidl.assertBranded(this, MessagePort); @@ -108,10 +108,13 @@ ); options = { transfer }; } else { - options = webidl.converters.PostMessageOptions(transferOrOptions, { - prefix, - context: "Argument 2", - }); + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } const { transfer } = options; if (transfer.includes(this)) { @@ -247,23 +250,37 @@ }; } - webidl.converters.PostMessageOptions = webidl.createDictionaryConverter( - "PostMessageOptions", - [ - { - key: "transfer", - converter: webidl.converters["sequence<object>"], - get defaultValue() { - return []; + webidl.converters.StructuredSerializeOptions = webidl + .createDictionaryConverter( + "StructuredSerializeOptions", + [ + { + key: "transfer", + converter: webidl.converters["sequence<object>"], + get defaultValue() { + return []; + }, }, - }, - ], - ); + ], + ); + + function structuredClone(value, options) { + const prefix = "Failed to execute 'structuredClone'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + options = webidl.converters.StructuredSerializeOptions(options, { + prefix, + context: "Argument 2", + }); + const messageData = serializeJsMessageData(value, options.transfer); + const [data] = deserializeJsMessageData(messageData); + return data; + } window.__bootstrap.messagePort = { MessageChannel, MessagePort, deserializeJsMessageData, serializeJsMessageData, + structuredClone, }; })(globalThis); diff --git a/extensions/web/lib.deno_web.d.ts b/extensions/web/lib.deno_web.d.ts index 6c79f61bd..3f110353f 100644 --- a/extensions/web/lib.deno_web.d.ts +++ b/extensions/web/lib.deno_web.d.ts @@ -673,7 +673,15 @@ declare class MessageEvent<T = any> extends Event { type Transferable = ArrayBuffer | MessagePort; -interface PostMessageOptions { +/** + * @deprecated + * + * This type has been renamed to StructuredSerializeOptions. Use that type for + * new code. + */ +type PostMessageOptions = StructuredSerializeOptions; + +interface StructuredSerializeOptions { transfer?: Transferable[]; } @@ -710,7 +718,7 @@ declare class MessagePort extends EventTarget { * objects or port, or if message could not be cloned. */ postMessage(message: any, transfer: Transferable[]): void; - postMessage(message: any, options?: PostMessageOptions): void; + postMessage(message: any, options?: StructuredSerializeOptions): void; /** * Begins dispatching messages received on the port. This is implictly called * when assiging a value to `this.onmessage`. @@ -737,3 +745,8 @@ declare class MessagePort extends EventTarget { options?: boolean | EventListenerOptions, ): void; } + +declare function structuredClone( + value: any, + options?: StructuredSerializeOptions, +): any; diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js index 38267f571..b5a8a9d0c 100644 --- a/runtime/js/11_workers.js +++ b/runtime/js/11_workers.js @@ -318,10 +318,13 @@ ); options = { transfer }; } else { - options = webidl.converters.PostMessageOptions(transferOrOptions, { - prefix, - context: "Argument 2", - }); + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } const { transfer } = options; const data = serializeJsMessageData(message, transfer); diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 16a444098..a85c40eb9 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -105,10 +105,13 @@ delete Object.prototype.__proto__; ); options = { transfer }; } else { - options = webidl.converters.PostMessageOptions(transferOrOptions, { - prefix, - context: "Argument 2", - }); + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } const { transfer } = options; const data = serializeJsMessageData(message, transfer); @@ -373,6 +376,7 @@ delete Object.prototype.__proto__; performance: util.writable(performance.performance), setInterval: util.writable(timers.setInterval), setTimeout: util.writable(timers.setTimeout), + structuredClone: util.writable(messagePort.structuredClone), GPU: util.nonEnumerable(webgpu.GPU), GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), |