Implement WindowOrWorkerGlobalScope::reportError (#40654)

This web API is alternative API to `throw e`, which is why we can reuse
a lot of the existing machinery.

The one testcase that isn't passing yet is because it reports an empty
`TypeError`. The current logic in `ErrorInfo` only retrieves the message
data, but doesn't include the type of the exception. For that, we need
to use `(*report)._base.errorNumber` and map that back to the original
type codes. However, deferring that to a follow-up as that requires some
more work in mozjs.

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe
2025-11-16 10:30:16 +01:00
committed by GitHub
parent 10796b8590
commit 8e0c2d5750
10 changed files with 63 additions and 46 deletions

View File

@@ -20,7 +20,7 @@ use js::jsapi::{
};
use js::jsval::UndefinedValue;
use js::rust::wrappers::{JS_ErrorFromException, JS_GetPendingException, JS_SetPendingException};
use js::rust::{HandleObject, HandleValue, MutableHandleValue};
use js::rust::{HandleObject, HandleValue, MutableHandleValue, describe_scripted_caller};
use libc::c_uint;
use script_bindings::conversions::SafeToJSValConvertible;
pub(crate) use script_bindings::error::*;
@@ -236,11 +236,12 @@ impl ErrorInfo {
fn from_dom_exception(object: HandleObject, cx: SafeJSContext) -> Option<ErrorInfo> {
let exception = unsafe { root_from_object::<DOMException>(object.get(), *cx).ok()? };
let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
Some(ErrorInfo {
filename: "".to_string(),
message: exception.stringifier().into(),
lineno: 0,
column: 0,
filename: scripted_caller.filename,
lineno: scripted_caller.line,
column: scripted_caller.col + 1,
})
}
@@ -254,6 +255,7 @@ impl ErrorInfo {
None
}
/// <https://html.spec.whatwg.org/multipage/#extract-error>
pub(crate) fn from_value(value: HandleValue, cx: SafeJSContext, can_gc: CanGc) -> ErrorInfo {
if value.is_object() {
rooted!(in(*cx) let object = value.to_object());
@@ -263,11 +265,14 @@ impl ErrorInfo {
}
match USVString::safe_from_jsval(cx, value, (), can_gc) {
Ok(ConversionResult::Success(USVString(string))) => ErrorInfo {
message: format!("uncaught exception: {}", string),
filename: String::new(),
lineno: 0,
column: 0,
Ok(ConversionResult::Success(USVString(string))) => {
let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
ErrorInfo {
message: format!("uncaught exception: {}", string),
filename: scripted_caller.filename,
lineno: scripted_caller.line,
column: scripted_caller.col + 1,
}
},
_ => {
panic!("uncaught exception: failed to stringify primitive");

View File

@@ -2725,7 +2725,28 @@ impl GlobalScope {
})
}
/// <https://html.spec.whatwg.org/multipage/#report-the-error>
/// <https://html.spec.whatwg.org/multipage/#report-an-exception>
pub(crate) fn report_an_exception(&self, cx: SafeJSContext, error: HandleValue, can_gc: CanGc) {
// Step 1. Let notHandled be true.
//
// Handled in `report_an_error`
// Step 2. Let errorInfo be the result of extracting error information from exception.
// Step 3. Let script be a script found in an implementation-defined way, or null.
// This should usually be the running script (most notably during run a classic script).
// Step 4. If script is a classic script and script's muted errors is true, then set errorInfo[error] to null,
// errorInfo[message] to "Script error.", errorInfo[filename] to the empty string,
// errorInfo[lineno] to 0, and errorInfo[colno] to 0.
let error_info = crate::dom::bindings::error::ErrorInfo::from_value(error, cx, can_gc);
// Step 5. If omitError is true, then set errorInfo[error] to null.
//
// `omitError` defaults to `false`
// Steps 6-7
self.report_an_error(error_info, error, can_gc);
}
/// Steps 6-7 of <https://html.spec.whatwg.org/multipage/#report-an-exception>
pub(crate) fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue, can_gc: CanGc) {
// Step 6. Early return if global is in error reporting mode,
if self.in_error_reporting_mode.get() {
@@ -2760,12 +2781,17 @@ impl GlobalScope {
// Step 6.3. Set global's in error reporting mode to false.
self.in_error_reporting_mode.set(false);
// Step 7.
// Step 7. If notHandled is true, then:
if not_handled {
// Step 7.2. If global implements DedicatedWorkerGlobalScope,
// queue a global task on the DOM manipulation task source with the
// global's associated Worker's relevant global object to run these steps:
//
// https://html.spec.whatwg.org/multipage/#runtime-script-errors-2
if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
dedicated.forward_error_to_worker_object(error_info);
} else if self.is::<Window>() {
// Step 7.3. Otherwise, the user agent may report exception to a developer console.
if let Some(ref chan) = self.devtools_chan {
let _ = chan.send(ScriptToDevtoolsControlMsg::ReportPageError(
self.pipeline_id,

View File

@@ -1329,6 +1329,12 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
Some(DomRoot::from_ref(container))
}
/// <https://html.spec.whatwg.org/multipage/#dom-reporterror>
fn ReportError(&self, cx: JSContext, error: HandleValue, can_gc: CanGc) {
self.as_global_scope()
.report_an_exception(cx, error, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-navigator>
fn Navigator(&self) -> DomRoot<Navigator> {
self.navigator

View File

@@ -637,15 +637,17 @@ impl DedicatedWorkerGlobalScope {
true
}
// https://html.spec.whatwg.org/multipage/#runtime-script-errors-2
/// Step 7.2 of <https://html.spec.whatwg.org/multipage/#report-an-exception>
pub(crate) fn forward_error_to_worker_object(&self, error_info: ErrorInfo) {
// Step 7.2.1. Let workerObject be the Worker object associated with global.
let worker = self.worker.borrow().as_ref().unwrap().clone();
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
let task = Box::new(task!(forward_error_to_worker_object: move || {
let worker = worker.root();
let global = worker.global();
// Step 1.
// Step 7.2.2. Set notHandled to the result of firing an event named error at workerObject, using ErrorEvent,
// with the cancelable attribute initialized to true, and additional attributes initialized according to errorInfo.
let event = ErrorEvent::new(
&global,
atom!("error"),
@@ -659,7 +661,7 @@ impl DedicatedWorkerGlobalScope {
CanGc::note(),
);
// Step 2.
// Step 7.2.3. If notHandled is true, then report exception for workerObject's relevant global object with omitError set to true.
if event.upcast::<Event>().fire(worker.upcast::<EventTarget>(), CanGc::note()) {
global.report_an_error(error_info, HandleValue::null(), CanGc::note());
}

View File

@@ -795,6 +795,12 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
self.upcast::<GlobalScope>().crypto(CanGc::note())
}
/// <https://html.spec.whatwg.org/multipage/#dom-reporterror>
fn ReportError(&self, cx: JSContext, error: HandleValue, can_gc: CanGc) {
self.upcast::<GlobalScope>()
.report_an_exception(cx, error, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-windowbase64-btoa>
fn Btoa(&self, btoa: DOMString) -> Fallible<DOMString> {
base64_btoa(btoa)