1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

feat(extensions/web): add structuredClone function (#11572)

Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
Leo K 2021-08-09 10:39:00 +02:00 committed by GitHub
parent 02c74fb709
commit 16ae4a0d57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 36 deletions

View file

@ -411,7 +411,7 @@ declare class Worker extends EventTarget {
options?: WorkerOptions, options?: WorkerOptions,
); );
postMessage(message: any, transfer: Transferable[]): void; postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: PostMessageOptions): void; postMessage(message: any, options?: StructuredSerializeOptions): void;
addEventListener<K extends keyof WorkerEventMap>( addEventListener<K extends keyof WorkerEventMap>(
type: K, type: K,
listener: (this: Worker, ev: WorkerEventMap[K]) => any, listener: (this: Worker, ev: WorkerEventMap[K]) => any,

View file

@ -70,7 +70,7 @@ declare class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
| null; | null;
close(): void; close(): void;
postMessage(message: any, transfer: Transferable[]): void; postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: PostMessageOptions): void; postMessage(message: any, options?: StructuredSerializeOptions): void;
addEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>( addEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>(
type: K, type: K,
listener: ( listener: (
@ -108,7 +108,10 @@ declare var onmessageerror:
| null; | null;
declare function close(): void; declare function close(): void;
declare function postMessage(message: any, transfer: Transferable[]): 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 navigator: WorkerNavigator;
declare var onerror: declare var onerror:
| ((this: DedicatedWorkerGlobalScope, ev: ErrorEvent) => any) | ((this: DedicatedWorkerGlobalScope, ev: ErrorEvent) => any)

View file

@ -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();
});

View file

@ -26,6 +26,8 @@ use v8::HandleScope;
use v8::Local; use v8::Local;
use v8::MapFnTo; use v8::MapFnTo;
use v8::SharedArrayBuffer; use v8::SharedArrayBuffer;
use v8::ValueDeserializerHelper;
use v8::ValueSerializerHelper;
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref EXTERNAL_REFERENCES: v8::ExternalReferences = pub static ref EXTERNAL_REFERENCES: v8::ExternalReferences =
@ -827,6 +829,7 @@ fn serialize(
let serialize_deserialize = Box::new(SerializeDeserialize { host_objects }); let serialize_deserialize = Box::new(SerializeDeserialize { host_objects });
let mut value_serializer = let mut value_serializer =
v8::ValueSerializer::new(scope, serialize_deserialize); v8::ValueSerializer::new(scope, serialize_deserialize);
value_serializer.write_header();
match value_serializer.write_value(scope.get_current_context(), value) { match value_serializer.write_value(scope.get_current_context(), value) {
Some(true) => { Some(true) => {
let vector = value_serializer.release(); let vector = value_serializer.release();
@ -884,6 +887,15 @@ fn deserialize(
let serialize_deserialize = Box::new(SerializeDeserialize { host_objects }); let serialize_deserialize = Box::new(SerializeDeserialize { host_objects });
let mut value_deserializer = let mut value_deserializer =
v8::ValueDeserializer::new(scope, serialize_deserialize, &zero_copy); 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()); let value = value_deserializer.read_value(scope.get_current_context());
match value { match value {

View file

@ -19,7 +19,7 @@ function assertArrayEquals(a1, a2) {
function main() { function main() {
const emptyString = ""; const emptyString = "";
const emptyStringSerialized = [34, 0]; const emptyStringSerialized = [255, 13, 34, 0];
assertArrayEquals(Deno.core.serialize(emptyString), emptyStringSerialized); assertArrayEquals(Deno.core.serialize(emptyString), emptyStringSerialized);
assert( assert(
Deno.core.deserialize(new Uint8Array(emptyStringSerialized)) === Deno.core.deserialize(new Uint8Array(emptyStringSerialized)) ===
@ -29,7 +29,7 @@ function main() {
const primitiveValueArray = ["test", "a", null, undefined]; const primitiveValueArray = ["test", "a", null, undefined];
// deno-fmt-ignore // deno-fmt-ignore
const primitiveValueArraySerialized = [ 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, 34, 1, 97, 48, 95, 36, 0, 4,
]; ];
assertArrayEquals( assertArrayEquals(
@ -48,11 +48,11 @@ function main() {
circularObject.test = circularObject; circularObject.test = circularObject;
// deno-fmt-ignore // deno-fmt-ignore
const circularObjectSerialized = [ const circularObjectSerialized = [
111, 34, 4, 116, 101, 115, 116, 94, 255, 13, 111, 34, 4, 116, 101, 115,
0, 34, 5, 116, 101, 115, 116, 50, 116, 94, 0, 34, 5, 116, 101, 115,
34, 2, 100, 100, 34, 5, 116, 101, 116, 50, 34, 2, 100, 100, 34, 5,
115, 116, 51, 34, 2, 97, 97, 123, 116, 101, 115, 116, 51, 34, 2, 97,
3, 97, 123, 3,
]; ];
assertArrayEquals( assertArrayEquals(

View file

@ -89,7 +89,7 @@
/** /**
* @param {any} message * @param {any} message
* @param {object[] | PostMessageOptions} transferOrOptions * @param {object[] | StructuredSerializeOptions} transferOrOptions
*/ */
postMessage(message, transferOrOptions = {}) { postMessage(message, transferOrOptions = {}) {
webidl.assertBranded(this, MessagePort); webidl.assertBranded(this, MessagePort);
@ -108,10 +108,13 @@
); );
options = { transfer }; options = { transfer };
} else { } else {
options = webidl.converters.PostMessageOptions(transferOrOptions, { options = webidl.converters.StructuredSerializeOptions(
prefix, transferOrOptions,
context: "Argument 2", {
}); prefix,
context: "Argument 2",
},
);
} }
const { transfer } = options; const { transfer } = options;
if (transfer.includes(this)) { if (transfer.includes(this)) {
@ -247,23 +250,37 @@
}; };
} }
webidl.converters.PostMessageOptions = webidl.createDictionaryConverter( webidl.converters.StructuredSerializeOptions = webidl
"PostMessageOptions", .createDictionaryConverter(
[ "StructuredSerializeOptions",
{ [
key: "transfer", {
converter: webidl.converters["sequence<object>"], key: "transfer",
get defaultValue() { converter: webidl.converters["sequence<object>"],
return []; 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 = { window.__bootstrap.messagePort = {
MessageChannel, MessageChannel,
MessagePort, MessagePort,
deserializeJsMessageData, deserializeJsMessageData,
serializeJsMessageData, serializeJsMessageData,
structuredClone,
}; };
})(globalThis); })(globalThis);

View file

@ -673,7 +673,15 @@ declare class MessageEvent<T = any> extends Event {
type Transferable = ArrayBuffer | MessagePort; 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[]; transfer?: Transferable[];
} }
@ -710,7 +718,7 @@ declare class MessagePort extends EventTarget {
* objects or port, or if message could not be cloned. * objects or port, or if message could not be cloned.
*/ */
postMessage(message: any, transfer: Transferable[]): void; 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 * Begins dispatching messages received on the port. This is implictly called
* when assiging a value to `this.onmessage`. * when assiging a value to `this.onmessage`.
@ -737,3 +745,8 @@ declare class MessagePort extends EventTarget {
options?: boolean | EventListenerOptions, options?: boolean | EventListenerOptions,
): void; ): void;
} }
declare function structuredClone(
value: any,
options?: StructuredSerializeOptions,
): any;

View file

@ -318,10 +318,13 @@
); );
options = { transfer }; options = { transfer };
} else { } else {
options = webidl.converters.PostMessageOptions(transferOrOptions, { options = webidl.converters.StructuredSerializeOptions(
prefix, transferOrOptions,
context: "Argument 2", {
}); prefix,
context: "Argument 2",
},
);
} }
const { transfer } = options; const { transfer } = options;
const data = serializeJsMessageData(message, transfer); const data = serializeJsMessageData(message, transfer);

View file

@ -105,10 +105,13 @@ delete Object.prototype.__proto__;
); );
options = { transfer }; options = { transfer };
} else { } else {
options = webidl.converters.PostMessageOptions(transferOrOptions, { options = webidl.converters.StructuredSerializeOptions(
prefix, transferOrOptions,
context: "Argument 2", {
}); prefix,
context: "Argument 2",
},
);
} }
const { transfer } = options; const { transfer } = options;
const data = serializeJsMessageData(message, transfer); const data = serializeJsMessageData(message, transfer);
@ -373,6 +376,7 @@ delete Object.prototype.__proto__;
performance: util.writable(performance.performance), performance: util.writable(performance.performance),
setInterval: util.writable(timers.setInterval), setInterval: util.writable(timers.setInterval),
setTimeout: util.writable(timers.setTimeout), setTimeout: util.writable(timers.setTimeout),
structuredClone: util.writable(messagePort.structuredClone),
GPU: util.nonEnumerable(webgpu.GPU), GPU: util.nonEnumerable(webgpu.GPU),
GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter),