Support optional message for dataclone error (#36308)

- [x] our [DataClone
error](d733abfca0/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](5d1c64dba9/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>
This commit is contained in:
Jerens Lensun 2025-04-14 02:10:04 +08:00 committed by GitHub
parent 740a94ef20
commit d5284dfad9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 61 additions and 10 deletions

View file

@ -574,7 +574,7 @@ impl BaseAudioContextMethods<crate::DomTypeHolder> 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;
}

View file

@ -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,

View file

@ -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<String>,
}
/// 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>
#[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<HashMap<StorageKey, DomRoot<Blob>>>,
/// 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<HashMap<MessagePortId, MessagePortImpl>>,
/// 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())
}
}

View file

@ -159,6 +159,21 @@ impl DOMException {
)
}
pub(crate) fn new_with_custom_message(
global: &GlobalScope,
code: DOMErrorName,
message: String,
can_gc: CanGc,
) -> DomRoot<DOMException> {
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))

View file

@ -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(())
}

View file

@ -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

View file

@ -44,7 +44,7 @@ pub enum Error {
/// InvalidNodeTypeError DOMException
InvalidNodeType,
/// DataCloneError DOMException
DataClone,
DataClone(Option<String>),
/// NoModificationAllowedError DOMException
NoModificationAllowed,
/// QuotaExceededError DOMException