summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo K <crowlkats@toaxl.com>2021-08-09 10:39:00 +0200
committerGitHub <noreply@github.com>2021-08-09 10:39:00 +0200
commit16ae4a0d5799c9a4ed776f32929f73b1063ae4e8 (patch)
treeed5d0def5e0acbf67f2c64ac4664b1af46f4c468
parent02c74fb70970fcadb7d1e6dab857eeb2cea20e09 (diff)
feat(extensions/web): add structuredClone function (#11572)
Co-authored-by: Luca Casonato <hello@lcas.dev>
-rw-r--r--cli/dts/lib.deno.shared_globals.d.ts2
-rw-r--r--cli/dts/lib.deno.worker.d.ts7
-rw-r--r--cli/tests/unit/structured_clone_test.ts19
-rw-r--r--core/bindings.rs12
-rw-r--r--core/serialize_deserialize_test.js14
-rw-r--r--extensions/web/13_message_port.js49
-rw-r--r--extensions/web/lib.deno_web.d.ts17
-rw-r--r--runtime/js/11_workers.js11
-rw-r--r--runtime/js/99_main.js12
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),