Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MessagePort, MessageChannel and StructuredClone implementation #3521

Draft
wants to merge 45 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1ff3448
Added isDetachedBuffer utility
alexandercerutti Mar 3, 2023
1eacf13
Added util isArrayBufferResizable
alexandercerutti Mar 3, 2023
0cfad34
Added incomplete implementation of MessagePort-impl
alexandercerutti Mar 3, 2023
b8abe28
Improved comments
alexandercerutti Mar 8, 2023
deacc4f
Assigned deserialized to messageClone
alexandercerutti Mar 8, 2023
e9ee6a5
Added filter on newPorts
alexandercerutti Mar 8, 2023
ab0fda0
Added entangle method on MessagePort
alexandercerutti Mar 8, 2023
9958b67
Added disentangle static method and removed remotePort as MessagePort…
alexandercerutti Mar 8, 2023
96f8e08
Added enabled getter and enqueue method in PortMessageQueue and messa…
alexandercerutti Mar 8, 2023
ea9b636
Removed _detached flag
alexandercerutti Mar 8, 2023
437247d
Added extends of EventTarget
alexandercerutti Mar 8, 2023
f92c61c
Added MessageChannel implementation
alexandercerutti Mar 8, 2023
115913e
Added initial StructuredClone-impl
alexandercerutti Apr 5, 2023
9051f26
Moved structuredSerializeInternal and structuredSerializeWithTransfer…
alexandercerutti Apr 5, 2023
955d07d
Moved structuredDeserializeWithTransfer to a different file
alexandercerutti Apr 5, 2023
a60a394
Added implementation for structuredClone
alexandercerutti Apr 5, 2023
0b01359
Added structuredClone and MessageChannel to Window
alexandercerutti Apr 5, 2023
5fbea25
Fixed EventTargetImpl import
alexandercerutti Apr 5, 2023
1edd652
Added some fallbacks and fixed imports and references
alexandercerutti Apr 5, 2023
1b61cd7
Fixed detached ArrayBuffer checks, serialization and deserialization
alexandercerutti Apr 5, 2023
33045fe
Fixed missing DOMException require
alexandercerutti Apr 5, 2023
55c0d0a
Added globalObject to structuredSerializeWithTransfer and Internal
alexandercerutti Apr 5, 2023
896be8a
Fixed checks for interfaceName while destructuring and added more ser…
alexandercerutti Apr 5, 2023
61fb6e8
Added structuredClone tests in to-run file
alexandercerutti Apr 5, 2023
4f1bb1f
Changed object creation to use Object.create
alexandercerutti Apr 6, 2023
e4caab2
Set structuredClone to create once structuredSerializeWithTransfer wi…
alexandercerutti Apr 6, 2023
18a1c5d
Changed properties to be camelCase
alexandercerutti Apr 6, 2023
db7e483
Changed module.exports for structured functions
alexandercerutti Apr 6, 2023
c17d6ae
Converted isDetachedBuffer to isArrayBufferDetached
alexandercerutti Apr 6, 2023
77e8364
Fixed forgotten camelCase properties
alexandercerutti Apr 6, 2023
b54a6f6
Fixed more issues about property accessing and checks
alexandercerutti Apr 6, 2023
37c79b2
Converted structuredSerialization memories to be a Map and converted …
alexandercerutti Apr 13, 2023
9787154
Fixed values reconstruction by using targetRealm
alexandercerutti Apr 15, 2023
087f87e
Fixed memory saving for big ints in structured serialization
alexandercerutti Apr 15, 2023
2ea4ff6
Fixed instanceof checking and deserializing
alexandercerutti Apr 15, 2023
3d716f9
Fixed Errors deserialization prototype and a check on targetRealm exp…
alexandercerutti Apr 15, 2023
d220110
Fixed number primitive serialization
alexandercerutti Apr 15, 2023
a2204a0
Fixed issues with MessagePort and MessageChannel impls
alexandercerutti Apr 15, 2023
4fda62f
Fixed Errors serialization and deserialization
alexandercerutti Apr 15, 2023
1f0f86d
Fixed buffers and uint8array data settings and big int building
alexandercerutti Apr 15, 2023
8c1c04f
Fixed RegExp typo for Type
alexandercerutti Apr 15, 2023
c3790ca
Set usage of valueOf on primitive objects
alexandercerutti Apr 15, 2023
4912bb4
Fixed Object deserialization
alexandercerutti Apr 15, 2023
a9d51ed
Fixed BigInt deserialization
alexandercerutti Apr 15, 2023
ca64e66
Fixed transferable map accessing and some globalObject usages
alexandercerutti Apr 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/jsdom/browser/Window.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ const OnErrorEventHandlerNonNull = require("../living/generated/OnErrorEventHand
const { fireAPageTransitionEvent } = require("../living/helpers/page-transition-event");
const namedPropertiesWindow = require("../living/named-properties-window");
const postMessage = require("../living/post-message");
const structuredClone = require("../living/structured-clone/structuredClone");
const DOMException = require("domexception/webidl2js-wrapper");
const { btoa, atob } = require("abab");
const idlUtils = require("../living/generated/utils");
const WebSocketImpl = require("../living/websockets/WebSocket-impl").implementation;
const MessageChannel = require("../living/message-channel/MessageChannel-impl").implementation;
const BarProp = require("../living/generated/BarProp");
const documents = require("../living/documents.js");
const External = require("../living/generated/External");
Expand Down Expand Up @@ -755,6 +757,8 @@ function Window(options) {
});

