diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/console/02_console.js | 21 | ||||
-rw-r--r-- | ext/web/13_message_port.js | 73 | ||||
-rw-r--r-- | ext/web/internal.d.ts | 3 | ||||
-rw-r--r-- | ext/web/message_port.rs | 16 |
4 files changed, 99 insertions, 14 deletions
diff --git a/ext/console/02_console.js b/ext/console/02_console.js index 178b3955b..455df4570 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -362,6 +362,7 @@ const entries = []; let iter; + let valueIsTypedArray = false; switch (options.typeName) { case "Map": @@ -376,6 +377,7 @@ default: if (isTypedArray(value)) { iter = ArrayPrototypeEntries(value); + valueIsTypedArray = true; } else { throw new TypeError("unreachable"); } @@ -385,7 +387,24 @@ const next = () => { return iter.next(); }; - for (const el of iter) { + while (true) { + let el; + try { + const res = iter.next(); + if (res.done) { + break; + } + el = res.value; + } catch (err) { + if (valueIsTypedArray) { + // TypedArray.prototype.entries doesn't throw, unless the ArrayBuffer + // is detached. We don't want to show the exception in that case, so + // we catch it here and pretend the ArrayBuffer has no entries (like + // Chrome DevTools does). + break; + } + throw err; + } if (entriesLength < inspectOptions.iterableLimit) { ArrayPrototypePush( entries, diff --git a/ext/web/13_message_port.js b/ext/web/13_message_port.js index d0d9e160e..2768c0a92 100644 --- a/ext/web/13_message_port.js +++ b/ext/web/13_message_port.js @@ -15,6 +15,8 @@ const { defineEventHandler } = window.__bootstrap.event; const { DOMException } = window.__bootstrap.domException; const { + ArrayBuffer, + ArrayPrototypeFilter, ArrayPrototypeIncludes, ArrayPrototypePush, ObjectSetPrototypeOf, @@ -22,6 +24,9 @@ SymbolFor, SymbolToStringTag, TypeError, + WeakSet, + WeakSetPrototypeAdd, + WeakSetPrototypeHas, } = window.__bootstrap.primordials; class MessageChannel { @@ -139,11 +144,11 @@ this[_id], ); if (data === null) break; - let message, transfer; + let message, transferables; try { const v = deserializeJsMessageData(data); message = v[0]; - transfer = v[1]; + transferables = v[1]; } catch (err) { const event = new MessageEvent("messageerror", { data: err }); this.dispatchEvent(event); @@ -151,7 +156,10 @@ } const event = new MessageEvent("message", { data: message, - ports: transfer, + ports: ArrayPrototypeFilter( + transferables, + (t) => t instanceof MessagePort, + ), }); this.dispatchEvent(event); } @@ -193,12 +201,22 @@ function deserializeJsMessageData(messageData) { /** @type {object[]} */ const transferables = []; + const hostObjects = []; + const arrayBufferIdsInTransferables = []; + const transferedArrayBuffers = []; for (const transferable of messageData.transferables) { switch (transferable.kind) { case "messagePort": { const port = createMessagePort(transferable.data); ArrayPrototypePush(transferables, port); + ArrayPrototypePush(hostObjects, port); + break; + } + case "arrayBuffer": { + ArrayPrototypePush(transferedArrayBuffers, transferable.data); + const i = ArrayPrototypePush(transferables, null); + ArrayPrototypePush(arrayBufferIdsInTransferables, i); break; } default: @@ -207,21 +225,53 @@ } const data = core.deserialize(messageData.data, { - hostObjects: transferables, + hostObjects, + transferedArrayBuffers, }); + for (const i in arrayBufferIdsInTransferables) { + const id = arrayBufferIdsInTransferables[i]; + transferables[id] = transferedArrayBuffers[i]; + } + return [data, transferables]; } + const detachedArrayBuffers = new WeakSet(); + /** * @param {any} data - * @param {object[]} tranferables + * @param {object[]} transferables * @returns {globalThis.__bootstrap.messagePort.MessageData} */ - function serializeJsMessageData(data, tranferables) { + function serializeJsMessageData(data, transferables) { + const transferedArrayBuffers = ArrayPrototypeFilter( + transferables, + (a) => a instanceof ArrayBuffer, + ); + + for (const arrayBuffer of transferedArrayBuffers) { + // This is hacky with both false positives and false negatives for + // detecting detached array buffers. V8 needs to add a way to tell if a + // buffer is detached or not. + if (WeakSetPrototypeHas(detachedArrayBuffers, arrayBuffer)) { + throw new DOMException( + "Can not transfer detached ArrayBuffer", + "DataCloneError", + ); + } + WeakSetPrototypeAdd(detachedArrayBuffers, arrayBuffer); + } + let serializedData; try { - serializedData = core.serialize(data, { hostObjects: tranferables }); + serializedData = core.serialize(data, { + hostObjects: ArrayPrototypeFilter( + transferables, + (a) => a instanceof MessagePort, + ), + transferedArrayBuffers, + }); } catch (err) { throw new DOMException(err.message, "DataCloneError"); } @@ -229,7 +279,8 @@ /** @type {globalThis.__bootstrap.messagePort.Transferable[]} */ const serializedTransferables = []; - for (const transferable of tranferables) { + let arrayBufferI = 0; + for (const transferable of transferables) { if (transferable instanceof MessagePort) { webidl.assertBranded(transferable, MessagePort); const id = transferable[_id]; @@ -244,6 +295,12 @@ kind: "messagePort", data: id, }); + } else if (transferable instanceof ArrayBuffer) { + ArrayPrototypePush(serializedTransferables, { + kind: "arrayBuffer", + data: transferedArrayBuffers[arrayBufferI], + }); + arrayBufferI++; } else { throw new DOMException("Value not transferable", "DataCloneError"); } diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index 3a2a0c1be..6e72c1a80 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -88,6 +88,9 @@ declare namespace globalThis { declare type Transferable = { kind: "messagePort"; data: number; + } | { + kind: "arrayBuffer"; + data: number; }; declare interface MessageData { data: Uint8Array; diff --git a/ext/web/message_port.rs b/ext/web/message_port.rs index 4af99d501..d8d8e5907 100644 --- a/ext/web/message_port.rs +++ b/ext/web/message_port.rs @@ -16,6 +16,7 @@ use tokio::sync::mpsc::UnboundedSender; enum Transferable { MessagePort(MessagePort), + ArrayBuffer(u32), } type MessagePortMessage = (Vec<u8>, Vec<Transferable>); @@ -127,6 +128,7 @@ pub fn op_message_port_create_entangled( pub enum JsTransferable { #[serde(rename_all = "camelCase")] MessagePort(ResourceId), + ArrayBuffer(u32), } fn deserialize_js_transferables( @@ -146,6 +148,9 @@ fn deserialize_js_transferables( .map_err(|_| type_error("Message port is not ready for transfer"))?; transferables.push(Transferable::MessagePort(resource.port)); } + JsTransferable::ArrayBuffer(id) => { + transferables.push(Transferable::ArrayBuffer(id)); + } } } Ok(transferables) @@ -165,6 +170,9 @@ fn serialize_transferables( }); js_transferables.push(JsTransferable::MessagePort(rid)); } + Transferable::ArrayBuffer(id) => { + js_transferables.push(JsTransferable::ArrayBuffer(id)); + } } } js_transferables @@ -182,11 +190,9 @@ pub fn op_message_port_post_message( 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")); - } + if let JsTransferable::MessagePort(id) = js_transferable { + if *id == rid { + return Err(type_error("Can not transfer self message port")); } } } |