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,
);
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,

View file

@ -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)

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::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 {

View file

@ -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(

View file

@ -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, {
options = webidl.converters.StructuredSerializeOptions(
transferOrOptions,
{
prefix,
context: "Argument 2",
});
},
);
}
const { transfer } = options;
if (transfer.includes(this)) {
@ -247,8 +250,9 @@
};
}
webidl.converters.PostMessageOptions = webidl.createDictionaryConverter(
"PostMessageOptions",
webidl.converters.StructuredSerializeOptions = webidl
.createDictionaryConverter(
"StructuredSerializeOptions",
[
{
key: "transfer",
@ -260,10 +264,23 @@
],
);
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);

View file

@ -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;

View file

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

View file

@ -105,10 +105,13 @@ delete Object.prototype.__proto__;
);
options = { transfer };
} else {
options = webidl.converters.PostMessageOptions(transferOrOptions, {
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),