this.postMessage = postMessage(window);
this.structuredClone = structuredClone(window);
this.MessageChannel = MessageChannel;

this.atob = function (str) {
const result = atob(str);
Expand Down
26 changes: 26 additions & 0 deletions lib/jsdom/living/message-channel/MessageChannel-impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use strict";

const MessagePortImpl = require("../message-port/MessagePort-impl");

class MessageChannelImpl {
constructor(globalObject) {
this._ports = [
new MessagePortImpl(globalObject),
new MessagePortImpl(globalObject)
];

MessagePortImpl.entangle(...this._ports);
}

get port1() {
return this._ports[0];
}

get port2() {
return this._ports[1];
}
}

module.exports = {
implementation: MessageChannelImpl
};
7 changes: 7 additions & 0 deletions lib/jsdom/living/message-channel/MessageChannel.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[Exposed=(Window,Worker)]
interface MessageChannel {
constructor();

readonly attribute MessagePort port1;
readonly attribute MessagePort port2;
};
176 changes: 176 additions & 0 deletions lib/jsdom/living/message-port/MessagePort-impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"use strict";

const DOMException = require("domexception/webidl2js-wrapper");
const MessageEvent = require("../generated/MessageEvent");
const { fireAnEvent } = require("../helpers/events");
const EventTargetImpl = require("../events/EventTarget-impl").implementation;
const { structuredSerializeWithTransfer } = require("../structured-clone/structuredSerializeWithTransfer");
const { structuredDeserializeWithTransfer } = require("../structured-clone/structuredDeserializeWithTransfer");

class PortMessageQueue {
constructor() {
this._enabled = false;

/**
* @type {[*, object]} queue
*/

this.queue = [];
}

get enabled() {
return this._enabled;
}

enable() {
if (this._enabled) {
return;
}

this._enabled = true;
}

enqueue(message, options) {
this.queue.push([message, options]);
}
}


class MessagePortImpl extends EventTargetImpl {
constructor(globalObject) {
super(globalObject);

this._globalObject = globalObject;
this._onmessage = undefined;
this._onmessageerror = undefined;
this._dataHolder = {
_remotePort: null,
_portMessageQueue: new PortMessageQueue()
};
}

/**
* @param {MessagePortImpl} port1
* @param {MessagePortImpl} port2
*/

static entangle(port1, port2) {
port1._dataHolder._remotePort = port2;
port2._dataHolder._remotePort = port1;
}

/**
* @param {MessagePortImpl} port1
* @param {MessagePortImpl} port2
*/

static disentangle(port1, port2) {
port2._dataHolder._remotePort = null;
port1._dataHolder._remotePort = null;
}

get onmessage() {
return this._onmessage;
}

set onmessage(value) {
this._onmessage = value;
this.start();
}

postMessage(message, optionsOrTransfer) {
const options = {};

if (Array.isArray(optionsOrTransfer)) {
options.transfer = optionsOrTransfer;
} else if (typeof optionsOrTransfer === "object") {
Object.assign(options, optionsOrTransfer);
}

const { transfer } = options;

if (transfer.some(t => t === this)) {
throw DOMException.create(this._globalObject, [
"The object can not be cloned.",
"DataCloneError"
]);
}

let doomed = false;
const targetPort = this._dataHolder._remotePort;

if (targetPort !== null && transfer.includes(targetPort)) {
doomed = true;
}

const serializeWithTransferResult = structuredSerializeWithTransfer(this._globalObject)(message, transfer);

if (!targetPort || doomed) {
// eslint-disable-next-line no-console
console.warn("Target port was posted to itself, causing the communication channel to be lost.");
return;
}

/**
* Out of standard, but we don't have the event loop
*/

if (!this._dataHolder._portMessageQueue.enabled) {
this._dataHolder._portMessageQueue.enqueue(message, options);
return;
}

queueMicrotask(() => {
const finalTargetPort = targetPort;
/**
* Let deserializeRecord be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm).
* If this throws an exception, catch it, fire an event named messageerror at finalTargetPort, using
* MessageEvent, and then return.
*/

let deserializeRecord;

try {
deserializeRecord = structuredDeserializeWithTransfer(serializeWithTransferResult, this._globalObject);
} catch (err) {
fireAnEvent("messageerror", finalTargetPort, MessageEvent, { data: err });
}

/**
* Let messageClone be deserializeRecord.[[Deserialized]].
*/

const messageClone = deserializeRecord.Deserialized;

/**
* Let newPorts be a new frozen array consisting of all MessagePort objects in
* deserializeRecord.[[TransferredValues]], if any, maintaining their relative order.
*/

const newPorts = Object.freeze(
deserializeRecord.TransferredValues.filter(value => value instanceof MessagePortImpl)
);

fireAnEvent("message", finalTargetPort, MessageEvent, { data: messageClone, ports: newPorts });
});
}

start() {
this._dataHolder._portMessageQueue.enable();

let event;

while ((event = this._dataHolder._portMessageQueue.queue.shift())) {
const [message, options] = event;
this.postMessage(message, options);
}
}

close() {
MessagePortImpl.disentangle(this, this._dataHolder._remotePort);
}
}

module.exports = {
implementation: MessagePortImpl
};
15 changes: 15 additions & 0 deletions lib/jsdom/living/message-port/MessagePort.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Exposed=(Window,Worker,AudioWorklet), Transferable]
interface MessagePort : EventTarget {
undefined postMessage(any message, sequence<object> transfer);
undefined postMessage(any message, optional StructuredSerializeOptions options = {});
undefined start();
undefined close();

// event handlers
attribute EventHandler onmessage;
attribute EventHandler onmessageerror;
};

