script: Extract DOMException creation from throwing an exception. (#38961)

This is preparation for #38740, which wants to use DOMExceptions without
immediately throwing them and aborting execution.

Testing: Existing WPT coverage will suffice for this refactor.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-08-27 07:12:01 -04:00 committed by GitHub
parent ebaf78116a
commit 559b05c1b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -18,6 +18,7 @@ use js::rust::{HandleObject, HandleValue, MutableHandleValue};
use libc::c_uint; use libc::c_uint;
use script_bindings::conversions::SafeToJSValConvertible; use script_bindings::conversions::SafeToJSValConvertible;
pub(crate) use script_bindings::error::*; pub(crate) use script_bindings::error::*;
use script_bindings::root::DomRoot;
use script_bindings::str::DOMString; use script_bindings::str::DOMString;
#[cfg(feature = "js_backtrace")] #[cfg(feature = "js_backtrace")]
@ -39,6 +40,16 @@ thread_local! {
static LAST_EXCEPTION_BACKTRACE: DomRefCell<Option<(Option<String>, String)>> = DomRefCell::new(None); static LAST_EXCEPTION_BACKTRACE: DomRefCell<Option<(Option<String>, String)>> = DomRefCell::new(None);
} }
/// Error values that have no equivalent DOMException representation.
enum JsEngineError {
/// An EMCAScript TypeError.
Type(String),
/// An ECMAScript RangeError.
Range(String),
/// The JS engine reported a thrown exception.
JSFailed,
}
/// Set a pending exception for the given `result` on `cx`. /// Set a pending exception for the given `result` on `cx`.
pub(crate) fn throw_dom_exception( pub(crate) fn throw_dom_exception(
cx: SafeJSContext, cx: SafeJSContext,
@ -56,6 +67,38 @@ pub(crate) fn throw_dom_exception(
}); });
} }
match create_dom_exception(global, result, can_gc) {
Ok(exception) => unsafe {
assert!(!JS_IsExceptionPending(*cx));
rooted!(in(*cx) let mut thrown = UndefinedValue());
exception.safe_to_jsval(cx, thrown.handle_mut());
JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture);
},
Err(JsEngineError::Type(message)) => unsafe {
assert!(!JS_IsExceptionPending(*cx));
throw_type_error(*cx, &message);
},
Err(JsEngineError::Range(message)) => unsafe {
assert!(!JS_IsExceptionPending(*cx));
throw_range_error(*cx, &message);
},
Err(JsEngineError::JSFailed) => unsafe {
assert!(JS_IsExceptionPending(*cx));
},
}
}
/// If possible, create a new DOMException representing the provided error.
/// If no such DOMException exists, return a subset of the original error values
/// that may need additional handling.
fn create_dom_exception(
global: &GlobalScope,
result: Error,
can_gc: CanGc,
) -> Result<DomRoot<DOMException>, JsEngineError> {
let code = match result { let code = match result {
Error::IndexSize => DOMErrorName::IndexSizeError, Error::IndexSize => DOMErrorName::IndexSizeError,
Error::NotFound => DOMErrorName::NotFoundError, Error::NotFound => DOMErrorName::NotFoundError,
@ -73,35 +116,28 @@ pub(crate) fn throw_dom_exception(
Error::Abort => DOMErrorName::AbortError, Error::Abort => DOMErrorName::AbortError,
Error::Timeout => DOMErrorName::TimeoutError, Error::Timeout => DOMErrorName::TimeoutError,
Error::InvalidNodeType => DOMErrorName::InvalidNodeTypeError, Error::InvalidNodeType => DOMErrorName::InvalidNodeTypeError,
Error::DataClone(message) => match message { Error::DataClone(Some(custom_message)) => {
Some(custom_message) => unsafe { return Ok(DOMException::new_with_custom_message(
assert!(!JS_IsExceptionPending(*cx)); global,
let exception = DOMException::new_with_custom_message( DOMErrorName::DataCloneError,
global, custom_message,
DOMErrorName::DataCloneError, can_gc,
custom_message, ));
can_gc,
);
rooted!(in(*cx) let mut thrown = UndefinedValue());
exception.safe_to_jsval(cx, thrown.handle_mut());
JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture);
return;
},
None => DOMErrorName::DataCloneError,
}, },
Error::DataClone(None) => DOMErrorName::DataCloneError,
Error::Data => DOMErrorName::DataError, Error::Data => DOMErrorName::DataError,
Error::TransactionInactive => DOMErrorName::TransactionInactiveError, Error::TransactionInactive => DOMErrorName::TransactionInactiveError,
Error::ReadOnly => DOMErrorName::ReadOnlyError, Error::ReadOnly => DOMErrorName::ReadOnlyError,
Error::Version => DOMErrorName::VersionError, Error::Version => DOMErrorName::VersionError,
Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError, Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError,
Error::QuotaExceeded { quota, requested } => unsafe { Error::QuotaExceeded { quota, requested } => {
assert!(!JS_IsExceptionPending(*cx)); return Ok(DomRoot::upcast(QuotaExceededError::new(
let exception = global,
QuotaExceededError::new(global, DOMString::new(), quota, requested, can_gc); DOMString::new(),
rooted!(in(*cx) let mut thrown = UndefinedValue()); quota,
exception.safe_to_jsval(cx, thrown.handle_mut()); requested,
JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture); can_gc,
return; )));
}, },
Error::TypeMismatch => DOMErrorName::TypeMismatchError, Error::TypeMismatch => DOMErrorName::TypeMismatchError,
Error::InvalidModification => DOMErrorName::InvalidModificationError, Error::InvalidModification => DOMErrorName::InvalidModificationError,
@ -110,29 +146,11 @@ pub(crate) fn throw_dom_exception(
Error::NotAllowed => DOMErrorName::NotAllowedError, Error::NotAllowed => DOMErrorName::NotAllowedError,
Error::Encoding => DOMErrorName::EncodingError, Error::Encoding => DOMErrorName::EncodingError,
Error::Constraint => DOMErrorName::ConstraintError, Error::Constraint => DOMErrorName::ConstraintError,
Error::Type(message) => unsafe { Error::Type(message) => return Err(JsEngineError::Type(message)),
assert!(!JS_IsExceptionPending(*cx)); Error::Range(message) => return Err(JsEngineError::Range(message)),
throw_type_error(*cx, &message); Error::JSFailed => return Err(JsEngineError::JSFailed),
return;
},
Error::Range(message) => unsafe {
assert!(!JS_IsExceptionPending(*cx));
throw_range_error(*cx, &message);
return;
},
Error::JSFailed => unsafe {
assert!(JS_IsExceptionPending(*cx));
return;
},
}; };
Ok(DOMException::new(global, code, can_gc))
unsafe {
assert!(!JS_IsExceptionPending(*cx));
let exception = DOMException::new(global, code, can_gc);
rooted!(in(*cx) let mut thrown = UndefinedValue());
exception.safe_to_jsval(cx, thrown.handle_mut());
JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture);
}
} }
/// A struct encapsulating information about a runtime script error. /// A struct encapsulating information about a runtime script error.