From a5b02047f91d6d95488f19c11353823205d98dbd Mon Sep 17 00:00:00 2001 From: Andrei Volykhin Date: Wed, 9 Jul 2025 14:43:09 +0300 Subject: [PATCH] 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 --- .../script/dom/bindings/structuredclone.rs | 97 ++++++++++--------- .../script/dom/bindings/transferable.rs | 7 +- components/script/dom/imagebitmap.rs | 48 +++++---- components/script/dom/messageport.rs | 10 +- components/script/dom/offscreencanvas.rs | 18 +++- components/script/dom/readablestream.rs | 35 ++++--- components/script/dom/transformstream.rs | 39 +++++--- components/script/dom/writablestream.rs | 35 ++++--- .../offscreencanvas.transferrable.html.ini | 3 - .../offscreencanvas.transferrable.w.html.ini | 3 - 10 files changed, 165 insertions(+), 130 deletions(-) delete mode 100644 tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index fc8edb4e334..7ee1dccd170 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -216,7 +216,10 @@ unsafe extern "C" fn read_callback( ptr::null_mut() } -struct InterfaceDoesNotMatch; +enum OperationError { + InterfaceDoesNotMatch, + Exception(Error), +} unsafe fn try_serialize( val: SerializableInterface, @@ -225,11 +228,11 @@ unsafe fn try_serialize( global: &GlobalScope, w: *mut JSStructuredCloneWriter, writer: &mut StructuredDataWriter, -) -> Result { +) -> Result { if let Ok(obj) = root_from_object::(*obj, cx) { return Ok(write_object(val, global, &*obj, w, writer)); } - Err(InterfaceDoesNotMatch) + Err(OperationError::InterfaceDoesNotMatch) } type SerializeOperation = unsafe fn( @@ -239,7 +242,7 @@ type SerializeOperation = unsafe fn( &GlobalScope, *mut JSStructuredCloneWriter, &mut StructuredDataWriter, -) -> Result; +) -> Result; fn serialize_for_type(val: SerializableInterface) -> SerializeOperation { match val { @@ -363,32 +366,34 @@ unsafe fn try_transfer( tag: *mut u32, ownership: *mut TransferableOwnership, extra_data: *mut u64, -) -> Result<(), ()> { - if let Ok(object) = root_from_object::(*obj, cx) { - *tag = StructuredCloneTags::from(interface) as u32; - *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM; - if let Ok((id, object)) = object.transfer() { - // 2. Store the transferred object at a given key. - let objects = T::serialized_storage(StructuredData::Writer(sc_writer)) - .get_or_insert_with(HashMap::new); - objects.insert(id, object); +) -> Result<(), OperationError> { + let Ok(object) = root_from_object::(*obj, cx) else { + return Err(OperationError::InterfaceDoesNotMatch); + }; - let index = id.index.0.get(); + *tag = StructuredCloneTags::from(interface) as u32; + *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM; - let mut big: [u8; 8] = [0; 8]; - let name_space = id.namespace_id.0.to_ne_bytes(); - let index = index.to_ne_bytes(); + let (id, object) = object.transfer().map_err(OperationError::Exception)?; - let (left, right) = big.split_at_mut(4); - left.copy_from_slice(&name_space); - right.copy_from_slice(&index); + // 2. Store the transferred object at a given key. + let objects = + T::serialized_storage(StructuredData::Writer(sc_writer)).get_or_insert_with(HashMap::new); + objects.insert(id, object); - // 3. Return a u64 representation of the key where the object is stored. - *extra_data = u64::from_ne_bytes(big); - return Ok(()); - } - } - Err(()) + let index = id.index.0.get(); + + let mut big: [u8; 8] = [0; 8]; + let name_space = id.namespace_id.0.to_ne_bytes(); + let index = index.to_ne_bytes(); + + let (left, right) = big.split_at_mut(4); + left.copy_from_slice(&name_space); + right.copy_from_slice(&index); + + // 3. Return a u64 representation of the key where the object is stored. + *extra_data = u64::from_ne_bytes(big); + Ok(()) } type TransferOperation = unsafe fn( @@ -399,7 +404,7 @@ type TransferOperation = unsafe fn( *mut u32, *mut TransferableOwnership, *mut u64, -) -> Result<(), ()>; +) -> Result<(), OperationError>; fn transfer_for_type(val: TransferrableInterface) -> TransferOperation { match val { @@ -425,8 +430,16 @@ unsafe extern "C" fn write_transfer_callback( let sc_writer = &mut *(closure as *mut StructuredDataWriter); for transferable in TransferrableInterface::iter() { let try_transfer = transfer_for_type(transferable); - if try_transfer(transferable, obj, cx, sc_writer, tag, ownership, extra_data).is_ok() { - return true; + + match try_transfer(transferable, obj, cx, sc_writer, tag, ownership, extra_data) { + Err(error) => match error { + OperationError::InterfaceDoesNotMatch => {}, + OperationError::Exception(error) => { + sc_writer.error = Some(error); + return false; + }, + }, + Ok(..) => return true, } } @@ -486,9 +499,11 @@ unsafe extern "C" fn report_error_callback( let msg_result = unsafe { CStr::from_ptr(error_message).to_str().map(str::to_string) }; if let Ok(msg) = msg_result { - let dom_error_record = &mut *(closure as *mut DOMErrorRecord); + let error = &mut *(closure as *mut Option); - dom_error_record.message = Some(msg) + if error.is_none() { + *error = Some(Error::DataClone(Some(msg))); + } } } @@ -516,18 +531,12 @@ pub(crate) enum StructuredData<'a, 'b> { Writer(&'a mut StructuredDataWriter), } -#[derive(Default)] -#[repr(C)] -pub(crate) struct DOMErrorRecord { - pub(crate) message: Option, -} - /// Reader and writer structs for results from, and inputs to, structured-data read/write operations. /// #[repr(C)] pub(crate) struct StructuredDataReader<'a> { - /// A struct of error message. - errors: DOMErrorRecord, + /// A error record. + error: Option, /// Rooted copies of every deserialized object to ensure they are not garbage collected. roots: RootedVec<'a, Box>>, /// A map of port implementations, @@ -556,8 +565,8 @@ pub(crate) struct StructuredDataReader<'a> { #[derive(Default)] #[repr(C)] pub(crate) struct StructuredDataWriter { - /// Error message. - pub(crate) errors: DOMErrorRecord, + /// Error record. + pub(crate) error: Option, /// Transferred ports. pub(crate) ports: Option>, /// Transferred transform streams. @@ -611,7 +620,7 @@ pub(crate) fn write( ); if !result { JS_ClearPendingException(*cx); - return Err(Error::DataClone(sc_writer.errors.message)); + return Err(sc_writer.error.unwrap_or(Error::DataClone(None))); } let nbytes = GetLengthOfJSStructuredCloneData(scdata); @@ -648,13 +657,13 @@ pub(crate) fn read( let _ac = enter_realm(global); rooted_vec!(let mut roots); let mut sc_reader = StructuredDataReader { + error: None, roots, port_impls: data.ports.take(), transform_streams_port_impls: data.transform_streams.take(), blob_impls: data.blobs.take(), points: data.points.take(), exceptions: data.exceptions.take(), - errors: DOMErrorRecord { message: None }, image_bitmaps: data.image_bitmaps.take(), transferred_image_bitmaps: data.transferred_image_bitmaps.take(), offscreen_canvases: data.offscreen_canvases.take(), @@ -688,7 +697,7 @@ pub(crate) fn read( ); if !result { JS_ClearPendingException(*cx); - return Err(Error::DataClone(sc_reader.errors.message)); + return Err(sc_reader.error.unwrap_or(Error::DataClone(None))); } DeleteJSAutoStructuredCloneBuffer(scbuf); diff --git a/components/script/dom/bindings/transferable.rs b/components/script/dom/bindings/transferable.rs index e6b2f000f3a..7650382b0c6 100644 --- a/components/script/dom/bindings/transferable.rs +++ b/components/script/dom/bindings/transferable.rs @@ -10,10 +10,12 @@ use std::hash::Hash; use base::id::NamespaceIndex; +use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::globalscope::GlobalScope; + pub(crate) trait Transferable: DomObject where Self: Sized, @@ -25,7 +27,10 @@ where true } - fn transfer(&self) -> Result<(NamespaceIndex, Self::Data), ()>; + /// + fn transfer(&self) -> Fallible<(NamespaceIndex, Self::Data)>; + + /// fn transfer_receive( owner: &GlobalScope, id: NamespaceIndex, diff --git a/components/script/dom/imagebitmap.rs b/components/script/dom/imagebitmap.rs index 4da3f24e87a..5a0da731207 100644 --- a/components/script/dom/imagebitmap.rs +++ b/components/script/dom/imagebitmap.rs @@ -11,7 +11,7 @@ use constellation_traits::SerializableImageBitmap; use dom_struct::dom_struct; use euclid::default::{Point2D, Rect, Size2D}; use pixels::{CorsStatus, PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; -use script_bindings::error::Error; +use script_bindings::error::{Error, Fallible}; use script_bindings::realms::{AlreadyInRealm, InRealm}; use crate::dom::bindings::cell::DomRefCell; @@ -630,14 +630,16 @@ impl Serializable for ImageBitmap { /// fn serialize(&self) -> Result<(ImageBitmapId, Self::Data), ()> { - // Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException. - if !self.origin_is_clean() { + // + // Step 19.1. If value has a [[Detached]] internal slot whose value is + // true, then throw a "DataCloneError" DOMException. + if self.is_detached() { return Err(()); } - // If value has a [[Detached]] internal slot whose value is true, - // then throw a "DataCloneError" DOMException. - if self.is_detached() { + // Step 1. If value's origin-clean flag is not set, then throw a + // "DataCloneError" DOMException. + if !self.origin_is_clean() { return Err(()); } @@ -673,45 +675,41 @@ impl Transferable for ImageBitmap { type Index = ImageBitmapIndex; type Data = SerializableImageBitmap; - fn can_transfer(&self) -> bool { - if !self.origin_is_clean() || self.is_detached() { - return false; - } - true - } - /// - fn transfer(&self) -> Result<(ImageBitmapId, SerializableImageBitmap), ()> { - // Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException. - if !self.origin_is_clean() { - return Err(()); + fn transfer(&self) -> Fallible<(ImageBitmapId, SerializableImageBitmap)> { + // + // Step 5.2. If transferable has a [[Detached]] internal slot and + // transferable.[[Detached]] is true, then throw a "DataCloneError" + // DOMException. + if self.is_detached() { + return Err(Error::DataClone(None)); } - // If value has a [[Detached]] internal slot whose value is true, - // then throw a "DataCloneError" DOMException. - if self.is_detached() { - return Err(()); + // Step 1. If value's origin-clean flag is not set, then throw a + // "DataCloneError" DOMException. + if !self.origin_is_clean() { + return Err(Error::DataClone(None)); } // Step 2. Set dataHolder.[[BitmapData]] to value's bitmap data. // Step 3. Unset value's bitmap data. - let serialized = SerializableImageBitmap { + let transferred = SerializableImageBitmap { bitmap_data: self.bitmap_data.borrow_mut().take().unwrap(), }; - Ok((ImageBitmapId::new(), serialized)) + Ok((ImageBitmapId::new(), transferred)) } /// fn transfer_receive( owner: &GlobalScope, _: ImageBitmapId, - serialized: SerializableImageBitmap, + transferred: SerializableImageBitmap, ) -> Result, ()> { // Step 1. Set value's bitmap data to serialized.[[BitmapData]]. Ok(ImageBitmap::new( owner, - serialized.bitmap_data, + transferred.bitmap_data, CanGc::note(), )) } diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs index d9cd6c50768..a002b5d7d35 100644 --- a/components/script/dom/messageport.rs +++ b/components/script/dom/messageport.rs @@ -19,7 +19,7 @@ use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{ MessagePortMethods, StructuredSerializeOptions, }; use crate::dom::bindings::conversions::root_from_object; -use crate::dom::bindings::error::{Error, ErrorResult, ErrorToJsval}; +use crate::dom::bindings::error::{Error, ErrorResult, ErrorToJsval, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; @@ -256,9 +256,13 @@ impl Transferable for MessagePort { type Data = MessagePortImpl; /// - fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { + fn transfer(&self) -> Fallible<(MessagePortId, MessagePortImpl)> { + // + // Step 5.2. If transferable has a [[Detached]] internal slot and + // transferable.[[Detached]] is true, then throw a "DataCloneError" + // DOMException. if self.detached.get() { - return Err(()); + return Err(Error::DataClone(None)); } self.detached.set(true); diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index c88ae5089db..b64c3d30825 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -152,16 +152,24 @@ impl Transferable for OffscreenCanvas { type Data = TransferableOffscreenCanvas; /// - fn transfer(&self) -> Result<(OffscreenCanvasId, TransferableOffscreenCanvas), ()> { - // TODO(#37919) Step 1. If value's context mode is not equal to none, - // then throw an "InvalidStateError" DOMException. + fn transfer(&self) -> Fallible<(OffscreenCanvasId, TransferableOffscreenCanvas)> { + // + // Step 5.2. If transferable has a [[Detached]] internal slot and + // transferable.[[Detached]] is true, then throw a "DataCloneError" + // DOMException. + if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() { + return Err(Error::DataClone(None)); + } + + // Step 1. If value's context mode is not equal to none, then throw an + // "InvalidStateError" DOMException. if !self.context.borrow().is_none() { - return Err(()); + return Err(Error::InvalidState); } // TODO(#37882): Allow to transfer with a placeholder canvas element. if self.placeholder.is_some() { - return Err(()); + return Err(Error::InvalidState); } // Step 2. Set value's context mode to detached. diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index 205a885acdd..4f82dfd7884 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -2406,11 +2406,12 @@ impl Transferable for ReadableStream { type Index = MessagePortIndex; type Data = MessagePortImpl; - /// - fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { - // If ! IsReadableStreamLocked(value) is true, throw a "DataCloneError" DOMException. + /// + fn transfer(&self) -> Fallible<(MessagePortId, MessagePortImpl)> { + // Step 1. If ! IsReadableStreamLocked(value) is true, throw a + // "DataCloneError" DOMException. if self.is_locked() { - return Err(()); + return Err(Error::DataClone(None)); } let global = self.global(); @@ -2419,36 +2420,36 @@ impl Transferable for ReadableStream { 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 writable be a new WritableStream in the current Realm. + // Step 5. Let writable be a new WritableStream in the current Realm. let writable = WritableStream::new_with_proto(&global, None, can_gc); - // Perform ! SetUpCrossRealmTransformWritable(writable, port1). + // Step 6. Perform ! SetUpCrossRealmTransformWritable(writable, port1). writable.setup_cross_realm_transform_writable(cx, &port_1, can_gc); - // Let promise be ! ReadableStreamPipeTo(value, writable, false, false, false). + // Step 7. Let promise be ! ReadableStreamPipeTo(value, writable, false, false, false). let promise = self.pipe_to( cx, &global, &writable, 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() } - /// + /// fn transfer_receive( owner: &GlobalScope, id: MessagePortId, @@ -2461,13 +2462,15 @@ impl Transferable for ReadableStream { // Note: dataHolder is used in `structuredclone.rs`, and value is created here. let value = ReadableStream::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 ! SetUpCrossRealmTransformReadable(value, port). + // Step 3. Perform ! SetUpCrossRealmTransformReadable(value, port). value.setup_cross_realm_transform_readable(cx, &transferred_port, can_gc); Ok(value) } diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs index 247c5f8b43d..6ed96b3034a 100644 --- a/components/script/dom/transformstream.rs +++ b/components/script/dom/transformstream.rs @@ -1024,23 +1024,26 @@ impl Transferable for TransformStream { type Index = MessagePortIndex; type Data = TransformStreamData; - fn transfer(&self) -> Result<(MessagePortId, TransformStreamData), ()> { + /// + fn transfer(&self) -> Fallible<(MessagePortId, TransformStreamData)> { let global = self.global(); let realm = enter_realm(&*global); let comp = InRealm::Entered(&realm); let cx = GlobalScope::get_cx(); let can_gc = CanGc::note(); - // Let readable be value.[[readable]]. + // Step 1. Let readable be value.[[readable]]. let readable = self.get_readable(); - // Let writable be value.[[writable]]. + // Step 2. Let writable be value.[[writable]]. let writable = self.get_writable(); - // If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException. - // If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException. + // Step 3. If ! IsReadableStreamLocked(readable) is true, throw a + // "DataCloneError" DOMException. + // Step 4. If ! IsWritableStreamLocked(writable) is true, throw a + // "DataCloneError" DOMException. if readable.is_locked() || writable.is_locked() { - return Err(()); + return Err(Error::DataClone(None)); } // First port pair (readable → proxy writable) @@ -1083,8 +1086,10 @@ impl Transferable for TransformStream { ) .set_promise_is_handled(); - // Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »). - // Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »). + // Step 5. Set dataHolder.[[readable]] to ! + // StructuredSerializeWithTransfer(readable, « readable »). + // Step 6. Set dataHolder.[[writable]] to ! + // StructuredSerializeWithTransfer(writable, « writable »). Ok(( *port1_peer.message_port_id(), TransformStreamData { @@ -1094,6 +1099,7 @@ impl Transferable for TransformStream { )) } + /// fn transfer_receive( owner: &GlobalScope, _id: MessagePortId, @@ -1105,18 +1111,23 @@ impl Transferable for TransformStream { let port1 = MessagePort::transfer_receive(owner, data.readable.0, data.readable.1)?; let port2 = MessagePort::transfer_receive(owner, data.writable.0, data.writable.1)?; - // Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm). - // Set value.[[readable]] to readableRecord.[[Deserialized]]. - // Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm). + // Step 1. Let readableRecord be ! + // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the + // current Realm). let proxy_readable = ReadableStream::new_with_proto(owner, None, can_gc); proxy_readable.setup_cross_realm_transform_readable(cx, &port2, can_gc); + // Step 2. Let writableRecord be ! + // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the + // current Realm). let proxy_writable = WritableStream::new_with_proto(owner, None, can_gc); proxy_writable.setup_cross_realm_transform_writable(cx, &port1, can_gc); - // Set value.[[readable]] to readableRecord.[[Deserialized]]. - // Set value.[[writable]] to writableRecord.[[Deserialized]]. - // Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined. + // Step 3. Set value.[[readable]] to readableRecord.[[Deserialized]]. + // Step 4. Set value.[[writable]] to writableRecord.[[Deserialized]]. + // Step 5. Set value.[[backpressure]], + // value.[[backpressureChangePromise]], and value.[[controller]] to + // undefined. let stream = TransformStream::new_with_proto(owner, None, can_gc); stream.readable.set(Some(&proxy_readable)); stream.writable.set(Some(&proxy_writable)); diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs index 4defef05007..b07ddac2a31 100644 --- a/components/script/dom/writablestream.rs +++ b/components/script/dom/writablestream.rs @@ -1222,11 +1222,12 @@ impl Transferable for WritableStream { type Index = MessagePortIndex; type Data = MessagePortImpl; - /// - fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { - // If ! IsWritableStreamLocked(value) is true, throw a "DataCloneError" DOMException. + /// + 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() } - /// + /// 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) } diff --git a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini deleted file mode 100644 index 2269d23a1e8..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[offscreencanvas.transferrable.html] - [Test that transfer an OffscreenCanvas that already have a 2d context throws exception.] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini index bb8c8d6c07c..d8d5e3a09ed 100644 --- a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini +++ b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini @@ -3,8 +3,5 @@ [Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker.] expected: FAIL - [Test that transfer an OffscreenCanvas that has a 2d context throws exception in a worker.] - expected: FAIL - [Test that transfer an OffscreenCanvas twice throws exception in a worker.] expected: FAIL