dictionary StructuredSerializeOptions {
sequence<object> transfer = [];
};
1 change: 0 additions & 1 deletion lib/jsdom/living/post-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ module.exports = function (globalObject) {

// TODO: event.source - requires reference to source window
// TODO: event.origin - requires reference to source window
// TODO: event.ports
// TODO: event.data - structured clone message - requires cloning DOM nodes
setTimeout(() => {
fireAnEvent("message", this, MessageEvent, { data: message });
Expand Down
17 changes: 17 additions & 0 deletions lib/jsdom/living/structured-clone/structuredClone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";

const { structuredDeserializeWithTransfer } = require("./structuredDeserializeWithTransfer");
const { structuredSerializeWithTransfer } = require("./structuredSerializeWithTransfer");

module.exports = function (globalObject) {
return function (value, options = {}) {
// Let serialized be ? StructuredSerializeWithTransfer(value, options["transfer"]).
const serialized = structuredSerializeWithTransfer(globalObject)(value, options.transfer);
alexandercerutti marked this conversation as resolved.
Show resolved Hide resolved

// Let deserializeRecord be ? StructuredDeserializeWithTransfer(serialized, this's relevant realm).
const deserializeRecord = structuredDeserializeWithTransfer(serialized, globalObject);

// Return deserializeRecord.[[Deserialized]].
return deserializeRecord.Deserialized;
};
};