summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--cli/dts/lib.deno.shared_globals.d.ts22
-rw-r--r--cli/tests/unit/message_channel_test.ts33
-rw-r--r--extensions/web/02_event.js42
-rw-r--r--extensions/web/03_abort_signal.js39
-rw-r--r--extensions/web/13_message_port.js253
-rw-r--r--extensions/web/Cargo.toml1
-rw-r--r--extensions/web/internal.d.ts14
-rw-r--r--extensions/web/lib.deno_web.d.ts89
-rw-r--r--extensions/web/lib.rs22
-rw-r--r--extensions/web/message_port.rs208
-rw-r--r--extensions/webidl/00_webidl.js6
-rw-r--r--extensions/webidl/internal.d.ts15
-rw-r--r--runtime/js/99_main.js3
m---------test_util/wpt0
-rw-r--r--tools/wpt/expectation.json40
-rw-r--r--tools/wpt/runner.ts2
17 files changed, 719 insertions, 71 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3c924936b..595700e5d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -781,6 +781,7 @@ dependencies = [
"encoding_rs",
"futures",
"serde",
+ "tokio",
"uuid",
]
diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts
index d46a43e67..46154c64e 100644
--- a/cli/dts/lib.deno.shared_globals.d.ts
+++ b/cli/dts/lib.deno.shared_globals.d.ts
@@ -347,24 +347,6 @@ type BufferSource = ArrayBufferView | ArrayBuffer;
declare var console: Console;
-interface MessageEventInit<T = any> extends EventInit {
- data?: T;
- origin?: string;
- lastEventId?: string;
-}
-
-declare class MessageEvent<T = any> extends Event {
- /**
- * Returns the data of the message.
- */
- readonly data: T;
- /**
- * Returns the last event ID string, for server-sent events.
- */
- readonly lastEventId: string;
- constructor(type: string, eventInitDict?: MessageEventInit);
-}
-
interface ErrorEventInit extends EventInit {
message?: string;
filename?: string;
@@ -382,10 +364,6 @@ declare class ErrorEvent extends Event {
constructor(type: string, eventInitDict?: ErrorEventInit);
}
-interface PostMessageOptions {
- transfer?: any[];
-}
-
interface AbstractWorkerEventMap {
"error": ErrorEvent;
}
diff --git a/cli/tests/unit/message_channel_test.ts b/cli/tests/unit/message_channel_test.ts
new file mode 100644
index 000000000..0cb2671d5
--- /dev/null
+++ b/cli/tests/unit/message_channel_test.ts
@@ -0,0 +1,33 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+// NOTE: these are just sometests to test the TypeScript types. Real coverage is
+// provided by WPT.
+import {
+ assert,
+ assertEquals,
+} from "../../../test_util/std/testing/asserts.ts";
+import { deferred } from "../../../test_util/std/async/deferred.ts";
+
+Deno.test("messagechannel", async () => {
+ const mc = new MessageChannel();
+ const mc2 = new MessageChannel();
+ assert(mc.port1);
+ assert(mc.port2);
+
+ const promise = deferred();
+
+ mc.port2.onmessage = (e) => {
+ assertEquals(e.data, "hello");
+ assertEquals(e.ports.length, 1);
+ assert(e.ports[0] instanceof MessagePort);
+ e.ports[0].close();
+ promise.resolve();
+ };
+
+ mc.port1.postMessage("hello", [mc2.port1]);
+ mc.port1.close();
+
+ await promise;
+
+ mc.port2.close();
+ mc2.port2.close();
+});
diff --git a/extensions/web/02_event.js b/extensions/web/02_event.js
index 8ee6acc61..17d5cb54e 100644
--- a/extensions/web/02_event.js
+++ b/extensions/web/02_event.js
@@ -1129,6 +1129,7 @@
});
this.data = eventInitDict?.data ?? null;
+ this.ports = eventInitDict?.ports ?? [];
this.origin = eventInitDict?.origin ?? "";
this.lastEventId = eventInitDict?.lastEventId ?? "";
}
@@ -1196,6 +1197,46 @@
}
}
+ const _eventHandlers = Symbol("eventHandlers");
+
+ function makeWrappedHandler(handler) {
+ function wrappedHandler(...args) {
+ if (typeof wrappedHandler.handler !== "function") {
+ return;
+ }
+ return wrappedHandler.handler.call(this, ...args);
+ }
+ wrappedHandler.handler = handler;
+ return wrappedHandler;
+ }
+
+ // TODO(benjamingr) reuse this here and websocket where possible
+ function defineEventHandler(emitter, name, init) {
+ // HTML specification section 8.1.5.1
+ Object.defineProperty(emitter, `on${name}`, {
+ get() {
+ return this[_eventHandlers]?.get(name)?.handler;
+ },
+ set(value) {
+ if (!this[_eventHandlers]) {
+ this[_eventHandlers] = new Map();
+ }
+ let handlerWrapper = this[_eventHandlers]?.get(name);
+ if (handlerWrapper) {
+ console.log("foo");
+ handlerWrapper.handler = value;
+ } else {
+ handlerWrapper = makeWrappedHandler(value);
+ this.addEventListener(name, handlerWrapper);
+ init?.(this);
+ }
+ this[_eventHandlers].set(name, handlerWrapper);
+ },
+ configurable: true,
+ enumerable: true,
+ });
+ }
+
window.Event = Event;
window.EventTarget = EventTarget;
window.ErrorEvent = ErrorEvent;
@@ -1213,5 +1254,6 @@
window.__bootstrap.event = {
setIsTrusted,
setTarget,
+ defineEventHandler,
};
})(this);
diff --git a/extensions/web/03_abort_signal.js b/extensions/web/03_abort_signal.js
index 6551380da..54a485dab 100644
--- a/extensions/web/03_abort_signal.js
+++ b/extensions/web/03_abort_signal.js
@@ -3,7 +3,7 @@
((window) => {
const webidl = window.__bootstrap.webidl;
- const { setIsTrusted } = window.__bootstrap.event;
+ const { setIsTrusted, defineEventHandler } = window.__bootstrap.event;
const add = Symbol("add");
const signalAbort = Symbol("signalAbort");
@@ -81,43 +81,6 @@
webidl.configurePrototype(AbortController);
- const handlerSymbol = Symbol("eventHandlers");
-
- function makeWrappedHandler(handler) {
- function wrappedHandler(...args) {
- if (typeof wrappedHandler.handler !== "function") {
- return;
- }
- return wrappedHandler.handler.call(this, ...args);
- }
- wrappedHandler.handler = handler;
- return wrappedHandler;
- }
- // TODO(benjamingr) reuse this here and websocket where possible
- function defineEventHandler(emitter, name) {
- // HTML specification section 8.1.5.1
- Object.defineProperty(emitter, `on${name}`, {
- get() {
- return this[handlerSymbol]?.get(name)?.handler;
- },
- set(value) {
- if (!this[handlerSymbol]) {
- this[handlerSymbol] = new Map();
- }
- let handlerWrapper = this[handlerSymbol]?.get(name);
- if (handlerWrapper) {
- handlerWrapper.handler = value;
- } else {
- handlerWrapper = makeWrappedHandler(value);
- this.addEventListener(name, handlerWrapper);
- }
- this[handlerSymbol].set(name, handlerWrapper);
- },
- configurable: true,
- enumerable: true,
- });
- }
-
webidl.converters["AbortSignal"] = webidl.createInterfaceConverter(
"AbortSignal",
AbortSignal,
diff --git a/extensions/web/13_message_port.js b/extensions/web/13_message_port.js
new file mode 100644
index 000000000..50d3f5d04
--- /dev/null
+++ b/extensions/web/13_message_port.js
@@ -0,0 +1,253 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+// @ts-check
+/// <reference path="../../core/lib.deno_core.d.ts" />
+/// <reference path="../webidl/internal.d.ts" />
+/// <reference path="./internal.d.ts" />
+/// <reference path="./lib.deno_web.d.ts" />
+
+"use strict";
+
+((window) => {
+ const core = window.Deno.core;
+ const webidl = window.__bootstrap.webidl;
+ const { setEventTargetData } = window.__bootstrap.eventTarget;
+ const { defineEventHandler } = window.__bootstrap.event;
+
+ class MessageChannel {
+ /** @type {MessagePort} */
+ #port1;
+ /** @type {MessagePort} */
+ #port2;
+
+ constructor() {
+ this[webidl.brand] = webidl.brand;
+ const [port1Id, port2Id] = opCreateEntangledMessagePort();
+ const port1 = createMessagePort(port1Id);
+ const port2 = createMessagePort(port2Id);
+ this.#port1 = port1;
+ this.#port2 = port2;
+ }
+
+ get port1() {
+ webidl.assertBranded(this, MessageChannel);
+ return this.#port1;
+ }
+
+ get port2() {
+ webidl.assertBranded(this, MessageChannel);
+ return this.#port2;
+ }
+
+ [Symbol.for("Deno.inspect")](inspect) {
+ return `MessageChannel ${
+ inspect({ port1: this.port1, port2: this.port2 })
+ }`;
+ }
+
+ get [Symbol.toStringTag]() {
+ return "MessageChannel";
+ }
+ }
+
+ webidl.configurePrototype(MessageChannel);
+
+ const _id = Symbol("id");
+ const _enabled = Symbol("enabled");
+
+ /**
+ * @param {number} id
+ * @returns {MessagePort}
+ */
+ function createMessagePort(id) {
+ const port = webidl.createBranded(MessagePort);
+ setEventTargetData(port);
+ port[_id] = id;
+ return port;
+ }
+
+ class MessagePort extends EventTarget {
+ /** @type {number | null} */
+ [_id] = null;
+ /** @type {boolean} */
+ [_enabled] = false;
+
+ constructor() {
+ super();
+ webidl.illegalConstructor();
+ }
+
+ /**
+ * @param {any} message
+ * @param {object[] | PostMessageOptions} transferOrOptions
+ */
+ postMessage(message, transferOrOptions = {}) {
+ webidl.assertBranded(this, MessagePort);
+ const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ message = webidl.converters.any(message);
+ let options;
+ if (
+ webidl.type(transferOrOptions) === "Object" &&
+ transferOrOptions !== undefined &&
+ transferOrOptions[Symbol.iterator] !== undefined
+ ) {
+ const transfer = webidl.converters["sequence<object>"](
+ transferOrOptions,
+ { prefix, context: "Argument 2" },
+ );
+ options = { transfer };
+ } else {
+ options = webidl.converters.PostMessageOptions(transferOrOptions, {
+ prefix,
+ context: "Argument 2",
+ });
+ }
+ const { transfer } = options;
+ if (transfer.includes(this)) {
+ throw new DOMException("Can not tranfer self", "DataCloneError");
+ }
+ const data = serializeJsMessageData(message, transfer);
+ if (this[_id] === null) return;
+ core.opSync("op_message_port_post_message", this[_id], data);
+ }
+
+ start() {
+ webidl.assertBranded(this, MessagePort);
+ if (this[_enabled]) return;
+ (async () => {
+ this[_enabled] = true;
+ while (true) {
+ if (this[_id] === null) break;
+ const data = await core.opAsync(
+ "op_message_port_recv_message",
+ this[_id],
+ );
+ if (data === null) break;
+ let message, transfer;
+ try {
+ const v = deserializeJsMessageData(data);
+ message = v[0];
+ transfer = v[1];
+ } catch (err) {
+ const event = new MessageEvent("messageerror", { data: err });
+ this.dispatchEvent(event);
+ return;
+ }
+ const event = new MessageEvent("message", {
+ data: message,
+ ports: transfer,
+ });
+ this.dispatchEvent(event);
+ }
+ this[_enabled] = false;
+ })();
+ }
+
+ close() {
+ webidl.assertBranded(this, MessagePort);
+ if (this[_id] !== null) {
+ core.close(this[_id]);
+ this[_id] = null;
+ }
+ }
+ }
+
+ defineEventHandler(MessagePort.prototype, "message", function (self) {
+ self.start();
+ });
+ defineEventHandler(MessagePort.prototype, "messageerror");
+
+ webidl.configurePrototype(MessagePort);
+
+ /**
+ * @returns {[number, number]}
+ */
+ function opCreateEntangledMessagePort() {
+ return core.opSync("op_message_port_create_entangled");
+ }
+
+ /**
+ * @param {globalThis.__bootstrap.messagePort.MessageData} messageData
+ * @returns {[any, object[]]}
+ */
+ function deserializeJsMessageData(messageData) {
+ /** @type {object[]} */
+ const transferables = [];
+
+ for (const transferable of messageData.transferables) {
+ switch (transferable.kind) {
+ case "messagePort": {
+ const port = createMessagePort(transferable.data);
+ transferables.push(port);
+ break;
+ }
+ default:
+ throw new TypeError("Unreachable");
+ }
+ }
+
+ const data = core.deserialize(messageData.data);
+
+ return [data, transferables];
+ }
+
+ /**
+ * @param {any} data
+ * @param {object[]} tranferables
+ * @returns {globalThis.__bootstrap.messagePort.MessageData}
+ */
+ function serializeJsMessageData(data, tranferables) {
+ let serializedData;
+ try {
+ serializedData = core.serialize(data);
+ } catch (err) {
+ throw new DOMException(err.message, "DataCloneError");
+ }
+
+ /** @type {globalThis.__bootstrap.messagePort.Transferable[]} */
+ const serializedTransferables = [];
+
+ for (const transferable of tranferables) {
+ if (transferable instanceof MessagePort) {
+ webidl.assertBranded(transferable, MessagePort);
+ const id = transferable[_id];
+ if (id === null) {
+ throw new DOMException(
+ "Can not transfer disentangled message port",
+ "DataCloneError",
+ );
+ }
+ transferable[_id] = null;
+ serializedTransferables.push({ kind: "messagePort", data: id });
+ } else {
+ throw new DOMException("Value not transferable", "DataCloneError");
+ }
+ }
+
+ return {
+ data: serializedData,
+ transferables: serializedTransferables,
+ };
+ }
+
+ webidl.converters.PostMessageOptions = webidl.createDictionaryConverter(
+ "PostMessageOptions",
+ [
+ {
+ key: "transfer",
+ converter: webidl.converters["sequence<object>"],
+ get defaultValue() {
+ return [];
+ },
+ },
+ ],
+ );
+
+ window.__bootstrap.messagePort = {
+ MessageChannel,
+ MessagePort,
+ deserializeJsMessageData,
+ serializeJsMessageData,
+ };
+})(globalThis);
diff --git a/extensions/web/Cargo.toml b/extensions/web/Cargo.toml
index 5da9092f3..33b75143b 100644
--- a/extensions/web/Cargo.toml
+++ b/extensions/web/Cargo.toml
@@ -18,6 +18,7 @@ base64 = "0.13.0"
deno_core = { version = "0.91.0", path = "../../core" }
encoding_rs = "0.8.28"
serde = "1.0"
+tokio = "1.7"
uuid = { version = "0.8.2", features = ["v4"] }
[dev-dependencies]
diff --git a/extensions/web/internal.d.ts b/extensions/web/internal.d.ts
index 8ab101077..06976b28b 100644
--- a/extensions/web/internal.d.ts
+++ b/extensions/web/internal.d.ts
@@ -4,9 +4,6 @@
/// <reference lib="esnext" />
declare namespace globalThis {
- declare var TextEncoder: typeof TextEncoder;
- declare var TextDecoder: typeof TextDecoder;
-
declare namespace __bootstrap {
declare var infra: {
collectSequenceOfCodepoints(
@@ -85,5 +82,16 @@ declare namespace globalThis {
ReadableStream: typeof ReadableStream;
isReadableStreamDisturbed(stream: ReadableStream): boolean;
};
+
+ declare namespace messagePort {
+ declare type Transferable = {
+ kind: "messagePort";
+ data: number;
+ };
+ declare interface MessageData {
+ data: Uint8Array;
+ transferables: Transferable[];
+ }
+ }
}
}
diff --git a/extensions/web/lib.deno_web.d.ts b/extensions/web/lib.deno_web.d.ts
index 888fe9de9..6c79f61bd 100644
--- a/extensions/web/lib.deno_web.d.ts
+++ b/extensions/web/lib.deno_web.d.ts
@@ -648,3 +648,92 @@ interface TransformStreamDefaultControllerTransformCallback<I, O> {
controller: TransformStreamDefaultController<O>,
): void | PromiseLike<void>;
}
+
+interface MessageEventInit<T = any> extends EventInit {
+ data?: T;
+ origin?: string;
+ lastEventId?: string;
+}
+
+declare class MessageEvent<T = any> extends Event {
+ /**
+ * Returns the data of the message.
+ */
+ readonly data: T;
+ /**
+ * Returns the last event ID string, for server-sent events.
+ */
+ readonly lastEventId: string;
+ /**
+ * Returns transfered ports.
+ */
+ readonly ports: ReadonlyArray<MessagePort>;
+ constructor(type: string, eventInitDict?: MessageEventInit);
+}
+
+type Transferable = ArrayBuffer | MessagePort;
+
+interface PostMessageOptions {
+ transfer?: Transferable[];
+}
+
+/** The MessageChannel interface of the Channel Messaging API allows us to
+ * create a new message channel and send data through it via its two MessagePort
+ * properties. */
+declare class MessageChannel {
+ constructor();
+ readonly port1: MessagePort;
+ readonly port2: MessagePort;
+}
+
+interface MessagePortEventMap {
+ "message": MessageEvent;
+ "messageerror": MessageEvent;
+}
+
+/** The MessagePort interface of the Channel Messaging API represents one of the
+ * two ports of a MessageChannel, allowing messages to be sent from one port and
+ * listening out for them arriving at the other. */
+declare class MessagePort extends EventTarget {
+ onmessage: ((this: MessagePort, ev: MessageEvent) => any) | null;
+ onmessageerror: ((this: MessagePort, ev: MessageEvent) => any) | null;
+ /**
+ * Disconnects the port, so that it is no longer active.
+ */
+ close(): void;
+ /**
+ * Posts a message through the channel. Objects listed in transfer are
+ * transferred, not just cloned, meaning that they are no longer usable on the
+ * sending side.
+ *
+ * Throws a "DataCloneError" DOMException if transfer contains duplicate
+ * objects or port, or if message could not be cloned.
+ */
+ postMessage(message: any, transfer: Transferable[]): void;
+ postMessage(message: any, options?: PostMessageOptions): void;
+ /**
+ * Begins dispatching messages received on the port. This is implictly called
+ * when assiging a value to `this.onmessage`.
+ */
+ start(): void;
+ addEventListener<K extends keyof MessagePortEventMap>(
+ type: K,
+ listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ removeEventListener<K extends keyof MessagePortEventMap>(
+ type: K,
+ listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any,
+ options?: boolean | EventListenerOptions,
+ ): void;
+ removeEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions,
+ ): void;
+}
diff --git a/extensions/web/lib.rs b/extensions/web/lib.rs
index 9f7836620..d74bb619d 100644
--- a/extensions/web/lib.rs
+++ b/extensions/web/lib.rs
@@ -1,11 +1,16 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+mod message_port;
+
+pub use crate::message_port::JsMessageData;
+
use deno_core::error::bad_resource_id;
use deno_core::error::null_opbuf;
use deno_core::error::range_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::include_js_files;
+use deno_core::op_async;
use deno_core::op_sync;
use deno_core::url::Url;
use deno_core::Extension;
@@ -30,6 +35,10 @@ use std::sync::Mutex;
use std::usize;
use uuid::Uuid;
+use crate::message_port::op_message_port_create_entangled;
+use crate::message_port::op_message_port_post_message;
+use crate::message_port::op_message_port_recv_message;
+
/// Load and execute the javascript code.
pub fn init(
blob_url_store: BlobUrlStore,
@@ -52,6 +61,7 @@ pub fn init(
"10_filereader.js",
"11_blob_url.js",
"12_location.js",
+ "13_message_port.js",
))
.ops(vec![
("op_base64_decode", op_sync(op_base64_decode)),
@@ -71,6 +81,18 @@ pub fn init(
"op_file_revoke_object_url",
op_sync(op_file_revoke_object_url),
),
+ (
+ "op_message_port_create_entangled",
+ op_sync(op_message_port_create_entangled),
+ ),
+ (
+ "op_message_port_post_message",
+ op_sync(op_message_port_post_message),
+ ),
+ (
+ "op_message_port_recv_message",
+ op_async(op_message_port_recv_message),
+ ),
])
.state(move |state| {
state.put(blob_url_store.clone());
diff --git a/extensions/web/message_port.rs b/extensions/web/message_port.rs
new file mode 100644
index 000000000..d10b455d5
--- /dev/null
+++ b/extensions/web/message_port.rs
@@ -0,0 +1,208 @@
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use deno_core::error::bad_resource_id;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::ZeroCopyBuf;
+use deno_core::{CancelFuture, Resource};
+use deno_core::{CancelHandle, OpState};
+use deno_core::{RcRef, ResourceId};
+use serde::Deserialize;
+use serde::Serialize;
+use tokio::sync::mpsc::unbounded_channel;
+use tokio::sync::mpsc::UnboundedReceiver;
+use tokio::sync::mpsc::UnboundedSender;
+
+enum Transferable {
+ MessagePort(MessagePort),
+}
+
+type MessagePortMessage = (Vec<u8>, Vec<Transferable>);
+
+pub struct MessagePort {
+ rx: RefCell<UnboundedReceiver<MessagePortMessage>>,
+ tx: UnboundedSender<MessagePortMessage>,
+}
+
+impl MessagePort {
+ pub fn send(
+ &self,
+ state: &mut OpState,
+ data: JsMessageData,
+ ) -> Result<(), AnyError> {
+ let transferables =
+ deserialize_js_transferables(state, data.transferables)?;
+
+ // Swallow the failed to send error. It means the channel was disentangled,
+ // but not cleaned up.
+ self.tx.send((data.data.to_vec(), transferables)).ok();
+
+ Ok(())
+ }
+
+ pub async fn recv(
+ &self,
+ state: Rc<RefCell<OpState>>,
+ ) -> Result<Option<JsMessageData>, AnyError> {
+ let mut rx = self
+ .rx
+ .try_borrow_mut()
+ .map_err(|_| type_error("Port receiver is already borrowed"))?;
+ if let Some((data, transferables)) = rx.recv().await {
+ let js_transferables =
+ serialize_transferables(&mut state.borrow_mut(), transferables);
+ return Ok(Some(JsMessageData {
+ data: ZeroCopyBuf::from(data),
+ transferables: js_transferables,
+ }));
+ }
+ Ok(None)
+ }
+}
+
+pub fn create_entangled_message_port() -> (MessagePort, MessagePort) {
+ let (port1_tx, port2_rx) = unbounded_channel::<MessagePortMessage>();
+ let (port2_tx, port1_rx) = unbounded_channel::<MessagePortMessage>();
+
+ let port1 = MessagePort {
+ rx: RefCell::new(port1_rx),
+ tx: port1_tx,
+ };
+
+ let port2 = MessagePort {
+ rx: RefCell::new(port2_rx),
+ tx: port2_tx,
+ };
+
+ (port1, port2)
+}
+
+pub struct MessagePortResource {
+ port: MessagePort,
+ cancel: CancelHandle,
+}
+
+impl Resource for MessagePortResource {
+ fn name(&self) -> Cow<str> {
+ "messagePort".into()
+ }
+
+ fn close(self: Rc<Self>) {
+ self.cancel.cancel();
+ }
+}
+
+pub fn op_message_port_create_entangled(
+ state: &mut OpState,
+ _: (),
+ _: (),
+) -> Result<(ResourceId, ResourceId), AnyError> {
+ let (port1, port2) = create_entangled_message_port();
+
+ let port1_id = state.resource_table.add(MessagePortResource {
+ port: port1,
+ cancel: CancelHandle::new(),
+ });
+
+ let port2_id = state.resource_table.add(MessagePortResource {
+ port: port2,
+ cancel: CancelHandle::new(),
+ });
+
+ Ok((port1_id, port2_id))
+}
+
+#[derive(Deserialize, Serialize)]
+#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
+pub enum JsTransferable {
+ #[serde(rename_all = "camelCase")]
+ MessagePort(ResourceId),
+}
+
+fn deserialize_js_transferables(
+ state: &mut OpState,
+ js_transferables: Vec<JsTransferable>,
+) -> Result<Vec<Transferable>, AnyError> {
+ let mut transferables = Vec::with_capacity(js_transferables.len());
+ for js_transferable in js_transferables {
+ match js_transferable {
+ JsTransferable::MessagePort(id) => {
+ let resource = state
+ .resource_table
+ .take::<MessagePortResource>(id)
+ .ok_or_else(|| type_error("Invalid message port transfer"))?;
+ resource.cancel.cancel();
+ let resource = Rc::try_unwrap(resource)
+ .map_err(|_| type_error("Message port is not ready for transfer"))?;
+ transferables.push(Transferable::MessagePort(resource.port));
+ }
+ }
+ }
+ Ok(transferables)
+}
+
+fn serialize_transferables(
+ state: &mut OpState,
+ transferables: Vec<Transferable>,
+) -> Vec<JsTransferable> {
+ let mut js_transferables = Vec::with_capacity(transferables.len());
+ for transferable in transferables {
+ match transferable {
+ Transferable::MessagePort(port) => {
+ let rid = state.resource_table.add(MessagePortResource {
+ port,
+ cancel: CancelHandle::new(),
+ });
+ js_transferables.push(JsTransferable::MessagePort(rid));
+ }
+ }
+ }
+ js_transferables
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct JsMessageData {
+ data: ZeroCopyBuf,
+ transferables: Vec<JsTransferable>,
+}
+
+pub fn op_message_port_post_message(
+ state: &mut OpState,
+ rid: ResourceId,
+ data: JsMessageData,
+) -> Result<(), AnyError> {
+ for js_transferable in &data.transferables {
+ match js_transferable {
+ JsTransferable::MessagePort(id) => {
+ if *id == rid {
+ return Err(type_error("Can not transfer self message port"));
+ }
+ }
+ }
+ }
+
+ let resource = state
+ .resource_table
+ .get::<MessagePortResource>(rid)
+ .ok_or_else(bad_resource_id)?;
+
+ resource.port.send(state, data)
+}
+
+pub async fn op_message_port_recv_message(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ _: (),
+) -> Result<Option<JsMessageData>, AnyError> {
+ let resource = {
+ let state = state.borrow();
+ match state.resource_table.get::<MessagePortResource>(rid) {
+ Some(resource) => resource,
+ None => return Ok(None),
+ }
+ };
+ let cancel = RcRef::map(resource.clone(), |r| &r.cancel);
+ resource.port.recv(state.clone()).or_cancel(cancel).await?
+}
diff --git a/extensions/webidl/00_webidl.js b/extensions/webidl/00_webidl.js
index 87e9eccb7..3e6dc95eb 100644
--- a/extensions/webidl/00_webidl.js
+++ b/extensions/webidl/00_webidl.js
@@ -564,7 +564,10 @@
converters.USVString,
);
converters["sequence<double>"] = createSequenceConverter(
- converters["double"],
+ converters.double,
+ );
+ converters["sequence<object>"] = createSequenceConverter(
+ converters.object,
);
converters["Promise<undefined>"] = createPromiseConverter(() => undefined);
@@ -630,6 +633,7 @@
get() {
return member.defaultValue;
},
+ enumerable: true,
});
}
}
diff --git a/extensions/webidl/internal.d.ts b/extensions/webidl/internal.d.ts
index 4d0f1ad45..9d151a6d1 100644
--- a/extensions/webidl/internal.d.ts
+++ b/extensions/webidl/internal.d.ts
@@ -321,6 +321,21 @@ declare namespace globalThis {
* Configure prototype properties enumerability / writability / configurability.
*/
declare function configurePrototype(prototype: any);
+
+ /**
+ * Get the WebIDL / ES type of a value.
+ */
+ declare function type(
+ v: any,
+ ):
+ | "Null"
+ | "Undefined"
+ | "Boolean"
+ | "Number"
+ | "String"
+ | "Symbol"
+ | "BigInt"
+ | "Object";
}
}
}
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 633750675..09a2ebb55 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -36,6 +36,7 @@ delete Object.prototype.__proto__;
const formData = window.__bootstrap.formData;
const fetch = window.__bootstrap.fetch;
const prompt = window.__bootstrap.prompt;
+ const messagePort = window.__bootstrap.messagePort;
const denoNs = window.__bootstrap.denoNs;
const denoNsUnstable = window.__bootstrap.denoNsUnstable;
const errors = window.__bootstrap.errors.errors;
@@ -299,6 +300,8 @@ delete Object.prototype.__proto__;
URLSearchParams: util.nonEnumerable(url.URLSearchParams),
WebSocket: util.nonEnumerable(webSocket.WebSocket),
BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel),
+ MessageChannel: util.nonEnumerable(messagePort.MessageChannel),
+ MessagePort: util.nonEnumerable(messagePort.MessagePort),
Worker: util.nonEnumerable(worker.Worker),
WritableStream: util.nonEnumerable(streams.WritableStream),
WritableStreamDefaultWriter: util.nonEnumerable(
diff --git a/test_util/wpt b/test_util/wpt
-Subproject a8e5772a0f1c4d666acc5aee2423c38da7c9a71
+Subproject 02ebfda73367c0419e19f83048fa5895a78cb41
diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json
index 962c12de6..3f30e9ca9 100644
--- a/tools/wpt/expectation.json
+++ b/tools/wpt/expectation.json
@@ -234,7 +234,9 @@
},
"hr-time": {
"monotonic-clock.any.html": true,
- "basic.any.html": ["Performance interface extends EventTarget."],
+ "basic.any.html": [
+ "Performance interface extends EventTarget."
+ ],
"idlharness.any.html": [
"Performance interface: existence and properties of interface object",
"Performance interface: existence and properties of interface prototype object",
@@ -587,6 +589,8 @@
],
"idlharness.any.html": true,
"url-constructor.any.html": [
+ "Parsing: <foo://ho|st/> against <about:blank>",
+ "Parsing: <http://ho%7Cst/> against <about:blank>",
"Parsing: <file://%43%7C> against <about:blank>",
"Parsing: <file://%43|> against <about:blank>",
"Parsing: <file://C%7C> against <about:blank>",
@@ -898,9 +902,7 @@
"FileAPI": {
"blob": {
"Blob-array-buffer.any.html": true,
- "Blob-constructor.any.html": [
- "Passing a FrozenArray as the blobParts array should work (FrozenArray<MessagePort>)."
- ],
+ "Blob-constructor.any.html": true,
"Blob-slice-overflow.any.html": true,
"Blob-slice.any.html": true,
"Blob-stream.any.html": true,
@@ -1010,7 +1012,33 @@
"postMessage results in correct event"
],
"interface.any.html": true
- }
+ },
+ "message-channels": {
+ "basics.any.html": true,
+ "close.any.html": true,
+ "dictionary-transferrable.any.html": false,
+ "implied-start.any.html": true,
+ "no-start.any.html": true,
+ "user-activation.tentative.any.html": false,
+ "worker-post-after-close.any.html": false,
+ "worker.any.html": false
+ },
+ "Channel_postMessage_Blob.any.html": false,
+ "Channel_postMessage_DataCloneErr.any.html": true,
+ "Channel_postMessage_clone_port.any.html": true,
+ "Channel_postMessage_clone_port_error.any.html": true,
+ "Channel_postMessage_event_properties.any.html": true,
+ "Channel_postMessage_ports_readonly_array.any.html": false,
+ "Channel_postMessage_target_source.any.html": true,
+ "Channel_postMessage_transfer_xsite_incoming_messages.window.html": false,
+ "Channel_postMessage_with_transfer_entangled.any.html": true,
+ "Channel_postMessage_with_transfer_incoming_messages.any.html": true,
+ "Channel_postMessage_with_transfer_outgoing_messages.any.html": true,
+ "MessageEvent-trusted.any.html": false,
+ "MessageEvent-trusted.window.html": false,
+ "MessageEvent.any.html": true,
+ "MessagePort_initial_disabled.any.html": true,
+ "MessagePort_onmessage_start.any.html": true
},
"xhr": {
"formdata": {
@@ -1209,4 +1237,4 @@
"eventhandlers.any.html?wss": false,
"referrer.any.html": true
}
-}
+} \ No newline at end of file
diff --git a/tools/wpt/runner.ts b/tools/wpt/runner.ts
index 3eb476fc9..3886d34d5 100644
--- a/tools/wpt/runner.ts
+++ b/tools/wpt/runner.ts
@@ -122,7 +122,7 @@ export async function runSingleTest(
harnessStatus = JSON.parse(line.slice(5));
} else {
stderr += line + "\n";
- console.error(stderr);
+ console.error(line);
}
}