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

feat: MessageChannel and MessagePort (#11051)

This commit introduces support for MessageChannel and MessagePort.
MessagePorts can be transfered across other MessagePorts.
This commit is contained in:
Luca Casonato 2021-06-21 19:53:52 +02:00 committed by GitHub
parent a2f939b99c
commit f9ff981daf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 720 additions and 72 deletions

1
Cargo.lock generated
View file

@ -781,6 +781,7 @@ dependencies = [
"encoding_rs",
"futures",
"serde",
"tokio",
"uuid",
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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[];
}
}
}
}

View file

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

View file

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

View file

@ -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?
}

View file

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

View file

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

View file

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

@ -1 +1 @@
Subproject commit a8e5772a0f1c4d666acc5aee2423c38da7c9a71c
Subproject commit 02ebfda73367c0419e19f83048fa5895a78cb418

View file

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

View file

@ -122,7 +122,7 @@ export async function runSingleTest(
harnessStatus = JSON.parse(line.slice(5));
} else {
stderr += line + "\n";
console.error(stderr);
console.error(line);
}
}