diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 61cbcd52d7..903cd39062 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2993,7 +2993,6 @@ navigation-api/focus-reset/autofocus.html?delayed-checkpoint navigation-api/focus-reset/autofocus.html?same-checkpoint navigation-api/navigate-event/defer/tentative/defer-same-document.html navigation-api/navigate-event/intercept-detach-multiple.html -navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html navigation-api/navigate-event/traverseTo-detach.html navigation-api/navigation-methods/reload-service-worker-fetch-event.html navigation-api/navigation-methods/return-value/back-204-205-download.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 3b105623fb..9007a92eef 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -3758,6 +3758,7 @@ dom/abort/abort-signal-any-crash.html dom/abort/abort-signal-timeout.html dom/abort/crashtests/any-on-abort.html dom/abort/crashtests/timeout-close.html +dom/abort/event.any.js?moli-wpt-any=window dom/abort/reason-constructor.html dom/attributes-are-nodes.html dom/collections/HTMLCollection-as-prototype.html @@ -7891,6 +7892,7 @@ navigation-api/navigate-event/navigate-window-open.html navigation-api/navigate-event/navigatesuccess-cross-document.html navigation-api/navigate-event/navigatesuccess-same-document.html navigation-api/navigate-event/navigation-back-same-document-preventDefault.html +navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html navigation-api/navigate-event/replaceState-inside-back-handler.html diff --git a/moli-core/tests/fixtures/runtime/navigation_event_realms.html b/moli-core/tests/fixtures/runtime/navigation_event_realms.html new file mode 100644 index 0000000000..bde4dc6763 --- /dev/null +++ b/moli-core/tests/fixtures/runtime/navigation_event_realms.html @@ -0,0 +1,143 @@ + diff --git a/moli-core/tests/history_event_realms.rs b/moli-core/tests/history_event_realms.rs new file mode 100644 index 0000000000..28a75f2a0b --- /dev/null +++ b/moli-core/tests/history_event_realms.rs @@ -0,0 +1,95 @@ +use anyhow::Result; +use moli_core::runtime::{Browser, BrowserConfig}; +use moli_test_support::FixtureServer; +use serde_json::{Value, json}; +use tokio::time::Duration; +use url::Url; + +async fn navigation_events(scenario: &str, target: &str, constructors: &str) -> Result { + let markup = include_str!("fixtures/runtime/navigation_event_realms.html") + .replace("__SCENARIO__", &serde_json::to_string(scenario)?) + .replace("__TARGET__", &serde_json::to_string(target)?) + .replace("__CONSTRUCTORS__", &serde_json::to_string(constructors)?); + let server = FixtureServer::spawn().await?; + let browser = Browser::new(BrowserConfig::default())?; + let mut url = Url::parse(&server.url("/compat/child-dynamic-markup-document"))?; + url.query_pairs_mut().append_pair("markup", &markup); + let result = tokio::time::timeout(Duration::from_secs(10), async { + let mut page = browser.fetch(url.as_str()).await?; + page.evaluate_runtime_expression_with_await_async( + "finished.then(value => JSON.stringify(value))", + true, + ) + .await + }) + .await??; + let result = serde_json::from_str(result["value"].as_str().unwrap())?; + server.shutdown().await; + Ok(result) +} + +#[tokio::test(flavor = "multi_thread")] +async fn cross_window_navigation_events_use_the_target_realm_and_native_constructors() -> Result<()> +{ + for constructors in ["original", "overwritten"] { + for target in ["top", "child"] { + for scenario in ["success", "reject"] { + let events = if scenario == "success" { + json!([ + "navigate", + "currententrychange", + "navigatesuccess", + "navigate", + "currententrychange", + "dispose", + "navigatesuccess", + "currententrychange", + "navigate", + "currententrychange", + "navigatesuccess" + ]) + } else { + json!(["navigate", "currententrychange", "abort", "navigateerror"]) + }; + assert_eq!( + navigation_events(scenario, target, constructors).await?, + json!({"errors": [], "reads": 0, "events": events}), + "{scenario}/{target}/{constructors}" + ); + } + } + } + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn child_joint_history_events_use_the_parent_event_target_realm() -> Result<()> { + for constructors in ["original", "overwritten"] { + for scenario in ["joint-cancel", "joint-prune"] { + let events = if scenario == "joint-cancel" { + json!(["navigate", "abort", "navigateerror"]) + } else { + json!(["dispose", "dispose"]) + }; + assert_eq!( + navigation_events(scenario, "top", constructors).await?, + json!({"errors": [], "reads": 0, "events": events}), + "{scenario}/{constructors}" + ); + } + } + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn parent_initiated_child_navigation_constructs_events_in_the_retiring_child_realm() +-> Result<()> { + for constructors in ["original", "overwritten"] { + assert_eq!( + navigation_events("cross-document", "child", constructors).await?, + json!({"errors": [], "reads": 0, "events": ["navigate"]}), + "{constructors}" + ); + } + Ok(()) +} diff --git a/moli-renderer-v8/src/context_bootstrap/history_mutation.rs b/moli-renderer-v8/src/context_bootstrap/history_mutation.rs index e78ff9c29c..6697702f61 100644 --- a/moli-renderer-v8/src/context_bootstrap/history_mutation.rs +++ b/moli-renderer-v8/src/context_bootstrap/history_mutation.rs @@ -194,6 +194,7 @@ fn mutate_history_object<'s>( let next_navigation_index = current_navigation_index + 1; let entry = create_navigation_entry( scope, + owner, url.as_str(), state_json.as_deref(), None, @@ -218,6 +219,7 @@ fn mutate_history_object<'s>( .unwrap_or_else(|| new_navigation_entry_key().as_str().to_owned()); let entry = create_navigation_entry( scope, + owner, url.as_str(), state_json.as_deref(), None, diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_activation.rs b/moli-renderer-v8/src/context_bootstrap/navigation_activation.rs index 4346907dc5..6f5f6c4574 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_activation.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_activation.rs @@ -138,6 +138,7 @@ pub(super) fn navigation_entry_object_from_snapshot<'s>( } let entry = create_navigation_entry( scope, + owner, &snapshot.url, snapshot.history_state_json.as_deref(), snapshot.navigation_state_json.as_deref(), diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_entry.rs b/moli-renderer-v8/src/context_bootstrap/navigation_entry.rs index f97711eca3..91a368969a 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_entry.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_entry.rs @@ -172,6 +172,7 @@ pub(super) fn restore_current_navigation_entry_scroll_position<'s>( pub(super) fn create_navigation_entry<'s>( scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, url: &str, history_state_json: Option<&str>, navigation_state_json: Option<&str>, @@ -180,6 +181,11 @@ pub(super) fn create_navigation_entry<'s>( id: &str, key: &str, ) -> v8::Local<'s, v8::Object> { + // Borrowed cross-window methods still create entries in the owner realm. + let context = owner + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); let history_snapshot = super::navigation_serialize::parse_history_entry_state(scope, history_state_json); let navigation_snapshot = diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_events.rs b/moli-renderer-v8/src/context_bootstrap/navigation_events.rs index a939a7cd7d..05956fd945 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_events.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_events.rs @@ -726,14 +726,14 @@ pub(super) fn dispatch_navigation_currententrychange<'s>( from: Option>, navigation_type: Option<&str>, ) { - let global = scope.get_current_context().global(scope); - let Some(event_ctor) = global - .get( - scope, - v8str(scope, "NavigationCurrentEntryChangeEvent").into(), - ) - .and_then(|value| v8::Local::::try_from(value).ok()) - else { + let context = navigation + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); + let Ok(event_ctor) = super::exposed_interfaces::ensure_intrinsic_interface_constructor( + scope, + "NavigationCurrentEntryChangeEvent", + ) else { return; }; let init = NavigationCurrentEntryChangeEventInitDeclaration { @@ -767,17 +767,13 @@ pub(super) fn dispatch_navigation_entry_dispose<'s>( scope: &mut v8::PinScope<'s, '_>, entry: v8::Local<'s, v8::Object>, ) { - let global = scope.get_current_context().global(scope); - let Some(event_ctor) = global - .get(scope, v8str(scope, "Event").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) - else { + let context = entry + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); + let Some(event) = construct_original_event(scope, "dispose") else { return; }; - let Some(event) = event_ctor.new_instance(scope, &[v8str(scope, "dispose").into()]) else { - return; - }; - mark_event_trusted(scope, event); let _ = dispatch_simple_event_target_event( scope, entry, @@ -791,18 +787,13 @@ pub(super) fn dispatch_navigation_success<'s>( scope: &mut v8::PinScope<'s, '_>, navigation: v8::Local<'s, v8::Object>, ) { - let global = scope.get_current_context().global(scope); - let Some(event_ctor) = global - .get(scope, v8str(scope, "Event").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) - else { + let context = navigation + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); + let Some(event) = construct_original_event(scope, "navigatesuccess") else { return; }; - let Some(event) = event_ctor.new_instance(scope, &[v8str(scope, "navigatesuccess").into()]) - else { - return; - }; - mark_event_trusted(scope, event); let _ = dispatch_simple_event_target_event( scope, navigation, @@ -818,7 +809,15 @@ pub(super) fn dispatch_navigation_error<'s>( error: v8::Local<'s, v8::Value>, filename: &str, ) { - let global = scope.get_current_context().global(scope); + let context = navigation + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); + let Ok(event_ctor) = + super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "ErrorEvent") + else { + return; + }; let message = error .to_string(scope) .map(|value| value.to_rust_string_lossy(scope)) @@ -833,10 +832,7 @@ pub(super) fn dispatch_navigation_error<'s>( .bind(scope) .expect("NavigationErrorEvent init declaration should bind"); let event_type = v8str(scope, "navigateerror"); - let event = global - .get(scope, v8str(scope, "ErrorEvent").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) - .and_then(|ctor| ctor.new_instance(scope, &[event_type.into(), init.into()])); + let event = event_ctor.new_instance(scope, &[event_type.into(), init.into()]); let Some(event) = event else { return; }; @@ -899,12 +895,14 @@ pub(super) fn dispatch_navigation_navigate_event_with_form_data_and_outcome<'s>( form_data: Option>, source_element: Option>, ) -> NavigationDispatchOutcome<'s> { - let global = scope.get_current_context().global(scope); + let context = navigation + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); let focus_reset_epoch = context_host_ptr_from_global_bridge(scope) .map(|host_ptr| unsafe { &*host_ptr }.focus_change_epoch()); - let Some(event_ctor) = global - .get(scope, v8str(scope, "NavigateEvent").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) + let Ok(event_ctor) = + super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "NavigateEvent") else { return NavigationDispatchOutcome::proceed(); }; @@ -938,6 +936,7 @@ pub(super) fn dispatch_navigation_navigate_event_with_form_data_and_outcome<'s>( else { return NavigationDispatchOutcome::proceed(); }; + mark_event_trusted(scope, event); set_navigate_event_private_bool(scope, event, NAVIGATE_EVENT_SYNTHETIC_SLOT, false); set_navigate_event_private_bool(scope, event, NAVIGATE_EVENT_FOCUS_RESET_SLOT, true); set_navigate_event_private_bool( @@ -1124,21 +1123,17 @@ fn dispatch_cross_document_navigation_navigate_event_for_window_with_type_form_d let Some(navigation) = window_navigation_for_holder(scope, owner) else { return true; }; + let context = navigation + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); if download_request.is_some() { let _ = cancel_active_navigation_event(scope, navigation); cancel_active_intercepted_same_document_navigation(scope, navigation); } - let event_ctor = owner - .get(scope, v8str(scope, "NavigateEvent").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) - .or_else(|| { - scope - .get_current_context() - .global(scope) - .get(scope, v8str(scope, "NavigateEvent").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) - }); - let Some(event_ctor) = event_ctor else { + let Ok(event_ctor) = + super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "NavigateEvent") + else { return true; }; let current_href = window_location_for_holder(scope, owner) @@ -1184,6 +1179,7 @@ fn dispatch_cross_document_navigation_navigate_event_for_window_with_type_form_d else { return true; }; + mark_event_trusted(scope, event); set_navigate_event_private_bool(scope, event, NAVIGATE_EVENT_SYNTHETIC_SLOT, false); set_navigate_event_private_bool(scope, event, NAVIGATE_EVENT_FOCUS_RESET_SLOT, true); set_navigate_event_private_bool( @@ -1235,12 +1231,14 @@ pub(super) fn dispatch_navigation_traverse_event_with_outcome<'s>( target_index: u32, info: Option>, ) -> NavigationDispatchOutcome<'s> { - let global = scope.get_current_context().global(scope); + let context = navigation + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); let focus_reset_epoch = context_host_ptr_from_global_bridge(scope) .map(|host_ptr| unsafe { &*host_ptr }.focus_change_epoch()); - let Some(event_ctor) = global - .get(scope, v8str(scope, "NavigateEvent").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) + let Ok(event_ctor) = + super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "NavigateEvent") else { return NavigationDispatchOutcome::proceed(); }; @@ -1289,6 +1287,7 @@ pub(super) fn dispatch_navigation_traverse_event_with_outcome<'s>( else { return NavigationDispatchOutcome::proceed(); }; + mark_event_trusted(scope, event); set_navigate_event_private_bool(scope, event, NAVIGATE_EVENT_SYNTHETIC_SLOT, false); set_navigate_event_private_bool(scope, event, NAVIGATE_EVENT_FOCUS_RESET_SLOT, true); set_navigate_event_private_bool( @@ -1560,10 +1559,8 @@ fn navigation_destination_entry_string_property<'s>( fn create_navigation_abort_signal<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Value> { - let global = scope.get_current_context().global(scope); - let Some(controller_ctor) = global - .get(scope, v8str(scope, "AbortController").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) + let Ok(controller_ctor) = + super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "AbortController") else { return v8::null(scope).into(); }; diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_mutation/local.rs b/moli-renderer-v8/src/context_bootstrap/navigation_mutation/local.rs index c8cfbf2af3..788088a6ee 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_mutation/local.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_mutation/local.rs @@ -30,6 +30,7 @@ pub(crate) fn apply_local_window_location_navigation<'s>( let state = v8::null(scope).into(); let next_entry = create_navigation_entry( scope, + owner, resolved.as_str(), None, None, @@ -60,6 +61,7 @@ pub(crate) fn apply_local_window_location_navigation<'s>( .unwrap_or_else(|| new_navigation_entry_key().as_str().to_owned()); let entry = create_navigation_entry( scope, + owner, resolved.as_str(), None, None, diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_mutation/same_document.rs b/moli-renderer-v8/src/context_bootstrap/navigation_mutation/same_document.rs index 524ee9da20..44ecdaa892 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_mutation/same_document.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_mutation/same_document.rs @@ -43,6 +43,7 @@ pub(in crate::context_bootstrap) fn update_navigation_current_entry_for_same_doc set_child_joint_top_index_for_entry(scope, owner, Some(current_entry)); let next_entry = create_navigation_entry( scope, + owner, href, history_state_json.as_deref(), navigation_state_json.as_deref(), @@ -71,6 +72,7 @@ pub(in crate::context_bootstrap) fn update_navigation_current_entry_for_same_doc .unwrap_or_else(|| new_navigation_entry_key().as_str().to_owned()); let entry = create_navigation_entry( scope, + owner, href, history_state_json.as_deref(), navigation_state_json.as_deref(), @@ -135,6 +137,7 @@ pub(in crate::context_bootstrap) fn apply_navigation_navigate_same_document<'s>( } let next_entry = create_navigation_entry( scope, + owner, href, None, None, @@ -170,6 +173,7 @@ pub(in crate::context_bootstrap) fn apply_navigation_navigate_same_document<'s>( .unwrap_or_else(|| new_navigation_entry_key().as_str().to_owned()); let entry = create_navigation_entry( scope, + owner, href, None, None, diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_restore.rs b/moli-renderer-v8/src/context_bootstrap/navigation_restore.rs index 0461e1d9a4..74282f1d8b 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_restore.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_restore.rs @@ -40,6 +40,7 @@ pub(crate) fn install_navigation_bootstrap_entry_for_holder<'s>( for snapshot in &entry_seed.entries { let entry = create_navigation_entry( scope, + owner, &snapshot.url, snapshot.history_state_json.as_deref(), snapshot.navigation_state_json.as_deref(), @@ -64,6 +65,7 @@ pub(crate) fn install_navigation_bootstrap_entry_for_holder<'s>( let entry_key = NavigationHistoryEntryKey::allocate(); let entry = create_navigation_entry( scope, + owner, "about:blank", None, None, diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_seed.rs b/moli-renderer-v8/src/context_bootstrap/navigation_seed.rs index 97cb9edd6f..e8253743c5 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_seed.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_seed.rs @@ -37,6 +37,7 @@ pub(super) fn build_history_entries_array_from_seed<'s>( for snapshot in &seed.entries { let entry = create_navigation_entry( scope, + owner, &snapshot.url, snapshot.history_state_json.as_deref(), snapshot.navigation_state_json.as_deref(), @@ -68,6 +69,7 @@ pub(super) fn build_current_navigation_entry_from_seed<'s>( let entry_key = NavigationHistoryEntryKey::allocate(); let entry = create_navigation_entry( scope, + owner, "about:blank", fallback_state_json.as_deref(), fallback_state_json.as_deref(), @@ -83,6 +85,7 @@ pub(super) fn build_current_navigation_entry_from_seed<'s>( }; let entry = create_navigation_entry( scope, + owner, &snapshot.url, snapshot.history_state_json.as_deref(), snapshot.navigation_state_json.as_deref(), diff --git a/moli-renderer-v8/src/native_bridge/abort/event.rs b/moli-renderer-v8/src/native_bridge/abort/event.rs index 097834e34f..2672363767 100644 --- a/moli-renderer-v8/src/native_bridge/abort/event.rs +++ b/moli-renderer-v8/src/native_bridge/abort/event.rs @@ -1,12 +1,11 @@ use super::{AbortDispatchSnapshot, AbortStore}; use crate::context_bootstrap::{ - EVENT_PASSIVE_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT, event_internal_bool_flag, - set_event_internal_flag, + EVENT_PASSIVE_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT, construct_original_event, + event_internal_bool_flag, set_event_internal_flag, }; use crate::exception_reporting::invoke_callback; use crate::host::invoke_prepared_event_callback_on_object; use crate::native_bridge::JsContextHost; -use crate::util::{v8_string, v8str}; pub(super) fn invoke_abort_algorithms<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -41,17 +40,11 @@ pub(super) fn dispatch_abort<'s>( // front so listeners and `onabort` retain local `TryCatch`, structured // stderr, and no stdout pollution. let signal = local_object_in_scope(scope, signal); - let global = scope.get_current_context().global(scope); - let Some(event_ctor) = global - .get(scope, v8str(scope, "Event").into()) - .and_then(|value| v8::Local::::try_from(value).ok()) - else { - return; - }; - let Some(event_type) = v8_string(scope, "abort") else { - return; - }; - let Some(event) = event_ctor.new_instance(scope, &[event_type.into()]) else { + let context = signal + .get_creation_context(scope) + .unwrap_or_else(|| scope.get_current_context()); + let scope = &mut v8::ContextScope::new(scope, context); + let Some(event) = construct_original_event(scope, "abort") else { return; }; AbortStore::define_hidden_value(scope, event, "target", signal.into());