mirror of
https://github.com/lexmount/moli.git
synced 2026-09-26 16:01:30 +00:00
fix(workers): share intrinsic bootstrap error events
This commit is contained in:
@@ -461,9 +461,8 @@ use self::window_runtime::global_caches_getter_callback;
|
||||
pub(crate) use self::window_runtime::install_child_window_own_methods;
|
||||
pub(crate) use self::window_template::install_window_own_template_bindings;
|
||||
pub(crate) use self::worker_host::{
|
||||
dispatch_worker_error_event_with_error, dispatch_worker_error_event_with_kind,
|
||||
dispatch_worker_event, flush_pending_worker_messages_for_listener,
|
||||
worker_has_message_delivery_listener,
|
||||
dispatch_worker_error_event_with_kind, dispatch_worker_event,
|
||||
flush_pending_worker_messages_for_listener, worker_has_message_delivery_listener,
|
||||
};
|
||||
pub(crate) use self::worker_location_runtime::install_worker_location_runtime_state;
|
||||
pub(super) use super::{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{
|
||||
CHILD_BROWSING_CONTEXT_HANDLE_SLOT, MessagePortRealmBinding,
|
||||
ensure_message_port_wrapper_for_id_in_realm,
|
||||
events::{clear_event_dispatch_fields, set_event_dispatch_fields},
|
||||
events::{clear_event_dispatch_fields, construct_original_event, set_event_dispatch_fields},
|
||||
invoke_simple_event_listener,
|
||||
navigation_serialize::{
|
||||
current_document_content_security_policies, current_document_referrer_policy,
|
||||
@@ -87,22 +87,6 @@ struct SharedWorkerObjectDeclaration<'scope> {
|
||||
port: v8::Local<'scope, v8::Object>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct SharedWorkerHostEventInitDeclaration {
|
||||
#[webapi(data_property, enumerable)]
|
||||
cancelable: bool,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::Event, prototype = "Object")]
|
||||
struct SharedWorkerHostEventFallbackDeclaration {
|
||||
#[webapi(data_property, enumerable)]
|
||||
r#type: String,
|
||||
#[webapi(data_property, enumerable)]
|
||||
cancelable: bool,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct SharedWorkerHostErrorEventInitDeclaration<'scope> {
|
||||
@@ -137,16 +121,6 @@ struct SharedWorkerHostErrorEventFallbackDeclaration<'scope, 'text> {
|
||||
error: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, scope_lifetime = 'scope, data_properties, enumerable)]
|
||||
struct SharedWorkerHostErrorEventDetailsDeclaration<'scope, 'text> {
|
||||
message: &'text str,
|
||||
filename: &'text str,
|
||||
lineno: u32,
|
||||
colno: u32,
|
||||
error: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
struct SharedWorkerOptions {
|
||||
name: String,
|
||||
script_kind: WorkerScriptKind,
|
||||
@@ -785,15 +759,16 @@ fn dispatch_shared_worker_error_event<'s>(
|
||||
event_kind: crate::worker::WorkerParentErrorEventKind,
|
||||
) -> bool {
|
||||
let event = match event_kind {
|
||||
// Fetch/parse failures fire a plain Event with the default flags.
|
||||
crate::worker::WorkerParentErrorEventKind::Event => {
|
||||
let event = new_event(scope, "error", true);
|
||||
set_error_event_details(scope, event, message, filename, lineno, colno, error);
|
||||
event
|
||||
}
|
||||
crate::worker::WorkerParentErrorEventKind::ErrorEvent => {
|
||||
new_error_event(scope, message, filename, lineno, colno, error)
|
||||
construct_original_event(scope, "error")
|
||||
}
|
||||
crate::worker::WorkerParentErrorEventKind::ErrorEvent => Some(new_error_event(
|
||||
scope, message, filename, lineno, colno, error,
|
||||
)),
|
||||
};
|
||||
let Some(event) = event else { return false };
|
||||
crate::context_bootstrap::mark_event_trusted(scope, event);
|
||||
set_event_dispatch_fields(scope, worker, event);
|
||||
|
||||
let listeners = simple_object_event_listeners_snapshot(
|
||||
@@ -815,7 +790,8 @@ fn dispatch_shared_worker_error_event<'s>(
|
||||
&[event.into()],
|
||||
event,
|
||||
);
|
||||
if listener.handler_slot.as_deref() == Some(SHARED_WORKER_ONERROR_SLOT)
|
||||
if event_kind == crate::worker::WorkerParentErrorEventKind::ErrorEvent
|
||||
&& listener.handler_slot.as_deref() == Some(SHARED_WORKER_ONERROR_SLOT)
|
||||
&& let Some(returned) = callback_result
|
||||
&& v8::Local::new(scope, &returned).boolean_value(scope)
|
||||
{
|
||||
@@ -845,35 +821,6 @@ fn dispatch_shared_worker_error_event<'s>(
|
||||
dispatched
|
||||
}
|
||||
|
||||
fn new_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event_type: &str,
|
||||
cancelable: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
if let Some(event_ctor) = global
|
||||
.get(scope, v8str(scope, "Event").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
{
|
||||
let init = SharedWorkerHostEventInitDeclaration::new(cancelable)
|
||||
.bind(scope)
|
||||
.expect("SharedWorker host Event init declaration should bind");
|
||||
if let Some(event) = event_ctor.new_instance(
|
||||
scope,
|
||||
&[
|
||||
v8::String::new(scope, event_type).unwrap().into(),
|
||||
init.into(),
|
||||
],
|
||||
) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
SharedWorkerHostEventFallbackDeclaration::new(event_type.to_owned(), cancelable)
|
||||
.bind(scope)
|
||||
.expect("SharedWorker host Event fallback declaration should bind")
|
||||
}
|
||||
|
||||
fn new_error_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
message: &str,
|
||||
@@ -911,17 +858,3 @@ fn new_error_event<'s>(
|
||||
.bind(scope)
|
||||
.expect("SharedWorker host ErrorEvent fallback declaration should bind")
|
||||
}
|
||||
|
||||
fn set_error_event_details<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
message: &str,
|
||||
filename: &str,
|
||||
lineno: u32,
|
||||
colno: u32,
|
||||
error: v8::Local<'s, v8::Value>,
|
||||
) {
|
||||
SharedWorkerHostErrorEventDetailsDeclaration::new(message, filename, lineno, colno, error)
|
||||
.initialize(scope, event)
|
||||
.expect("SharedWorker host ErrorEvent details declaration should initialize");
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ pub(in crate::context_bootstrap) fn worker_constructor_callback<'s>(
|
||||
lineno: 0,
|
||||
colno: 0,
|
||||
event_kind: crate::worker::WorkerParentErrorEventKind::Event,
|
||||
phase: crate::worker::WorkerErrorPhase::Runtime,
|
||||
phase: crate::worker::WorkerErrorPhase::Bootstrap,
|
||||
source: crate::worker::WorkerErrorSource::Runtime,
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ use moli_webapi_declare::WebApiObject;
|
||||
use super::{WORKER_LISTENERS_SLOT, WORKER_ONERROR_SLOT};
|
||||
use crate::context_bootstrap::{
|
||||
dispatch_simple_event_target_event,
|
||||
events::{clear_event_dispatch_fields, set_event_dispatch_fields},
|
||||
events::{clear_event_dispatch_fields, construct_original_event, set_event_dispatch_fields},
|
||||
invoke_simple_event_listener, simple_object_event_listeners_snapshot,
|
||||
simple_object_event_remove_listener_value_for_type,
|
||||
};
|
||||
@@ -14,22 +14,6 @@ use crate::structured_clone::V8StructuredClonePayload;
|
||||
use crate::util::v8str;
|
||||
use crate::worker::{WorkerParentErrorEventKind, WorkerToParentMessage};
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct WorkerHostEventInitDeclaration {
|
||||
#[webapi(data_property, enumerable)]
|
||||
cancelable: bool,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::Event, prototype = "Object")]
|
||||
struct WorkerHostEventFallbackDeclaration {
|
||||
#[webapi(data_property, enumerable)]
|
||||
r#type: String,
|
||||
#[webapi(data_property, enumerable)]
|
||||
cancelable: bool,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct WorkerHostMessageEventInitDeclaration<'scope> {
|
||||
@@ -84,16 +68,6 @@ struct WorkerHostErrorEventFallbackDeclaration<'scope, 'text> {
|
||||
error: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, scope_lifetime = 'scope, data_properties, enumerable)]
|
||||
struct WorkerHostErrorEventDetailsDeclaration<'scope, 'text> {
|
||||
message: &'text str,
|
||||
filename: &'text str,
|
||||
lineno: u32,
|
||||
colno: u32,
|
||||
error: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_worker_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
worker: v8::Local<'s, v8::Object>,
|
||||
@@ -255,27 +229,6 @@ pub(crate) fn flush_pending_worker_messages_for_listener<'s>(
|
||||
let _ = (scope, worker);
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_worker_error_event_with_error<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
worker: v8::Local<'s, v8::Object>,
|
||||
message: &str,
|
||||
filename: &str,
|
||||
lineno: u32,
|
||||
colno: u32,
|
||||
error: v8::Local<'s, v8::Value>,
|
||||
) -> bool {
|
||||
dispatch_worker_error_event_with_kind(
|
||||
scope,
|
||||
worker,
|
||||
message,
|
||||
filename,
|
||||
lineno,
|
||||
colno,
|
||||
error,
|
||||
WorkerParentErrorEventKind::ErrorEvent,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_worker_error_event_with_kind<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
worker: v8::Local<'s, v8::Object>,
|
||||
@@ -287,15 +240,14 @@ pub(crate) fn dispatch_worker_error_event_with_kind<'s>(
|
||||
event_kind: WorkerParentErrorEventKind,
|
||||
) -> bool {
|
||||
let event = match event_kind {
|
||||
WorkerParentErrorEventKind::Event => {
|
||||
let event = new_event(scope, "error", true);
|
||||
set_error_event_details(scope, event, message, filename, lineno, colno, error);
|
||||
event
|
||||
}
|
||||
WorkerParentErrorEventKind::ErrorEvent => {
|
||||
new_error_event(scope, message, filename, lineno, colno, error)
|
||||
}
|
||||
// Fetch/parse failures fire a plain Event with the default flags.
|
||||
WorkerParentErrorEventKind::Event => construct_original_event(scope, "error"),
|
||||
WorkerParentErrorEventKind::ErrorEvent => Some(new_error_event(
|
||||
scope, message, filename, lineno, colno, error,
|
||||
)),
|
||||
};
|
||||
let Some(event) = event else { return false };
|
||||
crate::context_bootstrap::mark_event_trusted(scope, event);
|
||||
set_event_dispatch_fields(scope, worker, event);
|
||||
|
||||
let listeners =
|
||||
@@ -311,7 +263,8 @@ pub(crate) fn dispatch_worker_error_event_with_kind<'s>(
|
||||
&[event.into()],
|
||||
event,
|
||||
);
|
||||
if listener.handler_slot.as_deref() == Some(WORKER_ONERROR_SLOT)
|
||||
if event_kind == WorkerParentErrorEventKind::ErrorEvent
|
||||
&& listener.handler_slot.as_deref() == Some(WORKER_ONERROR_SLOT)
|
||||
&& let Some(returned) = callback_result
|
||||
&& v8::Local::new(scope, &returned).boolean_value(scope)
|
||||
{
|
||||
@@ -351,35 +304,6 @@ pub(crate) fn dispatch_worker_error_event_with_kind<'s>(
|
||||
!default_prevented
|
||||
}
|
||||
|
||||
fn new_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event_type: &str,
|
||||
cancelable: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
if let Some(event_ctor) = global
|
||||
.get(scope, v8str(scope, "Event").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
{
|
||||
let init = WorkerHostEventInitDeclaration::new(cancelable)
|
||||
.bind(scope)
|
||||
.expect("worker host Event init declaration should bind");
|
||||
if let Some(event) = event_ctor.new_instance(
|
||||
scope,
|
||||
&[
|
||||
v8::String::new(scope, event_type).unwrap().into(),
|
||||
init.into(),
|
||||
],
|
||||
) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
WorkerHostEventFallbackDeclaration::new(event_type.to_owned(), cancelable)
|
||||
.bind(scope)
|
||||
.expect("worker host Event fallback declaration should bind")
|
||||
}
|
||||
|
||||
fn new_message_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event_type: &str,
|
||||
@@ -441,17 +365,3 @@ fn new_error_event<'s>(
|
||||
.bind(scope)
|
||||
.expect("worker host ErrorEvent fallback declaration should bind")
|
||||
}
|
||||
|
||||
fn set_error_event_details<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
message: &str,
|
||||
filename: &str,
|
||||
lineno: u32,
|
||||
colno: u32,
|
||||
error: v8::Local<'s, v8::Value>,
|
||||
) {
|
||||
WorkerHostErrorEventDetailsDeclaration::new(message, filename, lineno, colno, error)
|
||||
.initialize(scope, event)
|
||||
.expect("worker host ErrorEvent details declaration should initialize");
|
||||
}
|
||||
|
||||
@@ -35,9 +35,8 @@ pub(in crate::context_bootstrap) use constructor::{
|
||||
|
||||
pub(super) use constructor::worker_constructor_callback;
|
||||
pub(crate) use dispatch::{
|
||||
dispatch_worker_error_event_with_error, dispatch_worker_error_event_with_kind,
|
||||
dispatch_worker_event, flush_pending_worker_messages_for_listener,
|
||||
worker_has_message_delivery_listener,
|
||||
dispatch_worker_error_event_with_kind, dispatch_worker_event,
|
||||
flush_pending_worker_messages_for_listener, worker_has_message_delivery_listener,
|
||||
};
|
||||
pub(super) use methods::{worker_post_message_callback, worker_terminate_callback};
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ __dedicatedWorkerErrorPhaseWorker.onerror = event => {
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("__dedicatedWorkerErrorPhases.join('|')")?,
|
||||
"worker:Event:bootstrap syntax:true",
|
||||
"worker:Event:undefined:false",
|
||||
"initial script errors must stop after the Worker error event"
|
||||
);
|
||||
|
||||
@@ -296,7 +296,7 @@ __dedicatedWorkerErrorPhaseWorker.onerror = event => {
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("__dedicatedWorkerErrorPhases.join('|')")?,
|
||||
"worker:Event:bootstrap syntax:true|worker:ErrorEvent:runtime boom:true|window:ErrorEvent:runtime boom",
|
||||
"worker:Event:undefined:false|worker:ErrorEvent:runtime boom:true|window:ErrorEvent:runtime boom",
|
||||
"uncanceled runtime errors must retain their owning Window propagation"
|
||||
);
|
||||
|
||||
@@ -306,6 +306,87 @@ __dedicatedWorkerErrorPhaseWorker.onerror = event => {
|
||||
.expect("DedicatedWorker bootstrap/runtime error propagation test should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn dedicated_worker_bootstrap_error_uses_intrinsic_event_without_author_hooks() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let (mut page_vm, _resource_source, _owner_wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(
|
||||
&loader,
|
||||
Url::parse("https://worker-bootstrap-event.test/").unwrap(),
|
||||
);
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
globalThis.__bootstrapEventWorker = new Worker("data:text/javascript,onmessage = () => {}");
|
||||
(() => {
|
||||
const originalEvent = Event;
|
||||
const originalEventDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'Event');
|
||||
const flags = ['bubbles', 'cancelable', 'composed'];
|
||||
const originalFlags = flags.map(name => Object.getOwnPropertyDescriptor(Object.prototype, name));
|
||||
let reads = 0;
|
||||
const poison = {configurable: true, get() { ++reads; throw new Error('author event hook'); }};
|
||||
globalThis.__bootstrapEventFailures = 'pending';
|
||||
__bootstrapEventWorker.onerror = function(event) {
|
||||
const failures = [];
|
||||
try {
|
||||
if (Object.getPrototypeOf(event) !== originalEvent.prototype) failures.push('wrong Event prototype');
|
||||
if (event.type !== 'error' || event.isTrusted !== true) failures.push('wrong type or trust');
|
||||
if (event.target !== __bootstrapEventWorker || event.currentTarget !== this || this !== __bootstrapEventWorker) failures.push('wrong dispatch target');
|
||||
if (arguments.length !== 1) failures.push('wrong callback arguments');
|
||||
for (const name of flags) if (event[name] !== false) failures.push(name);
|
||||
for (const name of ['message', 'filename', 'lineno', 'colno', 'error']) {
|
||||
if (name in event) failures.push('unexpected ' + name);
|
||||
}
|
||||
if (event.defaultPrevented) failures.push('initially canceled');
|
||||
event.preventDefault();
|
||||
if (event.defaultPrevented) failures.push('cancelable bootstrap event');
|
||||
if (reads !== 0) failures.push('called author event hooks');
|
||||
globalThis.__bootstrapEventFailures = JSON.stringify(failures);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'Event', originalEventDescriptor);
|
||||
flags.forEach((name, index) => {
|
||||
if (originalFlags[index]) Object.defineProperty(Object.prototype, name, originalFlags[index]);
|
||||
else delete Object.prototype[name];
|
||||
});
|
||||
}
|
||||
};
|
||||
Object.defineProperty(globalThis, 'Event', poison);
|
||||
for (const name of flags) Object.defineProperty(Object.prototype, name, poison);
|
||||
})()
|
||||
"#,
|
||||
)?;
|
||||
let (_worker_id, producer) = page_vm
|
||||
.vm()
|
||||
.only_dedicated_worker_client_event_producer_for_test()?;
|
||||
producer
|
||||
.send(RendererDedicatedWorkerClientEvent::Message(
|
||||
RendererDedicatedWorkerMessageEvent::Error {
|
||||
message: "bootstrap parse error".to_owned(),
|
||||
filename: "https://worker-bootstrap-event.test/broken.js".to_owned(),
|
||||
lineno: 3,
|
||||
colno: 5,
|
||||
event_kind: WorkerParentErrorEventKind::Event,
|
||||
phase: WorkerErrorPhase::Bootstrap,
|
||||
source: WorkerErrorSource::InitialScriptEvaluation,
|
||||
},
|
||||
))
|
||||
.expect("bootstrap error should enter the typed Worker source");
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::DedicatedWorkerClientEvent,
|
||||
&loader,
|
||||
)
|
||||
.await?
|
||||
);
|
||||
assert_eq!(page_vm.vm_mut().eval("__bootstrapEventFailures")?, "[]");
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("Worker bootstrap errors should use the intrinsic Event");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn dedicated_worker_relay_terminal_waits_for_both_selected_source_fifos() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
@@ -6337,7 +6337,7 @@ async fn worker_url_fetch_failure_dispatches_error_event() {
|
||||
let mut page_vm = test_page_vm_with_document_url(document_url);
|
||||
let local_executor = page_vm.local_executor.clone();
|
||||
|
||||
let error_message = local_executor
|
||||
let error_event = local_executor
|
||||
.run(async move {
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
@@ -6346,7 +6346,13 @@ async fn worker_url_fetch_failure_dispatches_error_event() {
|
||||
globalThis.__workerDone = false;
|
||||
const worker = new Worker("/missing-worker.js");
|
||||
worker.onerror = (event) => {
|
||||
globalThis.__workerError = event.message;
|
||||
globalThis.__workerError = [
|
||||
event.type,
|
||||
Object.getPrototypeOf(event) === Event.prototype,
|
||||
event.target === worker,
|
||||
event.bubbles, event.cancelable, event.composed, event.isTrusted,
|
||||
['message', 'filename', 'lineno', 'colno', 'error'].some(name => name in event)
|
||||
];
|
||||
globalThis.__workerDone = true;
|
||||
};
|
||||
})()
|
||||
@@ -6358,7 +6364,7 @@ async fn worker_url_fetch_failure_dispatches_error_event() {
|
||||
"worker url load failure should dispatch an error event",
|
||||
)
|
||||
.await?;
|
||||
page_vm.vm_mut().eval("String(globalThis.__workerError)")
|
||||
page_vm.vm_mut().eval("JSON.stringify(globalThis.__workerError)")
|
||||
})
|
||||
.await
|
||||
.expect("worker url failure test should run on owner lane");
|
||||
@@ -6366,11 +6372,9 @@ async fn worker_url_fetch_failure_dispatches_error_event() {
|
||||
server
|
||||
.await
|
||||
.expect("worker script failure server should finish");
|
||||
assert!(
|
||||
error_message.contains("HTTP request")
|
||||
&& error_message.contains("404")
|
||||
&& error_message.contains("/missing-worker.js"),
|
||||
"unexpected worker load error: {error_message}"
|
||||
assert_eq!(
|
||||
error_event,
|
||||
r#"["error",true,true,false,false,false,true,false]"#
|
||||
);
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -170,6 +170,95 @@ async fn shared_worker_error_body_leaves_reactions_for_selected_completion() {
|
||||
.expect("SharedWorker current-owner event should run through its typed executor");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn shared_worker_bootstrap_error_uses_intrinsic_event_without_author_hooks() {
|
||||
run_page_vm_async_test(async move {
|
||||
for (setup, remaining_clients) in [
|
||||
(r#"
|
||||
globalThis.__bootstrapEventWorker = new SharedWorker(
|
||||
"data:text/javascript," + encodeURIComponent("function ("),
|
||||
"intrinsic-bootstrap-event"
|
||||
);
|
||||
"#, 0),
|
||||
(r#"
|
||||
const url = "data:text/javascript,onconnect = () => {}";
|
||||
globalThis.__runningSharedWorker = new SharedWorker(url, "intrinsic-connection-event");
|
||||
globalThis.__bootstrapEventWorker = new SharedWorker(url, {
|
||||
name: "intrinsic-connection-event", type: "module"
|
||||
});
|
||||
"#, 1),
|
||||
] {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let (mut page_vm, _resource_source, mut page_wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(
|
||||
&loader,
|
||||
Url::parse("https://shared-worker-bootstrap-event.test/").unwrap(),
|
||||
);
|
||||
let mut shared_worker_wake_rx = install_shared_worker_service_wake(&page_vm);
|
||||
page_vm.vm_mut().eval(setup)?;
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
(() => {
|
||||
const originalEvent = Event;
|
||||
const originalEventDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'Event');
|
||||
const flags = ['bubbles', 'cancelable', 'composed'];
|
||||
const originalFlags = flags.map(name => Object.getOwnPropertyDescriptor(Object.prototype, name));
|
||||
let reads = 0;
|
||||
const poison = {configurable: true, get() { ++reads; throw new Error('author event hook'); }};
|
||||
globalThis.__bootstrapEventFailures = 'pending';
|
||||
__bootstrapEventWorker.onerror = function(event) {
|
||||
const failures = [];
|
||||
try {
|
||||
if (Object.getPrototypeOf(event) !== originalEvent.prototype) failures.push('wrong Event prototype');
|
||||
if (event.type !== 'error' || event.isTrusted !== true) failures.push('wrong type or trust');
|
||||
if (event.target !== __bootstrapEventWorker || event.currentTarget !== this || this !== __bootstrapEventWorker) failures.push('wrong dispatch target');
|
||||
if (arguments.length !== 1) failures.push('wrong callback arguments');
|
||||
for (const name of flags) if (event[name] !== false) failures.push(name);
|
||||
for (const name of ['message', 'filename', 'lineno', 'colno', 'error']) {
|
||||
if (name in event) failures.push('unexpected ' + name);
|
||||
}
|
||||
if (event.defaultPrevented) failures.push('initially canceled');
|
||||
event.preventDefault();
|
||||
if (event.defaultPrevented) failures.push('cancelable bootstrap event');
|
||||
if (reads !== 0) failures.push('called author event hooks');
|
||||
globalThis.__bootstrapEventFailures = JSON.stringify(failures);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'Event', originalEventDescriptor);
|
||||
flags.forEach((name, index) => {
|
||||
if (originalFlags[index]) Object.defineProperty(Object.prototype, name, originalFlags[index]);
|
||||
else delete Object.prototype[name];
|
||||
});
|
||||
}
|
||||
};
|
||||
Object.defineProperty(globalThis, 'Event', poison);
|
||||
for (const name of flags) Object.defineProperty(Object.prototype, name, poison);
|
||||
})()
|
||||
"#,
|
||||
)?;
|
||||
wait_for_shared_worker_client_event(
|
||||
&mut page_vm,
|
||||
&mut shared_worker_wake_rx,
|
||||
&mut page_wake_rx,
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::SharedWorkerClientEvent,
|
||||
&loader,
|
||||
)
|
||||
.await?
|
||||
);
|
||||
assert_eq!(page_vm.vm_mut().eval("__bootstrapEventFailures")?, "[]");
|
||||
assert_eq!(page_vm.vm().shared_worker_client_count_for_test(), remaining_clients);
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("SharedWorker bootstrap errors should use the intrinsic Event");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn shared_worker_nonterminal_error_runs_through_selected_completion_and_retains_endpoint() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
@@ -2914,13 +2914,9 @@ async fn worker_pending_activity_diagnostics_split_loading_and_running_worker_is
|
||||
#[tokio::test]
|
||||
async fn external_dedicated_module_worker_retains_creator_csp_for_static_imports() {
|
||||
run_page_vm_async_test(async move {
|
||||
let (dependency_base_url, dependency_server) = spawn_path_response_http_server(vec![(
|
||||
"/dependency.js",
|
||||
"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *",
|
||||
"export const value = 'unexpected';".to_owned(),
|
||||
Duration::ZERO,
|
||||
)])
|
||||
.await;
|
||||
let (dependency_base_url, mut dependency_request, dependency_server) =
|
||||
spawn_shared_worker_script_capture_http_server("export const value = 'unexpected';")
|
||||
.await;
|
||||
let dependency_url = format!("{dependency_base_url}/dependency.js");
|
||||
let worker_source = format!(
|
||||
r#"
|
||||
@@ -2962,7 +2958,13 @@ async fn external_dedicated_module_worker_retains_creator_csp_for_static_imports
|
||||
};
|
||||
worker.onerror = event => {
|
||||
event.preventDefault();
|
||||
globalThis.__moduleWorkerCspResult = "error:" + event.message;
|
||||
globalThis.__moduleWorkerCspResult = JSON.stringify([
|
||||
event.type,
|
||||
Object.getPrototypeOf(event) === Event.prototype,
|
||||
event.bubbles, event.cancelable, event.composed, event.isTrusted,
|
||||
event.defaultPrevented,
|
||||
['message', 'filename', 'lineno', 'colno', 'error'].some(name => name in event)
|
||||
]);
|
||||
globalThis.__moduleWorkerCspDone = true;
|
||||
};
|
||||
})()
|
||||
@@ -2977,9 +2979,10 @@ async fn external_dedicated_module_worker_retains_creator_csp_for_static_imports
|
||||
let result = page_vm
|
||||
.vm_mut()
|
||||
.eval("globalThis.__moduleWorkerCspResult")?;
|
||||
assert!(
|
||||
result.starts_with("error:") && result.contains("Content Security Policy"),
|
||||
"static module import should be blocked by creator worker-src: {result:?}"
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"["error",true,false,false,false,true,false,false]"#,
|
||||
"blocked static import should fire a bootstrap Event before evaluation"
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -2988,6 +2991,10 @@ async fn external_dedicated_module_worker_retains_creator_csp_for_static_imports
|
||||
server
|
||||
.await
|
||||
.expect("external module worker CSP server should finish");
|
||||
assert!(
|
||||
matches!(dependency_request.try_recv(), Err(tokio::sync::oneshot::error::TryRecvError::Empty)),
|
||||
"creator CSP must block the dependency before any HTTP request is sent"
|
||||
);
|
||||
dependency_server.abort();
|
||||
})
|
||||
.await;
|
||||
@@ -3935,7 +3942,6 @@ async fn worker_script_load_failure_does_not_dispatch_window_error() {
|
||||
)])
|
||||
.await;
|
||||
let document_url = Url::parse(&format!("{base_url}/page.html")).expect("document url");
|
||||
let missing_worker_url = format!("{base_url}/does-not-exist.js");
|
||||
let mut page_vm = test_page_vm_with_document_url(document_url);
|
||||
let local_executor = page_vm.local_executor.clone();
|
||||
|
||||
@@ -3952,7 +3958,17 @@ async fn worker_script_load_failure_does_not_dispatch_window_error() {
|
||||
});
|
||||
const worker = new Worker("/does-not-exist.js");
|
||||
worker.onerror = event => {
|
||||
globalThis.__missingWorkerEvents.push("worker:" + event.message);
|
||||
event.preventDefault();
|
||||
globalThis.__missingWorkerEvents.push({
|
||||
target: "worker",
|
||||
type: event.type,
|
||||
constructor: event.constructor.name,
|
||||
trusted: event.isTrusted,
|
||||
cancelable: event.cancelable,
|
||||
defaultPrevented: event.defaultPrevented,
|
||||
hasErrorDetails: ["message", "filename", "lineno", "colno", "error"]
|
||||
.some(name => name in event)
|
||||
});
|
||||
globalThis.__missingWorkerDone = true;
|
||||
};
|
||||
})()
|
||||
@@ -3973,9 +3989,7 @@ async fn worker_script_load_failure_does_not_dispatch_window_error() {
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("JSON.stringify(globalThis.__missingWorkerEvents)")?,
|
||||
format!(
|
||||
r#"["worker:HTTP request `{missing_worker_url}` returned 404 Not Found"]"#
|
||||
)
|
||||
r#"[{"target":"worker","type":"error","constructor":"Event","trusted":true,"cancelable":false,"defaultPrevented":false,"hasErrorDetails":false}]"#
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -4006,7 +4020,10 @@ async fn shared_worker_rejects_cross_origin_redirected_script() {
|
||||
globalThis.__sharedWorkerRedirectDone = false;
|
||||
const worker = new SharedWorker("/redirect-source.js", "cross-origin-redirect-script");
|
||||
worker.onerror = (event) => {
|
||||
globalThis.__sharedWorkerRedirectOutcome = "error:" + event.message;
|
||||
globalThis.__sharedWorkerRedirectOutcome = JSON.stringify([
|
||||
event.type, event.constructor.name, event.cancelable,
|
||||
"message" in event, event.isTrusted
|
||||
]);
|
||||
globalThis.__sharedWorkerRedirectDone = true;
|
||||
};
|
||||
worker.port.onmessage = (event) => {
|
||||
@@ -4024,9 +4041,10 @@ async fn shared_worker_rejects_cross_origin_redirected_script() {
|
||||
)
|
||||
.await?;
|
||||
let outcome = page_vm.vm_mut().eval("globalThis.__sharedWorkerRedirectOutcome")?;
|
||||
assert!(
|
||||
outcome.starts_with("error:"),
|
||||
"redirected cross-origin script must not execute, got {outcome:?}"
|
||||
assert_eq!(
|
||||
outcome,
|
||||
r#"["error","Event",false,false,true]"#,
|
||||
"redirected cross-origin script must fail with a plain Event"
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -4933,7 +4951,7 @@ async fn shared_worker_terminal_error_forgets_page_client_wrapper_tracking() {
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("JSON.stringify(globalThis.__sharedWorkerTerminalErrorRecord)")?,
|
||||
r#"{"type":"error","cancelable":true,"hasMessage":true}"#
|
||||
r#"{"type":"error","cancelable":false,"hasMessage":false}"#
|
||||
);
|
||||
wait_for_shared_worker_client_count(
|
||||
&mut page_vm,
|
||||
|
||||
@@ -5095,8 +5095,9 @@ async fn dedicated_worker_script_load_failure_does_not_dispatch_window_error() {
|
||||
globalThis.__lm_worker_script_error_events.push([
|
||||
"worker",
|
||||
event.type,
|
||||
String(event.message).includes("HTTP request"),
|
||||
String(event.filename).endsWith("/does-not-exist.js")
|
||||
Object.getPrototypeOf(event) === Event.prototype,
|
||||
event.bubbles, event.cancelable, event.composed, event.isTrusted,
|
||||
['message', 'filename', 'lineno', 'colno', 'error'].some(name => name in event)
|
||||
].join(":"));
|
||||
};
|
||||
return "installed";
|
||||
@@ -5129,7 +5130,9 @@ async fn dedicated_worker_script_load_failure_does_not_dispatch_window_error() {
|
||||
.expect("worker script load failure events should evaluate");
|
||||
assert_eq!(
|
||||
renderer_json_value(events),
|
||||
Some(serde_json::json!("[\"worker:error:true:true\"]")),
|
||||
Some(serde_json::json!(
|
||||
"[\"worker:error:true:false:false:false:true:false\"]"
|
||||
)),
|
||||
"worker script load failure must not bubble to window.onerror"
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! DedicatedWorker error-event dispatch and propagation.
|
||||
//!
|
||||
//! This is deliberately separate from ordinary Worker message dispatch.
|
||||
//! `dispatch_worker_error_event_with_*` retains the established inner
|
||||
//! `dispatch_worker_error_event_with_kind` retains the established inner
|
||||
//! checkpoint used to settle listener cancellation before deciding whether an
|
||||
//! uncanceled Worker error propagates to the owning Window. That semantic
|
||||
//! checkpoint is not the HTML task-end checkpoint: the selected Page-task
|
||||
@@ -16,7 +16,7 @@ pub(super) fn dispatch_script_load_failure<'s>(
|
||||
script_url: &str,
|
||||
) {
|
||||
let worker_error = v8::null(scope).into();
|
||||
crate::context_bootstrap::dispatch_worker_error_event_with_error(
|
||||
crate::context_bootstrap::dispatch_worker_error_event_with_kind(
|
||||
scope,
|
||||
worker,
|
||||
error_message,
|
||||
@@ -24,6 +24,7 @@ pub(super) fn dispatch_script_load_failure<'s>(
|
||||
0,
|
||||
0,
|
||||
worker_error,
|
||||
crate::worker::WorkerParentErrorEventKind::Event,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ fn connect_with_runtime_service(
|
||||
client.send_error(
|
||||
"Failed to connect SharedWorker: loading host is unavailable.",
|
||||
params.key.script_url(),
|
||||
WorkerParentErrorEventKind::ErrorEvent,
|
||||
WorkerParentErrorEventKind::Event,
|
||||
);
|
||||
client.close_ports();
|
||||
runtime_service.remove_client(client_id);
|
||||
@@ -88,7 +88,7 @@ fn connect_with_runtime_service(
|
||||
client.send_error(
|
||||
shared_worker_compatibility_error_message(&error),
|
||||
params.key.script_url(),
|
||||
WorkerParentErrorEventKind::ErrorEvent,
|
||||
WorkerParentErrorEventKind::Event,
|
||||
);
|
||||
client.close_ports();
|
||||
client_id
|
||||
|
||||
@@ -140,7 +140,7 @@ impl RendererSharedWorkerHost {
|
||||
client_id,
|
||||
"Failed to connect SharedWorker: worker runtime is unavailable.",
|
||||
failure_filename,
|
||||
WorkerParentErrorEventKind::ErrorEvent,
|
||||
WorkerParentErrorEventKind::Event,
|
||||
);
|
||||
false
|
||||
}
|
||||
@@ -156,7 +156,7 @@ impl RendererSharedWorkerHost {
|
||||
client_id,
|
||||
"Failed to connect SharedWorker: worker runtime is unavailable.",
|
||||
"",
|
||||
WorkerParentErrorEventKind::ErrorEvent,
|
||||
WorkerParentErrorEventKind::Event,
|
||||
);
|
||||
failed.push(client_id);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ fn finish_loading_with_runtime_service(
|
||||
clients,
|
||||
message,
|
||||
params.key.script_url(),
|
||||
WorkerParentErrorEventKind::ErrorEvent,
|
||||
WorkerParentErrorEventKind::Event,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -335,6 +335,7 @@ mod tests {
|
||||
SharedWorkerClientEvent::Error(error)
|
||||
if error.endpoint_disposition()
|
||||
== SharedWorkerClientEndpointDisposition::Retire
|
||||
&& error.event_kind() == WorkerParentErrorEventKind::Event
|
||||
));
|
||||
assert!(
|
||||
message_port_owner
|
||||
@@ -665,6 +666,7 @@ mod tests {
|
||||
SharedWorkerClientEvent::Error(error)
|
||||
if error.endpoint_disposition()
|
||||
== SharedWorkerClientEndpointDisposition::Retire
|
||||
&& error.event_kind() == WorkerParentErrorEventKind::Event
|
||||
));
|
||||
assert!(
|
||||
message_port_owner
|
||||
|
||||
@@ -2957,12 +2957,16 @@ pub(super) fn dispatch_nested_worker_event(
|
||||
lineno,
|
||||
colno,
|
||||
event_kind,
|
||||
phase,
|
||||
..
|
||||
} => {
|
||||
let unhandled = crate::context_bootstrap::dispatch_worker_event(scope, worker, message);
|
||||
// Bootstrap failures only fire an Event at the child Worker. Only
|
||||
// uncanceled runtime errors propagate to its owner's global scope.
|
||||
let propagate = unhandled && *phase == super::handle::WorkerErrorPhase::Runtime;
|
||||
NestedWorkerDispatchResult {
|
||||
dispatched: true,
|
||||
unhandled_error: unhandled.then(|| NestedWorkerUnhandledError {
|
||||
unhandled_error: propagate.then(|| NestedWorkerUnhandledError {
|
||||
message: error_message.clone(),
|
||||
filename: filename.clone(),
|
||||
lineno: *lineno,
|
||||
|
||||
@@ -9335,17 +9335,25 @@ async fn nested_worker_script_load_failure_is_async_error_event() {
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
let result = "not-run";
|
||||
let globalErrors = 0;
|
||||
onerror = () => { ++globalErrors; return true; };
|
||||
try {
|
||||
const child = new Worker("missing-child.js");
|
||||
child.onerror = event => {
|
||||
event.preventDefault();
|
||||
postMessage({
|
||||
const observation = {
|
||||
constructed: result === "constructed",
|
||||
type: event.type,
|
||||
messageIsNonEmpty: event.message.length > 0,
|
||||
filename: event.filename
|
||||
});
|
||||
close();
|
||||
intrinsicEvent: Object.getPrototypeOf(event) === Event.prototype,
|
||||
target: event.target === child,
|
||||
trusted: event.isTrusted,
|
||||
flags: [event.bubbles, event.cancelable, event.composed, event.defaultPrevented],
|
||||
hasErrorDetails: ['message', 'filename', 'lineno', 'colno', 'error'].some(name => name in event)
|
||||
};
|
||||
setTimeout(() => {
|
||||
postMessage({ ...observation, globalErrors });
|
||||
close();
|
||||
}, 0);
|
||||
};
|
||||
result = "constructed";
|
||||
} catch (error) {
|
||||
@@ -9363,9 +9371,7 @@ async fn nested_worker_script_load_failure_is_async_error_event() {
|
||||
.expect("channel closed");
|
||||
assert_eq!(
|
||||
expect_post_json(msg),
|
||||
format!(
|
||||
r#"{{"constructed":true,"type":"error","messageIsNonEmpty":true,"filename":"{base_url}/missing-child.js"}}"#
|
||||
)
|
||||
r#"{"constructed":true,"type":"error","intrinsicEvent":true,"target":true,"trusted":true,"flags":[false,false,false,false],"hasErrorDetails":false,"globalErrors":0}"#
|
||||
);
|
||||
timeout(TIMEOUT, server)
|
||||
.await
|
||||
@@ -9373,6 +9379,48 @@ async fn nested_worker_script_load_failure_is_async_error_event() {
|
||||
.expect("nested worker script server should finish");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nested_worker_parse_errors_use_intrinsic_events_without_global_propagation() {
|
||||
ensure_v8();
|
||||
for child_type in ["classic", "module"] {
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
const originalEvent = Event;
|
||||
let globalErrors = 0;
|
||||
let authorReads = 0;
|
||||
onerror = () => { ++globalErrors; return true; };
|
||||
const child = new Worker("data:text/javascript,function%20(", { type: "CHILD_TYPE" });
|
||||
child.onerror = event => {
|
||||
event.preventDefault();
|
||||
const observation = {
|
||||
type: event.type,
|
||||
intrinsicEvent: Object.getPrototypeOf(event) === originalEvent.prototype,
|
||||
target: event.target === child,
|
||||
trusted: event.isTrusted,
|
||||
flags: [event.bubbles, event.cancelable, event.composed, event.defaultPrevented],
|
||||
hasErrorDetails: ['message', 'filename', 'lineno', 'colno', 'error'].some(name => name in event)
|
||||
};
|
||||
setTimeout(() => {
|
||||
postMessage({ ...observation, globalErrors, authorReads });
|
||||
close();
|
||||
}, 0);
|
||||
};
|
||||
Object.defineProperty(globalThis, "Event", {
|
||||
configurable: true,
|
||||
get() { ++authorReads; throw new Error("author Event getter"); }
|
||||
});
|
||||
"#
|
||||
.replace("CHILD_TYPE", child_type),
|
||||
"test://nested_worker_parse_error".into(),
|
||||
);
|
||||
assert_eq!(
|
||||
recv_post_json(&mut handle).await,
|
||||
r#"{"type":"error","intrinsicEvent":true,"target":true,"trusted":true,"flags":[false,false,false,false],"hasErrorDetails":false,"globalErrors":0,"authorReads":0}"#,
|
||||
"{child_type} child bootstrap must not invoke author hooks or parent onerror"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_onmessage_exception_routes_through_worker_global_onerror() {
|
||||
ensure_v8();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function shared_worker_url(source) {
|
||||
return "data:text/javascript," + encodeURIComponent(source);
|
||||
@@ -295,8 +296,7 @@ promise_test(async function () {
|
||||
new SharedWorker(url, { name: "type-check", type: "classic" });
|
||||
const worker = new SharedWorker(url, { name: "type-check", type: "module" });
|
||||
const event = await next_worker_error(worker);
|
||||
assert_equals(event.type, "error");
|
||||
assert_true(String(event.message).includes("script type"));
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
}, "SharedWorker type mismatch for an existing key dispatches error");
|
||||
|
||||
promise_test(async function () {
|
||||
@@ -304,8 +304,7 @@ promise_test(async function () {
|
||||
new SharedWorker(url, { name: "credentials-check", credentials: "same-origin" });
|
||||
const worker = new SharedWorker(url, { name: "credentials-check", credentials: "include" });
|
||||
const event = await next_worker_error(worker);
|
||||
assert_equals(event.type, "error");
|
||||
assert_true(String(event.message).includes("credentials"));
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
}, "SharedWorker credentials mismatch for an existing key dispatches error");
|
||||
|
||||
promise_test(async function () {
|
||||
@@ -313,8 +312,7 @@ promise_test(async function () {
|
||||
let onerrorCalled = false;
|
||||
worker.onerror = function (event) {
|
||||
onerrorCalled = true;
|
||||
assert_equals(event.type, "error");
|
||||
assert_true(String(event.message).includes("shared worker script"));
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
};
|
||||
await next_worker_error(worker);
|
||||
assert_true(onerrorCalled, "onerror handler is invoked for script load failure");
|
||||
@@ -326,8 +324,7 @@ promise_test(async function () {
|
||||
"invalid-data-url-script",
|
||||
);
|
||||
const event = await next_worker_error(worker);
|
||||
assert_equals(event.type, "error");
|
||||
assert_true(String(event.message).includes("data URL"));
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
}, "SharedWorker invalid data URL script dispatches an async error event");
|
||||
|
||||
test(function () {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
function assert_worker_bootstrap_error(event, worker) {
|
||||
assert_equals(Object.getPrototypeOf(event), Event.prototype,
|
||||
"bootstrap failure uses Event, not ErrorEvent");
|
||||
assert_equals(event.type, "error");
|
||||
assert_equals(event.target, worker);
|
||||
assert_true(event.isTrusted);
|
||||
assert_false(event.bubbles);
|
||||
assert_false(event.cancelable);
|
||||
assert_false(event.composed);
|
||||
assert_false(event.defaultPrevented);
|
||||
event.preventDefault();
|
||||
assert_false(event.defaultPrevented, "bootstrap errors cannot be canceled");
|
||||
for (const name of ["message", "filename", "lineno", "colno", "error"]) {
|
||||
assert_false(name in event, "bootstrap Event has no ErrorEvent." + name);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function wait(ms) {
|
||||
return new Promise(function (resolve) {
|
||||
@@ -39,19 +40,7 @@ promise_test(async function () {
|
||||
"worker load failure onerror",
|
||||
);
|
||||
|
||||
assert_equals(event.type, "error", "load failures should dispatch error events");
|
||||
assert_true(
|
||||
event.message.indexOf("HTTP request") !== -1,
|
||||
"load failure should mention the HTTP request",
|
||||
);
|
||||
assert_true(
|
||||
event.message.indexOf("404") !== -1,
|
||||
"load failure should surface the HTTP status",
|
||||
);
|
||||
assert_true(
|
||||
event.message.indexOf("/wpt/ported/worker/resources/missing-worker.js") !== -1,
|
||||
"load failure should mention the missing worker URL",
|
||||
);
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
} finally {
|
||||
window.onerror = previousOnerror;
|
||||
worker.terminate();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function worker_url(source) {
|
||||
return "data:text/javascript," + encodeURIComponent(source);
|
||||
@@ -23,7 +24,7 @@ function wait_for(promise, description) {
|
||||
]);
|
||||
}
|
||||
|
||||
async function assert_module_parse_failure(source, expectedFragments, description) {
|
||||
async function assert_module_parse_failure(source, description) {
|
||||
const previousOnerror = window.onerror;
|
||||
let messageSeen = false;
|
||||
const worker = new Worker(worker_url(source), { type: "module" });
|
||||
@@ -46,11 +47,7 @@ async function assert_module_parse_failure(source, expectedFragments, descriptio
|
||||
"module worker " + description + " early error",
|
||||
);
|
||||
|
||||
assert_equals(event.type, "error", "module parse failure should dispatch an error event");
|
||||
const lowerMessage = String(event.message).toLowerCase();
|
||||
assert_true(expectedFragments.some(function (fragment) {
|
||||
return lowerMessage.indexOf(fragment.toLowerCase()) !== -1;
|
||||
}), "parse failure should expose a syntax error message");
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
assert_false(messageSeen, "module evaluation should not continue after the early error");
|
||||
} finally {
|
||||
window.onerror = previousOnerror;
|
||||
@@ -63,14 +60,14 @@ promise_test(async function () {
|
||||
"return;",
|
||||
"postMessage('unexpected');",
|
||||
].join("\n");
|
||||
await assert_module_parse_failure(topLevelSource, ["return", "SyntaxError"], "top-level return");
|
||||
await assert_module_parse_failure(topLevelSource, "top-level return");
|
||||
|
||||
const withSource = [
|
||||
"with ({ value: 1 }) {",
|
||||
" postMessage(value);",
|
||||
"}",
|
||||
].join("\n");
|
||||
await assert_module_parse_failure(withSource, ["with", "strict", "SyntaxError"], "with statement");
|
||||
await assert_module_parse_failure(withSource, "with statement");
|
||||
|
||||
const deleteSource = [
|
||||
"var target = 1;",
|
||||
@@ -79,7 +76,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_parse_failure(
|
||||
deleteSource,
|
||||
["delete", "strict", "SyntaxError"],
|
||||
"delete identifier",
|
||||
);
|
||||
|
||||
@@ -94,7 +90,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_parse_failure(
|
||||
dependencySource,
|
||||
["with", "strict", "SyntaxError"],
|
||||
"dependency with statement",
|
||||
);
|
||||
}, "Dedicated module workers reject parse-time early errors before evaluation");
|
||||
|
||||
+12
-6
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function worker_url(source) {
|
||||
return "data:text/javascript," + encodeURIComponent(source);
|
||||
@@ -39,11 +40,16 @@ async function assert_json_module_failure(source, expectedFragments, description
|
||||
}),
|
||||
]);
|
||||
|
||||
assert_equals(event.type, "error", "JSON module attribute failure should dispatch an error event");
|
||||
const lowerMessage = String(event.message).toLowerCase();
|
||||
assert_true(expectedFragments.every(function (fragment) {
|
||||
return lowerMessage.indexOf(fragment.toLowerCase()) !== -1;
|
||||
}), description + " should expose the JSON import-attributes reason; got: " + event.message);
|
||||
if (expectedFragments === null) {
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
} else {
|
||||
// JSON decoding remains an evaluation error on the current loader.
|
||||
assert_equals(event.type, "error", "JSON module attribute failure should dispatch an error event");
|
||||
const lowerMessage = String(event.message).toLowerCase();
|
||||
assert_true(expectedFragments.every(function (fragment) {
|
||||
return lowerMessage.indexOf(fragment.toLowerCase()) !== -1;
|
||||
}), description + " should expose the JSON import-attributes reason; got: " + event.message);
|
||||
}
|
||||
assert_false(messageSeen, "module evaluation should not continue after JSON import-attributes failure");
|
||||
} finally {
|
||||
window.onerror = previousOnerror;
|
||||
@@ -61,7 +67,7 @@ promise_test(async function () {
|
||||
"import config from " + JSON.stringify(jsonUrl) + ";",
|
||||
"postMessage(config);",
|
||||
].join("\n"),
|
||||
["syntax"],
|
||||
null,
|
||||
"static JSON import without type",
|
||||
);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function worker_url(source) {
|
||||
return "data:text/javascript," + encodeURIComponent(source);
|
||||
@@ -17,7 +18,7 @@ function next_message(worker) {
|
||||
});
|
||||
}
|
||||
|
||||
async function assert_worker_error(source, expectedFragments, description) {
|
||||
async function assert_worker_error(source, description) {
|
||||
const previousOnerror = window.onerror;
|
||||
let messageSeen = false;
|
||||
const worker = new Worker(worker_url(source), { type: "module" });
|
||||
@@ -37,11 +38,7 @@ async function assert_worker_error(source, expectedFragments, description) {
|
||||
};
|
||||
});
|
||||
|
||||
assert_equals(event.type, "error", description + " should dispatch an error event");
|
||||
const lowerMessage = String(event.message).toLowerCase();
|
||||
assert_true(expectedFragments.every(function (fragment) {
|
||||
return lowerMessage.indexOf(fragment.toLowerCase()) !== -1;
|
||||
}), description + " should expose the expected error reason");
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
assert_false(messageSeen, description + " should stop module evaluation");
|
||||
} finally {
|
||||
window.onerror = previousOnerror;
|
||||
@@ -107,7 +104,6 @@ promise_test(async function () {
|
||||
"import config from " + JSON.stringify(jsonUrl) + ";",
|
||||
"postMessage(config);",
|
||||
].join("\n"),
|
||||
["json"],
|
||||
"HTTP JSON MIME without type=json",
|
||||
);
|
||||
|
||||
@@ -116,7 +112,6 @@ promise_test(async function () {
|
||||
"import value from " + JSON.stringify(javascriptJsonPathUrl) + " with { type: 'json' };",
|
||||
"postMessage(value);",
|
||||
].join("\n"),
|
||||
["json"],
|
||||
"HTTP JavaScript MIME with type=json",
|
||||
);
|
||||
|
||||
@@ -125,7 +120,6 @@ promise_test(async function () {
|
||||
"import value from " + JSON.stringify(plainJsonUrl) + " with { type: 'json' };",
|
||||
"postMessage(value);",
|
||||
].join("\n"),
|
||||
["json"],
|
||||
"HTTP text/plain JSON-like body with type=json",
|
||||
);
|
||||
}, "Dedicated module workers reject HTTP JSON import-attribute mismatches by Content-Type");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function worker_url(source) {
|
||||
return "data:text/javascript," + encodeURIComponent(source);
|
||||
@@ -23,7 +24,7 @@ function wait_for(promise, description) {
|
||||
]);
|
||||
}
|
||||
|
||||
async function assert_module_link_failure(source, expectedFragments, description) {
|
||||
async function assert_module_link_failure(source, description) {
|
||||
const previousOnerror = window.onerror;
|
||||
let messageSeen = false;
|
||||
const worker = new Worker(worker_url(source), { type: "module" });
|
||||
@@ -46,11 +47,7 @@ async function assert_module_link_failure(source, expectedFragments, description
|
||||
"module worker " + description + " link error",
|
||||
);
|
||||
|
||||
assert_equals(event.type, "error", "module link failure should dispatch an error event");
|
||||
const lowerMessage = String(event.message).toLowerCase();
|
||||
assert_true(expectedFragments.some(function (fragment) {
|
||||
return lowerMessage.indexOf(fragment.toLowerCase()) !== -1;
|
||||
}), "link failure should expose a syntax/link error message");
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
assert_false(messageSeen, "module evaluation should not continue after the link failure");
|
||||
} finally {
|
||||
window.onerror = previousOnerror;
|
||||
@@ -67,7 +64,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_link_failure(
|
||||
missingNamedSource,
|
||||
["missing export", "syntaxerror"],
|
||||
"missing named import",
|
||||
);
|
||||
|
||||
@@ -77,7 +73,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_link_failure(
|
||||
missingDefaultSource,
|
||||
["missing export", "syntaxerror"],
|
||||
"missing default import",
|
||||
);
|
||||
|
||||
@@ -90,7 +85,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_link_failure(
|
||||
missingReexportSource,
|
||||
["missing export", "syntaxerror"],
|
||||
"missing re-export",
|
||||
);
|
||||
}, "Dedicated module workers reject missing static import exports before evaluation");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<script src="/wpt/resources/testharness.js"></script>
|
||||
<script src="/wpt/resources/testharnessreport.js"></script>
|
||||
<script src="/wpt/resources/moli-wpt-adapter.js"></script>
|
||||
<script src="/wpt/ported/worker/resources/assert-bootstrap-error.js"></script>
|
||||
<script>
|
||||
function worker_url(source) {
|
||||
return "data:text/javascript," + encodeURIComponent(source);
|
||||
@@ -23,7 +24,7 @@ function wait_for(promise, description) {
|
||||
]);
|
||||
}
|
||||
|
||||
async function assert_module_graph_failure(workerUrl, expectedFragments, description) {
|
||||
async function assert_module_graph_failure(workerUrl, description) {
|
||||
const previousOnerror = window.onerror;
|
||||
let messageSeen = false;
|
||||
const worker = new Worker(workerUrl, { type: "module" });
|
||||
@@ -46,11 +47,7 @@ async function assert_module_graph_failure(workerUrl, expectedFragments, descrip
|
||||
"module worker " + description + " graph failure",
|
||||
);
|
||||
|
||||
assert_equals(event.type, "error", "module graph failure should dispatch an error event");
|
||||
const lowerMessage = String(event.message).toLowerCase();
|
||||
assert_true(expectedFragments.some(function (fragment) {
|
||||
return lowerMessage.indexOf(fragment.toLowerCase()) !== -1;
|
||||
}), "graph failure should expose the dependency load or resolution reason");
|
||||
assert_worker_bootstrap_error(event, worker);
|
||||
assert_false(messageSeen, "module evaluation should not continue after the graph failure");
|
||||
} finally {
|
||||
window.onerror = previousOnerror;
|
||||
@@ -65,7 +62,6 @@ promise_test(async function () {
|
||||
).href;
|
||||
await assert_module_graph_failure(
|
||||
missingHttpMainUrl,
|
||||
["404", "not found"],
|
||||
"HTTP dependency 404",
|
||||
);
|
||||
|
||||
@@ -75,7 +71,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_graph_failure(
|
||||
worker_url(unsupportedSchemeSource),
|
||||
["unsupported scheme", "ftp"],
|
||||
"unsupported dependency scheme",
|
||||
);
|
||||
|
||||
@@ -85,7 +80,6 @@ promise_test(async function () {
|
||||
].join("\n");
|
||||
await assert_module_graph_failure(
|
||||
worker_url(invalidSpecifierSource),
|
||||
["failed to resolve", "invalid"],
|
||||
"invalid dependency specifier",
|
||||
);
|
||||
}, "Dedicated module workers report dependency load and resolution failures before evaluation");
|
||||
|
||||
Reference in New Issue
Block a user