mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
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:
parent
cae73341b2
commit
a5b02047f9
10 changed files with 165 additions and 130 deletions
|
@ -216,7 +216,10 @@ unsafe extern "C" fn read_callback(
|
||||||
ptr::null_mut()
|
ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InterfaceDoesNotMatch;
|
enum OperationError {
|
||||||
|
InterfaceDoesNotMatch,
|
||||||
|
Exception(Error),
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn try_serialize<T: Serializable + IDLInterface>(
|
unsafe fn try_serialize<T: Serializable + IDLInterface>(
|
||||||
val: SerializableInterface,
|
val: SerializableInterface,
|
||||||
|
@ -225,11 +228,11 @@ unsafe fn try_serialize<T: Serializable + IDLInterface>(
|
||||||
global: &GlobalScope,
|
global: &GlobalScope,
|
||||||
w: *mut JSStructuredCloneWriter,
|
w: *mut JSStructuredCloneWriter,
|
||||||
writer: &mut StructuredDataWriter,
|
writer: &mut StructuredDataWriter,
|
||||||
) -> Result<bool, InterfaceDoesNotMatch> {
|
) -> Result<bool, OperationError> {
|
||||||
if let Ok(obj) = root_from_object::<T>(*obj, cx) {
|
if let Ok(obj) = root_from_object::<T>(*obj, cx) {
|
||||||
return Ok(write_object(val, global, &*obj, w, writer));
|
return Ok(write_object(val, global, &*obj, w, writer));
|
||||||
}
|
}
|
||||||
Err(InterfaceDoesNotMatch)
|
Err(OperationError::InterfaceDoesNotMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SerializeOperation = unsafe fn(
|
type SerializeOperation = unsafe fn(
|
||||||
|
@ -239,7 +242,7 @@ type SerializeOperation = unsafe fn(
|
||||||
&GlobalScope,
|
&GlobalScope,
|
||||||
*mut JSStructuredCloneWriter,
|
*mut JSStructuredCloneWriter,
|
||||||
&mut StructuredDataWriter,
|
&mut StructuredDataWriter,
|
||||||
) -> Result<bool, InterfaceDoesNotMatch>;
|
) -> Result<bool, OperationError>;
|
||||||
|
|
||||||
fn serialize_for_type(val: SerializableInterface) -> SerializeOperation {
|
fn serialize_for_type(val: SerializableInterface) -> SerializeOperation {
|
||||||
match val {
|
match val {
|
||||||
|
@ -363,32 +366,34 @@ unsafe fn try_transfer<T: Transferable + IDLInterface>(
|
||||||
tag: *mut u32,
|
tag: *mut u32,
|
||||||
ownership: *mut TransferableOwnership,
|
ownership: *mut TransferableOwnership,
|
||||||
extra_data: *mut u64,
|
extra_data: *mut u64,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), OperationError> {
|
||||||
if let Ok(object) = root_from_object::<T>(*obj, cx) {
|
let Ok(object) = root_from_object::<T>(*obj, cx) else {
|
||||||
*tag = StructuredCloneTags::from(interface) as u32;
|
return Err(OperationError::InterfaceDoesNotMatch);
|
||||||
*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);
|
|
||||||
|
|
||||||
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 (id, object) = object.transfer().map_err(OperationError::Exception)?;
|
||||||
let name_space = id.namespace_id.0.to_ne_bytes();
|
|
||||||
let index = index.to_ne_bytes();
|
|
||||||
|
|
||||||
let (left, right) = big.split_at_mut(4);
|
// 2. Store the transferred object at a given key.
|
||||||
left.copy_from_slice(&name_space);
|
let objects =
|
||||||
right.copy_from_slice(&index);
|
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.
|
let index = id.index.0.get();
|
||||||
*extra_data = u64::from_ne_bytes(big);
|
|
||||||
return Ok(());
|
let mut big: [u8; 8] = [0; 8];
|
||||||
}
|
let name_space = id.namespace_id.0.to_ne_bytes();
|
||||||
}
|
let index = index.to_ne_bytes();
|
||||||
Err(())
|
|
||||||
|
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(
|
type TransferOperation = unsafe fn(
|
||||||
|
@ -399,7 +404,7 @@ type TransferOperation = unsafe fn(
|
||||||
*mut u32,
|
*mut u32,
|
||||||
*mut TransferableOwnership,
|
*mut TransferableOwnership,
|
||||||
*mut u64,
|
*mut u64,
|
||||||
) -> Result<(), ()>;
|
) -> Result<(), OperationError>;
|
||||||
|
|
||||||
fn transfer_for_type(val: TransferrableInterface) -> TransferOperation {
|
fn transfer_for_type(val: TransferrableInterface) -> TransferOperation {
|
||||||
match val {
|
match val {
|
||||||
|
@ -425,8 +430,16 @@ unsafe extern "C" fn write_transfer_callback(
|
||||||
let sc_writer = &mut *(closure as *mut StructuredDataWriter);
|
let sc_writer = &mut *(closure as *mut StructuredDataWriter);
|
||||||
for transferable in TransferrableInterface::iter() {
|
for transferable in TransferrableInterface::iter() {
|
||||||
let try_transfer = transfer_for_type(transferable);
|
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) };
|
let msg_result = unsafe { CStr::from_ptr(error_message).to_str().map(str::to_string) };
|
||||||
|
|
||||||
if let Ok(msg) = msg_result {
|
if let Ok(msg) = msg_result {
|
||||||
let dom_error_record = &mut *(closure as *mut DOMErrorRecord);
|
let error = &mut *(closure as *mut Option<Error>);
|
||||||
|
|
||||||
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),
|
Writer(&'a mut StructuredDataWriter),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub(crate) struct DOMErrorRecord {
|
|
||||||
pub(crate) message: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reader and writer structs for results from, and inputs to, structured-data read/write operations.
|
/// Reader and writer structs for results from, and inputs to, structured-data read/write operations.
|
||||||
/// <https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data>
|
/// <https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data>
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct StructuredDataReader<'a> {
|
pub(crate) struct StructuredDataReader<'a> {
|
||||||
/// A struct of error message.
|
/// A error record.
|
||||||
errors: DOMErrorRecord,
|
error: Option<Error>,
|
||||||
/// Rooted copies of every deserialized object to ensure they are not garbage collected.
|
/// Rooted copies of every deserialized object to ensure they are not garbage collected.
|
||||||
roots: RootedVec<'a, Box<Heap<*mut JSObject>>>,
|
roots: RootedVec<'a, Box<Heap<*mut JSObject>>>,
|
||||||
/// A map of port implementations,
|
/// A map of port implementations,
|
||||||
|
@ -556,8 +565,8 @@ pub(crate) struct StructuredDataReader<'a> {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct StructuredDataWriter {
|
pub(crate) struct StructuredDataWriter {
|
||||||
/// Error message.
|
/// Error record.
|
||||||
pub(crate) errors: DOMErrorRecord,
|
pub(crate) error: Option<Error>,
|
||||||
/// Transferred ports.
|
/// Transferred ports.
|
||||||
pub(crate) ports: Option<HashMap<MessagePortId, MessagePortImpl>>,
|
pub(crate) ports: Option<HashMap<MessagePortId, MessagePortImpl>>,
|
||||||
/// Transferred transform streams.
|
/// Transferred transform streams.
|
||||||
|
@ -611,7 +620,7 @@ pub(crate) fn write(
|
||||||
);
|
);
|
||||||
if !result {
|
if !result {
|
||||||
JS_ClearPendingException(*cx);
|
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);
|
let nbytes = GetLengthOfJSStructuredCloneData(scdata);
|
||||||
|
@ -648,13 +657,13 @@ pub(crate) fn read(
|
||||||
let _ac = enter_realm(global);
|
let _ac = enter_realm(global);
|
||||||
rooted_vec!(let mut roots);
|
rooted_vec!(let mut roots);
|
||||||
let mut sc_reader = StructuredDataReader {
|
let mut sc_reader = StructuredDataReader {
|
||||||
|
error: None,
|
||||||
roots,
|
roots,
|
||||||
port_impls: data.ports.take(),
|
port_impls: data.ports.take(),
|
||||||
transform_streams_port_impls: data.transform_streams.take(),
|
transform_streams_port_impls: data.transform_streams.take(),
|
||||||
blob_impls: data.blobs.take(),
|
blob_impls: data.blobs.take(),
|
||||||
points: data.points.take(),
|
points: data.points.take(),
|
||||||
exceptions: data.exceptions.take(),
|
exceptions: data.exceptions.take(),
|
||||||
errors: DOMErrorRecord { message: None },
|
|
||||||
image_bitmaps: data.image_bitmaps.take(),
|
image_bitmaps: data.image_bitmaps.take(),
|
||||||
transferred_image_bitmaps: data.transferred_image_bitmaps.take(),
|
transferred_image_bitmaps: data.transferred_image_bitmaps.take(),
|
||||||
offscreen_canvases: data.offscreen_canvases.take(),
|
offscreen_canvases: data.offscreen_canvases.take(),
|
||||||
|
@ -688,7 +697,7 @@ pub(crate) fn read(
|
||||||
);
|
);
|
||||||
if !result {
|
if !result {
|
||||||
JS_ClearPendingException(*cx);
|
JS_ClearPendingException(*cx);
|
||||||
return Err(Error::DataClone(sc_reader.errors.message));
|
return Err(sc_reader.error.unwrap_or(Error::DataClone(None)));
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteJSAutoStructuredCloneBuffer(scbuf);
|
DeleteJSAutoStructuredCloneBuffer(scbuf);
|
||||||
|
|
|
@ -10,10 +10,12 @@ use std::hash::Hash;
|
||||||
|
|
||||||
use base::id::NamespaceIndex;
|
use base::id::NamespaceIndex;
|
||||||
|
|
||||||
|
use crate::dom::bindings::error::Fallible;
|
||||||
use crate::dom::bindings::reflector::DomObject;
|
use crate::dom::bindings::reflector::DomObject;
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
use crate::dom::bindings::structuredclone::StructuredData;
|
use crate::dom::bindings::structuredclone::StructuredData;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
|
|
||||||
pub(crate) trait Transferable: DomObject
|
pub(crate) trait Transferable: DomObject
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
@ -25,7 +27,10 @@ where
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer(&self) -> Result<(NamespaceIndex<Self::Index>, Self::Data), ()>;
|
/// <https://html.spec.whatwg.org/multipage/#transfer-steps>
|
||||||
|
fn transfer(&self) -> Fallible<(NamespaceIndex<Self::Index>, Self::Data)>;
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#transfer-receiving-steps>
|
||||||
fn transfer_receive(
|
fn transfer_receive(
|
||||||
owner: &GlobalScope,
|
owner: &GlobalScope,
|
||||||
id: NamespaceIndex<Self::Index>,
|
id: NamespaceIndex<Self::Index>,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use constellation_traits::SerializableImageBitmap;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use euclid::default::{Point2D, Rect, Size2D};
|
use euclid::default::{Point2D, Rect, Size2D};
|
||||||
use pixels::{CorsStatus, PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
|
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 script_bindings::realms::{AlreadyInRealm, InRealm};
|
||||||
|
|
||||||
use crate::dom::bindings::cell::DomRefCell;
|
use crate::dom::bindings::cell::DomRefCell;
|
||||||
|
@ -630,14 +630,16 @@ impl Serializable for ImageBitmap {
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:serialization-steps>
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:serialization-steps>
|
||||||
fn serialize(&self) -> Result<(ImageBitmapId, Self::Data), ()> {
|
fn serialize(&self) -> Result<(ImageBitmapId, Self::Data), ()> {
|
||||||
// Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
|
// <https://html.spec.whatwg.org/multipage/#structuredserializeinternal>
|
||||||
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(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If value has a [[Detached]] internal slot whose value is true,
|
// Step 1. If value's origin-clean flag is not set, then throw a
|
||||||
// then throw a "DataCloneError" DOMException.
|
// "DataCloneError" DOMException.
|
||||||
if self.is_detached() {
|
if !self.origin_is_clean() {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -673,45 +675,41 @@ impl Transferable for ImageBitmap {
|
||||||
type Index = ImageBitmapIndex;
|
type Index = ImageBitmapIndex;
|
||||||
type Data = SerializableImageBitmap;
|
type Data = SerializableImageBitmap;
|
||||||
|
|
||||||
fn can_transfer(&self) -> bool {
|
|
||||||
if !self.origin_is_clean() || self.is_detached() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-steps>
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-steps>
|
||||||
fn transfer(&self) -> Result<(ImageBitmapId, SerializableImageBitmap), ()> {
|
fn transfer(&self) -> Fallible<(ImageBitmapId, SerializableImageBitmap)> {
|
||||||
// Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
|
// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer>
|
||||||
if !self.origin_is_clean() {
|
// Step 5.2. If transferable has a [[Detached]] internal slot and
|
||||||
return Err(());
|
// 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,
|
// Step 1. If value's origin-clean flag is not set, then throw a
|
||||||
// then throw a "DataCloneError" DOMException.
|
// "DataCloneError" DOMException.
|
||||||
if self.is_detached() {
|
if !self.origin_is_clean() {
|
||||||
return Err(());
|
return Err(Error::DataClone(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2. Set dataHolder.[[BitmapData]] to value's bitmap data.
|
// Step 2. Set dataHolder.[[BitmapData]] to value's bitmap data.
|
||||||
// Step 3. Unset 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(),
|
bitmap_data: self.bitmap_data.borrow_mut().take().unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((ImageBitmapId::new(), serialized))
|
Ok((ImageBitmapId::new(), transferred))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-receiving-steps>
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-receiving-steps>
|
||||||
fn transfer_receive(
|
fn transfer_receive(
|
||||||
owner: &GlobalScope,
|
owner: &GlobalScope,
|
||||||
_: ImageBitmapId,
|
_: ImageBitmapId,
|
||||||
serialized: SerializableImageBitmap,
|
transferred: SerializableImageBitmap,
|
||||||
) -> Result<DomRoot<Self>, ()> {
|
) -> Result<DomRoot<Self>, ()> {
|
||||||
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
|
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
|
||||||
Ok(ImageBitmap::new(
|
Ok(ImageBitmap::new(
|
||||||
owner,
|
owner,
|
||||||
serialized.bitmap_data,
|
transferred.bitmap_data,
|
||||||
CanGc::note(),
|
CanGc::note(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{
|
||||||
MessagePortMethods, StructuredSerializeOptions,
|
MessagePortMethods, StructuredSerializeOptions,
|
||||||
};
|
};
|
||||||
use crate::dom::bindings::conversions::root_from_object;
|
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::inheritance::Castable;
|
||||||
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
|
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
|
@ -256,9 +256,13 @@ impl Transferable for MessagePort {
|
||||||
type Data = MessagePortImpl;
|
type Data = MessagePortImpl;
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-steps>
|
/// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-steps>
|
||||||
fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> {
|
fn transfer(&self) -> Fallible<(MessagePortId, MessagePortImpl)> {
|
||||||
|
// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer>
|
||||||
|
// Step 5.2. If transferable has a [[Detached]] internal slot and
|
||||||
|
// transferable.[[Detached]] is true, then throw a "DataCloneError"
|
||||||
|
// DOMException.
|
||||||
if self.detached.get() {
|
if self.detached.get() {
|
||||||
return Err(());
|
return Err(Error::DataClone(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.detached.set(true);
|
self.detached.set(true);
|
||||||
|
|
|
@ -152,16 +152,24 @@ impl Transferable for OffscreenCanvas {
|
||||||
type Data = TransferableOffscreenCanvas;
|
type Data = TransferableOffscreenCanvas;
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#the-offscreencanvas-interface:transfer-steps>
|
/// <https://html.spec.whatwg.org/multipage/#the-offscreencanvas-interface:transfer-steps>
|
||||||
fn transfer(&self) -> Result<(OffscreenCanvasId, TransferableOffscreenCanvas), ()> {
|
fn transfer(&self) -> Fallible<(OffscreenCanvasId, TransferableOffscreenCanvas)> {
|
||||||
// TODO(#37919) Step 1. If value's context mode is not equal to none,
|
// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer>
|
||||||
// then throw an "InvalidStateError" DOMException.
|
// 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() {
|
if !self.context.borrow().is_none() {
|
||||||
return Err(());
|
return Err(Error::InvalidState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(#37882): Allow to transfer with a placeholder canvas element.
|
// TODO(#37882): Allow to transfer with a placeholder canvas element.
|
||||||
if self.placeholder.is_some() {
|
if self.placeholder.is_some() {
|
||||||
return Err(());
|
return Err(Error::InvalidState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2. Set value's context mode to detached.
|
// Step 2. Set value's context mode to detached.
|
||||||
|
|
|
@ -2406,11 +2406,12 @@ impl Transferable for ReadableStream {
|
||||||
type Index = MessagePortIndex;
|
type Index = MessagePortIndex;
|
||||||
type Data = MessagePortImpl;
|
type Data = MessagePortImpl;
|
||||||
|
|
||||||
/// <https://streams.spec.whatwg.org/#ref-for-readablestream%E2%91%A1%E2%91%A0>
|
/// <https://streams.spec.whatwg.org/#ref-for-transfer-steps>
|
||||||
fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> {
|
fn transfer(&self) -> Fallible<(MessagePortId, MessagePortImpl)> {
|
||||||
// If ! IsReadableStreamLocked(value) is true, throw a "DataCloneError" DOMException.
|
// Step 1. If ! IsReadableStreamLocked(value) is true, throw a
|
||||||
|
// "DataCloneError" DOMException.
|
||||||
if self.is_locked() {
|
if self.is_locked() {
|
||||||
return Err(());
|
return Err(Error::DataClone(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = self.global();
|
let global = self.global();
|
||||||
|
@ -2419,36 +2420,36 @@ impl Transferable for ReadableStream {
|
||||||
let cx = GlobalScope::get_cx();
|
let cx = GlobalScope::get_cx();
|
||||||
let can_gc = CanGc::note();
|
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);
|
let port_1 = MessagePort::new(&global, can_gc);
|
||||||
global.track_message_port(&port_1, None);
|
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);
|
let port_2 = MessagePort::new(&global, can_gc);
|
||||||
global.track_message_port(&port_2, None);
|
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());
|
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);
|
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);
|
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(
|
let promise = self.pipe_to(
|
||||||
cx, &global, &writable, false, false, false, None, comp, can_gc,
|
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();
|
promise.set_promise_is_handled();
|
||||||
|
|
||||||
// Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
|
// Step 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
|
||||||
port_2.transfer()
|
port_2.transfer()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://streams.spec.whatwg.org/#ref-for-readablestream%E2%91%A1%E2%91%A0>
|
/// <https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps>
|
||||||
fn transfer_receive(
|
fn transfer_receive(
|
||||||
owner: &GlobalScope,
|
owner: &GlobalScope,
|
||||||
id: MessagePortId,
|
id: MessagePortId,
|
||||||
|
@ -2461,13 +2462,15 @@ impl Transferable for ReadableStream {
|
||||||
// Note: dataHolder is used in `structuredclone.rs`, and value is created here.
|
// Note: dataHolder is used in `structuredclone.rs`, and value is created here.
|
||||||
let value = ReadableStream::new_with_proto(owner, None, can_gc);
|
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`.
|
// 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)?;
|
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);
|
value.setup_cross_realm_transform_readable(cx, &transferred_port, can_gc);
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1024,23 +1024,26 @@ impl Transferable for TransformStream {
|
||||||
type Index = MessagePortIndex;
|
type Index = MessagePortIndex;
|
||||||
type Data = TransformStreamData;
|
type Data = TransformStreamData;
|
||||||
|
|
||||||
fn transfer(&self) -> Result<(MessagePortId, TransformStreamData), ()> {
|
/// <https://streams.spec.whatwg.org/#ref-for-transfer-steps②>
|
||||||
|
fn transfer(&self) -> Fallible<(MessagePortId, TransformStreamData)> {
|
||||||
let global = self.global();
|
let global = self.global();
|
||||||
let realm = enter_realm(&*global);
|
let realm = enter_realm(&*global);
|
||||||
let comp = InRealm::Entered(&realm);
|
let comp = InRealm::Entered(&realm);
|
||||||
let cx = GlobalScope::get_cx();
|
let cx = GlobalScope::get_cx();
|
||||||
let can_gc = CanGc::note();
|
let can_gc = CanGc::note();
|
||||||
|
|
||||||
// Let readable be value.[[readable]].
|
// Step 1. Let readable be value.[[readable]].
|
||||||
let readable = self.get_readable();
|
let readable = self.get_readable();
|
||||||
|
|
||||||
// Let writable be value.[[writable]].
|
// Step 2. Let writable be value.[[writable]].
|
||||||
let writable = self.get_writable();
|
let writable = self.get_writable();
|
||||||
|
|
||||||
// If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException.
|
// Step 3. If ! IsReadableStreamLocked(readable) is true, throw a
|
||||||
// If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException.
|
// "DataCloneError" DOMException.
|
||||||
|
// Step 4. If ! IsWritableStreamLocked(writable) is true, throw a
|
||||||
|
// "DataCloneError" DOMException.
|
||||||
if readable.is_locked() || writable.is_locked() {
|
if readable.is_locked() || writable.is_locked() {
|
||||||
return Err(());
|
return Err(Error::DataClone(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// First port pair (readable → proxy writable)
|
// First port pair (readable → proxy writable)
|
||||||
|
@ -1083,8 +1086,10 @@ impl Transferable for TransformStream {
|
||||||
)
|
)
|
||||||
.set_promise_is_handled();
|
.set_promise_is_handled();
|
||||||
|
|
||||||
// Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »).
|
// Step 5. Set dataHolder.[[readable]] to !
|
||||||
// Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »).
|
// StructuredSerializeWithTransfer(readable, « readable »).
|
||||||
|
// Step 6. Set dataHolder.[[writable]] to !
|
||||||
|
// StructuredSerializeWithTransfer(writable, « writable »).
|
||||||
Ok((
|
Ok((
|
||||||
*port1_peer.message_port_id(),
|
*port1_peer.message_port_id(),
|
||||||
TransformStreamData {
|
TransformStreamData {
|
||||||
|
@ -1094,6 +1099,7 @@ impl Transferable for TransformStream {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps②>
|
||||||
fn transfer_receive(
|
fn transfer_receive(
|
||||||
owner: &GlobalScope,
|
owner: &GlobalScope,
|
||||||
_id: MessagePortId,
|
_id: MessagePortId,
|
||||||
|
@ -1105,18 +1111,23 @@ impl Transferable for TransformStream {
|
||||||
let port1 = MessagePort::transfer_receive(owner, data.readable.0, data.readable.1)?;
|
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 port2 = MessagePort::transfer_receive(owner, data.writable.0, data.writable.1)?;
|
||||||
|
|
||||||
// Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm).
|
// Step 1. Let readableRecord be !
|
||||||
// Set value.[[readable]] to readableRecord.[[Deserialized]].
|
// StructuredDeserializeWithTransfer(dataHolder.[[readable]], the
|
||||||
// Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm).
|
// current Realm).
|
||||||
let proxy_readable = ReadableStream::new_with_proto(owner, None, can_gc);
|
let proxy_readable = ReadableStream::new_with_proto(owner, None, can_gc);
|
||||||
proxy_readable.setup_cross_realm_transform_readable(cx, &port2, 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);
|
let proxy_writable = WritableStream::new_with_proto(owner, None, can_gc);
|
||||||
proxy_writable.setup_cross_realm_transform_writable(cx, &port1, can_gc);
|
proxy_writable.setup_cross_realm_transform_writable(cx, &port1, can_gc);
|
||||||
|
|
||||||
// Set value.[[readable]] to readableRecord.[[Deserialized]].
|
// Step 3. Set value.[[readable]] to readableRecord.[[Deserialized]].
|
||||||
// Set value.[[writable]] to writableRecord.[[Deserialized]].
|
// Step 4. Set value.[[writable]] to writableRecord.[[Deserialized]].
|
||||||
// Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined.
|
// Step 5. Set value.[[backpressure]],
|
||||||
|
// value.[[backpressureChangePromise]], and value.[[controller]] to
|
||||||
|
// undefined.
|
||||||
let stream = TransformStream::new_with_proto(owner, None, can_gc);
|
let stream = TransformStream::new_with_proto(owner, None, can_gc);
|
||||||
stream.readable.set(Some(&proxy_readable));
|
stream.readable.set(Some(&proxy_readable));
|
||||||
stream.writable.set(Some(&proxy_writable));
|
stream.writable.set(Some(&proxy_writable));
|
||||||
|
|
|
@ -1222,11 +1222,12 @@ impl Transferable for WritableStream {
|
||||||
type Index = MessagePortIndex;
|
type Index = MessagePortIndex;
|
||||||
type Data = MessagePortImpl;
|
type Data = MessagePortImpl;
|
||||||
|
|
||||||
/// <https://streams.spec.whatwg.org/#ref-for-writablestream%E2%91%A0%E2%91%A4>
|
/// <https://streams.spec.whatwg.org/#ref-for-transfer-steps①>
|
||||||
fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> {
|
fn transfer(&self) -> Fallible<(MessagePortId, MessagePortImpl)> {
|
||||||
// If ! IsWritableStreamLocked(value) is true, throw a "DataCloneError" DOMException.
|
// Step 1. If ! IsWritableStreamLocked(value) is true, throw a
|
||||||
|
// "DataCloneError" DOMException.
|
||||||
if self.is_locked() {
|
if self.is_locked() {
|
||||||
return Err(());
|
return Err(Error::DataClone(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = self.global();
|
let global = self.global();
|
||||||
|
@ -1235,34 +1236,34 @@ impl Transferable for WritableStream {
|
||||||
let cx = GlobalScope::get_cx();
|
let cx = GlobalScope::get_cx();
|
||||||
let can_gc = CanGc::note();
|
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);
|
let port_1 = MessagePort::new(&global, can_gc);
|
||||||
global.track_message_port(&port_1, None);
|
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);
|
let port_2 = MessagePort::new(&global, can_gc);
|
||||||
global.track_message_port(&port_2, None);
|
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());
|
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);
|
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);
|
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);
|
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();
|
promise.set_promise_is_handled();
|
||||||
|
|
||||||
// Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
|
// Step 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
|
||||||
port_2.transfer()
|
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(
|
fn transfer_receive(
|
||||||
owner: &GlobalScope,
|
owner: &GlobalScope,
|
||||||
id: MessagePortId,
|
id: MessagePortId,
|
||||||
|
@ -1275,13 +1276,15 @@ impl Transferable for WritableStream {
|
||||||
// Note: dataHolder is used in `structuredclone.rs`, and value is created here.
|
// Note: dataHolder is used in `structuredclone.rs`, and value is created here.
|
||||||
let value = WritableStream::new_with_proto(owner, None, can_gc);
|
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`.
|
// 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)?;
|
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);
|
value.setup_cross_realm_transform_writable(cx, &transferred_port, can_gc);
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[offscreencanvas.transferrable.html]
|
|
||||||
[Test that transfer an OffscreenCanvas that already have a 2d context throws exception.]
|
|
||||||
expected: FAIL
|
|
|
@ -3,8 +3,5 @@
|
||||||
[Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker.]
|
[Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker.]
|
||||||
expected: FAIL
|
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.]
|
[Test that transfer an OffscreenCanvas twice throws exception in a worker.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue