Skip to content

Commit

Permalink
feat(extensions/web): add structuredClone function (denoland#11572)
Browse files Browse the repository at this point in the history
Co-authored-by: Luca Casonato <[email protected]>
  • Loading branch information
crowlKats and lucacasonato committed Aug 9, 2021
1 parent 02c74fb commit 16ae4a0
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 36 deletions.
2 changes: 1 addition & 1 deletion cli/dts/lib.deno.shared_globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions cli/dts/lib.deno.worker.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions cli/tests/unit/structured_clone_test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
12 changes: 12 additions & 0 deletions core/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down
14 changes: 7 additions & 7 deletions core/serialize_deserialize_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) ===
Expand All @@ -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(
Expand All @@ -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(
Expand Down
49 changes: 33 additions & 16 deletions extensions/web/13_message_port.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@

/**
* @param {any} message
* @param {object[] | PostMessageOptions} transferOrOptions
* @param {object[] | StructuredSerializeOptions} transferOrOptions
*/
postMessage(message, transferOrOptions = {}) {
webidl.assertBranded(this, MessagePort);
Expand All @@ -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)) {
Expand Down Expand Up @@ -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);
17 changes: 15 additions & 2 deletions extensions/web/lib.deno_web.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}

Expand Down Expand Up @@ -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`.
Expand All @@ -737,3 +745,8 @@ declare class MessagePort extends EventTarget {
options?: boolean | EventListenerOptions,
): void;
}

declare function structuredClone(
value: any,
options?: StructuredSerializeOptions,
): any;
11 changes: 7 additions & 4 deletions runtime/js/11_workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 8 additions & 4 deletions runtime/js/99_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit 16ae4a0

Please sign in to comment.