From d5284dfad9ee90a3cf689f41e8bddf8c98567a3f Mon Sep 17 00:00:00 2001 From: Jerens Lensun <54782057+jerensl@users.noreply.github.com> Date: Mon, 14 Apr 2025 02:10:04 +0800 Subject: [PATCH] Support optional message for dataclone error (#36308) - [x] our [DataClone error](https://github.com/servo/servo/blob/d733abfca02cdb9fd2af4f0d82ff050e25f71829/components/script/dom/bindings/error.rs#L80) needs to support an optional message - [x] we need to add support to our DOMException implementation to allow an optional message to replace the default message - [x] we need to create a new struct used by both StructuredDataReader and StructuredDataWriter for storing the error message in the report_error_callback - [x] report_error_callback needs to cast the closure pointer to the new struct - [x] the code that [throws a DataClone error](https://github.com/servo/servo/blob/5d1c64dba9cf3e65f770370eb17f00ad4114edce/components/script/dom/bindings/structuredclone.rs#L542) needs to use the stored error message if it's available Testing: *Describe how this pull request is tested or why it doesn't require tests* Fixes: #36191 --------- Signed-off-by: jerensl <54782057+jerensl@users.noreply.github.com> --- components/script/dom/baseaudiocontext.rs | 2 +- components/script/dom/bindings/error.rs | 17 +++++++++- .../script/dom/bindings/structuredclone.rs | 31 ++++++++++++++++--- components/script/dom/domexception.rs | 15 +++++++++ components/script/dom/globalscope.rs | 2 +- components/script/dom/messageport.rs | 2 +- components/script_bindings/error.rs | 2 +- 7 files changed, 61 insertions(+), 10 deletions(-) diff --git a/components/script/dom/baseaudiocontext.rs b/components/script/dom/baseaudiocontext.rs index 41633683824..2ef7bce981b 100644 --- a/components/script/dom/baseaudiocontext.rs +++ b/components/script/dom/baseaudiocontext.rs @@ -574,7 +574,7 @@ impl BaseAudioContextMethods for BaseAudioContext { .decode_audio_data(audio_data, callbacks); } else { // Step 3. - promise.reject_error(Error::DataClone, can_gc); + promise.reject_error(Error::DataClone(None), can_gc); return promise; } diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index b9d51e77fcf..b0fd301df6a 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -70,7 +70,22 @@ pub(crate) fn throw_dom_exception( Error::Abort => DOMErrorName::AbortError, Error::Timeout => DOMErrorName::TimeoutError, Error::InvalidNodeType => DOMErrorName::InvalidNodeTypeError, - Error::DataClone => DOMErrorName::DataCloneError, + Error::DataClone(message) => match message { + Some(custom_message) => unsafe { + assert!(!JS_IsExceptionPending(*cx)); + let exception = DOMException::new_with_custom_message( + global, + DOMErrorName::DataCloneError, + custom_message, + can_gc, + ); + rooted!(in(*cx) let mut thrown = UndefinedValue()); + exception.to_jsval(*cx, thrown.handle_mut()); + JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture); + return; + }, + None => DOMErrorName::DataCloneError, + }, Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError, Error::QuotaExceeded => DOMErrorName::QuotaExceededError, Error::TypeMismatch => DOMErrorName::TypeMismatchError, diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 572faca1907..fb701406762 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -5,6 +5,7 @@ //! This module implements structured cloning, as defined by [HTML](https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data). use std::collections::HashMap; +use std::ffi::CStr; use std::num::NonZeroU32; use std::os::raw; use std::ptr; @@ -312,6 +313,7 @@ unsafe extern "C" fn read_transfer_callback( let sc_reader = &mut *(closure as *mut StructuredDataReader); let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); let owner = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); + for transferrable in TransferrableInterface::iter() { if tag == StructuredCloneTags::from(transferrable) as u32 { let transfer_receiver = receiver_for_type(transferrable); @@ -439,9 +441,16 @@ unsafe extern "C" fn can_transfer_callback( unsafe extern "C" fn report_error_callback( _cx: *mut JSContext, _errorid: u32, - _closure: *mut ::std::os::raw::c_void, - _error_message: *const ::std::os::raw::c_char, + closure: *mut raw::c_void, + error_message: *const ::std::os::raw::c_char, ) { + 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); + + dom_error_record.message = Some(msg) + } } unsafe extern "C" fn sab_cloned_callback( @@ -468,9 +477,18 @@ pub(crate) enum StructuredData<'a> { 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 struct of error message. + pub(crate) errors: DOMErrorRecord, /// A map of deserialized blobs, stored temporarily here to keep them rooted. pub(crate) blobs: Option>>, /// A map of deserialized points, stored temporarily here to keep them rooted. @@ -493,7 +511,10 @@ pub(crate) struct StructuredDataReader { /// A data holder for transferred and serialized objects. #[derive(Default)] +#[repr(C)] pub(crate) struct StructuredDataWriter { + /// Error message. + pub(crate) errors: DOMErrorRecord, /// Transferred ports. pub(crate) ports: Option>, /// Serialized points. @@ -537,7 +558,7 @@ pub(crate) fn write( ); if !result { JS_ClearPendingException(*cx); - return Err(Error::DataClone); + return Err(Error::DataClone(sc_writer.errors.message)); } let nbytes = GetLengthOfJSStructuredCloneData(scdata); @@ -575,6 +596,7 @@ pub(crate) fn read( port_impls: data.ports.take(), blob_impls: data.blobs.take(), points: data.points.take(), + errors: DOMErrorRecord { message: None }, }; let sc_reader_ptr = &mut sc_reader as *mut _; unsafe { @@ -605,14 +627,13 @@ pub(crate) fn read( ); if !result { JS_ClearPendingException(*cx); - return Err(Error::DataClone); + return Err(Error::DataClone(sc_reader.errors.message)); } DeleteJSAutoStructuredCloneBuffer(scbuf); // Any transfer-received port-impls should have been taken out. assert!(sc_reader.port_impls.is_none()); - Ok(sc_reader.message_ports.take().unwrap_or_default()) } } diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs index 6a2ec644e5d..a5ad0bfb043 100644 --- a/components/script/dom/domexception.rs +++ b/components/script/dom/domexception.rs @@ -159,6 +159,21 @@ impl DOMException { ) } + pub(crate) fn new_with_custom_message( + global: &GlobalScope, + code: DOMErrorName, + message: String, + can_gc: CanGc, + ) -> DomRoot { + let (_, name) = DOMException::get_error_data_by_code(code); + + reflect_dom_object( + Box::new(DOMException::new_inherited(DOMString::from(message), name)), + global, + can_gc, + ) + } + // not an IDL stringifier, used internally pub(crate) fn stringifier(&self) -> DOMString { DOMString::from(format!("{}: {}", self.name, self.message)) diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index e23a35b349d..aaa0721c748 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -3215,7 +3215,7 @@ impl GlobalScope { let data = structuredclone::write(cx, value, Some(guard))?; - structuredclone::read(self, data, retval).map_err(|_| Error::DataClone)?; + structuredclone::read(self, data, retval).map_err(|_| Error::DataClone(None))?; Ok(()) } diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs index 8addfe0731f..d299ab8156f 100644 --- a/components/script/dom/messageport.rs +++ b/components/script/dom/messageport.rs @@ -119,7 +119,7 @@ impl MessagePort { for port in ports { // Step 2 if port.message_port_id() == self.message_port_id() { - return Err(Error::DataClone); + return Err(Error::DataClone(None)); } // Step 4 diff --git a/components/script_bindings/error.rs b/components/script_bindings/error.rs index 031eb41ee22..8424ff0fa95 100644 --- a/components/script_bindings/error.rs +++ b/components/script_bindings/error.rs @@ -44,7 +44,7 @@ pub enum Error { /// InvalidNodeTypeError DOMException InvalidNodeType, /// DataCloneError DOMException - DataClone, + DataClone(Option), /// NoModificationAllowedError DOMException NoModificationAllowed, /// QuotaExceededError DOMException