script: Allow to throw a custom exception on structured cloning (#37948)

The structured cloning with transfer list
https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer
throws a "DataCloneError" DOM expection by default if
serialization/transferral
is not possible, but a platform object can throw a custom excepton on
its serialization/transfer steps.

One example is OffscreenCanvas, which can throw
an "InvalidStateError" exception if the context mode is not none on
transfer steps.

https://html.spec.whatwg.org/multipage/#the-offscreencanvas-interface:transfer-steps

Testing: Improvements in the following tests
-
html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable*

Fixes: #37919

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-07-09 14:43:09 +03:00 committed by GitHub
parent cae73341b2
commit a5b02047f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 165 additions and 130 deletions

View file

@ -1222,11 +1222,12 @@ impl Transferable for WritableStream {
type Index = MessagePortIndex;
type Data = MessagePortImpl;
/// <https://streams.spec.whatwg.org/#ref-for-writablestream%E2%91%A0%E2%91%A4>
fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> {
// If ! IsWritableStreamLocked(value) is true, throw a "DataCloneError" DOMException.
/// <https://streams.spec.whatwg.org/#ref-for-transfer-steps①>
fn transfer(&self) -> Fallible<(MessagePortId, MessagePortImpl)> {
// Step 1. If ! IsWritableStreamLocked(value) is true, throw a
// "DataCloneError" DOMException.
if self.is_locked() {
return Err(());
return Err(Error::DataClone(None));
}
let global = self.global();
@ -1235,34 +1236,34 @@ impl Transferable for WritableStream {
let cx = GlobalScope::get_cx();
let can_gc = CanGc::note();
// Let port1 be a new MessagePort in the current Realm.
// Step 2. Let port1 be a new MessagePort in the current Realm.
let port_1 = MessagePort::new(&global, can_gc);
global.track_message_port(&port_1, None);
// Let port2 be a new MessagePort in the current Realm.
// Step 3. Let port2 be a new MessagePort in the current Realm.
let port_2 = MessagePort::new(&global, can_gc);
global.track_message_port(&port_2, None);
// Entangle port1 and port2.
// Step 4. Entangle port1 and port2.
global.entangle_ports(*port_1.message_port_id(), *port_2.message_port_id());
// Let readable be a new ReadableStream in the current Realm.
// Step 5. Let readable be a new ReadableStream in the current Realm.
let readable = ReadableStream::new_with_proto(&global, None, can_gc);
// Perform ! SetUpCrossRealmTransformReadable(readable, port1).
// Step 6. Perform ! SetUpCrossRealmTransformReadable(readable, port1).
readable.setup_cross_realm_transform_readable(cx, &port_1, can_gc);
// Let promise be ! ReadableStreamPipeTo(readable, value, false, false, false).
// Step 7. Let promise be ! ReadableStreamPipeTo(readable, value, false, false, false).
let promise = readable.pipe_to(cx, &global, self, false, false, false, None, comp, can_gc);
// Set promise.[[PromiseIsHandled]] to true.
// Step 8. Set promise.[[PromiseIsHandled]] to true.
promise.set_promise_is_handled();
// Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
// Step 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
port_2.transfer()
}
/// <https://streams.spec.whatwg.org/#ref-for-writablestream%E2%91%A0%E2%91%A4>
/// <https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①>
fn transfer_receive(
owner: &GlobalScope,
id: MessagePortId,
@ -1275,13 +1276,15 @@ impl Transferable for WritableStream {
// Note: dataHolder is used in `structuredclone.rs`, and value is created here.
let value = WritableStream::new_with_proto(owner, None, can_gc);
// Let deserializedRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm).
// Step 1. Let deserializedRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
// Realm).
// Done with the `Deserialize` derive of `MessagePortImpl`.
// Let port be deserializedRecord.[[Deserialized]].
// Step 2. Let port be deserializedRecord.[[Deserialized]].
let transferred_port = MessagePort::transfer_receive(owner, id, port_impl)?;
// Perform ! SetUpCrossRealmTransformWritable(value, port).
// Step 3. Perform ! SetUpCrossRealmTransformWritable(value, port).
value.setup_cross_realm_transform_writable(cx, &transferred_port, can_gc);
Ok(value)
}