diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index cbc2b19abc1..7229f422175 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -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 { let exception = unsafe { root_from_object::(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 } + /// 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"); diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index f806e907f4b..0ed19f4c6ab 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -2725,7 +2725,28 @@ impl GlobalScope { }) } - /// + /// + 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 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::() { dedicated.forward_error_to_worker_object(error_info); } else if self.is::() { + // 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, diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 9f4e8961e98..2df40c731c6 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1329,6 +1329,12 @@ impl WindowMethods for Window { Some(DomRoot::from_ref(container)) } + /// + fn ReportError(&self, cx: JSContext, error: HandleValue, can_gc: CanGc) { + self.as_global_scope() + .report_an_exception(cx, error, can_gc); + } + /// fn Navigator(&self) -> DomRoot { self.navigator diff --git a/components/script/dom/workers/dedicatedworkerglobalscope.rs b/components/script/dom/workers/dedicatedworkerglobalscope.rs index fc34ed86a27..94566d8f05f 100644 --- a/components/script/dom/workers/dedicatedworkerglobalscope.rs +++ b/components/script/dom/workers/dedicatedworkerglobalscope.rs @@ -637,15 +637,17 @@ impl DedicatedWorkerGlobalScope { true } - // https://html.spec.whatwg.org/multipage/#runtime-script-errors-2 + /// Step 7.2 of 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::().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::().fire(worker.upcast::(), CanGc::note()) { global.report_an_error(error_info, HandleValue::null(), CanGc::note()); } diff --git a/components/script/dom/workers/workerglobalscope.rs b/components/script/dom/workers/workerglobalscope.rs index 3a1a7fb2006..2921723f673 100644 --- a/components/script/dom/workers/workerglobalscope.rs +++ b/components/script/dom/workers/workerglobalscope.rs @@ -795,6 +795,12 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { self.upcast::().crypto(CanGc::note()) } + /// + fn ReportError(&self, cx: JSContext, error: HandleValue, can_gc: CanGc) { + self.upcast::() + .report_an_exception(cx, error, can_gc); + } + /// fn Btoa(&self, btoa: DOMString) -> Fallible { base64_btoa(btoa) diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 63de399c720..a6d308a46ac 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -684,7 +684,7 @@ DOMInterfaces = { }, 'Window': { - 'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'FetchLater', 'Open', 'SetInterval', 'SetTimeout', 'Stop', 'StructuredClone', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'], + 'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'FetchLater', 'Open', 'ReportError', 'SetInterval', 'SetTimeout', 'Stop', 'StructuredClone', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'], 'inRealms': ['Fetch', 'GetOpener', 'WebdriverCallback'], 'additionalTraits': ['crate::interfaces::WindowHelpers'], }, @@ -696,7 +696,7 @@ DOMInterfaces = { 'WorkerGlobalScope': { 'inRealms': ['Fetch'], - 'canGc': ['Fetch', 'CreateImageBitmap', 'CreateImageBitmap_', 'ImportScripts', 'SetInterval', 'SetTimeout', 'StructuredClone', 'TrustedTypes'], + 'canGc': ['Fetch', 'CreateImageBitmap', 'CreateImageBitmap_', 'ImportScripts', 'ReportError', 'SetInterval', 'SetTimeout', 'StructuredClone', 'TrustedTypes'], }, 'Worklet': { diff --git a/components/script_bindings/webidls/WindowOrWorkerGlobalScope.webidl b/components/script_bindings/webidls/WindowOrWorkerGlobalScope.webidl index 58b38d3ed05..29b3c8c9d29 100644 --- a/components/script_bindings/webidls/WindowOrWorkerGlobalScope.webidl +++ b/components/script_bindings/webidls/WindowOrWorkerGlobalScope.webidl @@ -10,6 +10,8 @@ typedef (TrustedScript or DOMString or Function) TimerHandler; interface mixin WindowOrWorkerGlobalScope { [Replaceable] readonly attribute USVString origin; + undefined reportError(any e); + // base64 utility methods [Throws] DOMString btoa(DOMString data); [Throws] DOMString atob(DOMString data); diff --git a/tests/wpt/meta/html/dom/idlharness.any.js.ini b/tests/wpt/meta/html/dom/idlharness.any.js.ini index afefc701243..c056defb6bd 100644 --- a/tests/wpt/meta/html/dom/idlharness.any.js.ini +++ b/tests/wpt/meta/html/dom/idlharness.any.js.ini @@ -53,9 +53,6 @@ [WorkerGlobalScope interface: attribute crossOriginIsolated] expected: FAIL - [WorkerGlobalScope interface: operation reportError(any)] - expected: FAIL - [DedicatedWorkerGlobalScope interface: internal [[SetPrototypeOf\]\] method of interface prototype object - setting to a new value via Object.setPrototypeOf should throw a TypeError] expected: FAIL @@ -86,12 +83,6 @@ [WorkerGlobalScope interface: self must inherit property "crossOriginIsolated" with the proper type] expected: FAIL - [WorkerGlobalScope interface: self must inherit property "reportError(any)" with the proper type] - expected: FAIL - - [WorkerGlobalScope interface: calling reportError(any) on self with too few arguments must throw TypeError] - expected: FAIL - [WorkerNavigator interface: member taintEnabled] expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/scripting/reporterror-cross-realm-method.html.ini b/tests/wpt/meta/html/webappapis/scripting/reporterror-cross-realm-method.html.ini deleted file mode 100644 index 18b3caf7345..00000000000 --- a/tests/wpt/meta/html/webappapis/scripting/reporterror-cross-realm-method.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[reporterror-cross-realm-method.html] - [self.reportError() dispatches an "error" event for this's relevant global object] - expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/scripting/reporterror.any.js.ini b/tests/wpt/meta/html/webappapis/scripting/reporterror.any.js.ini index 6e2d40c6abe..b1ee97118bf 100644 --- a/tests/wpt/meta/html/webappapis/scripting/reporterror.any.js.ini +++ b/tests/wpt/meta/html/webappapis/scripting/reporterror.any.js.ini @@ -1,26 +1,8 @@ [reporterror.any.worker.html] - [self.reportError(1)] - expected: FAIL - [self.reportError(TypeError)] expected: FAIL - [self.reportError(undefined)] - expected: FAIL - - [self.reportError() doesn't invoke getters] - expected: FAIL - [reporterror.any.html] - [self.reportError(1)] - expected: FAIL - [self.reportError(TypeError)] expected: FAIL - - [self.reportError(undefined)] - expected: FAIL - - [self.reportError() doesn't invoke getters] - expected: FAIL