fix: construct native navigation events in their target realm

Use intrinsic constructors in the Navigation or history entry target realm
for navigate, currententrychange, navigatesuccess, navigateerror and dispose.
Create history entries in their owning window even when methods are borrowed
from another window. Preserve native navigate event trust and construct
navigation abort controllers and trusted abort events through intrinsics.

Add three Browser integration tests covering 14 cross-window, joint-history
and cross-document flows, including throwing public constructor getters.
The same fixture matches Chrome in all 14 flows.

Validation: cargo fmt --all; cargo clippy --workspace --all-targets
--all-features -- -D warnings; cargo nextest run --no-fail-fast
(17918 passed, 13 skipped). The 334-case WPT comparison gains two passing
cases and two subtests without regressions; passed ledger is 9107.
This commit is contained in:
ldm0
2026-09-23 00:11:56 +08:00
parent 0e07e58286
commit a5c4e8b6fd
13 changed files with 319 additions and 70 deletions
@@ -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
@@ -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
@@ -0,0 +1,143 @@
<!doctype html><body><script>
window.finished = (async () => {
await new Promise(resolve => addEventListener('load', () => setTimeout(resolve, 0), {once: true}));
const frame = document.createElement('iframe');
const loaded = new Promise(resolve => frame.onload = () => setTimeout(resolve, 0));
frame.src = '/static?navigation-event-realm';
document.body.append(frame);
await loaded;
const child = frame.contentWindow;
const scenario = __SCENARIO__;
const target = __TARGET__ === 'top' ? window : child;
const caller = target === window ? child : window;
const errors = [];
const events = [];
let reads = 0;
const check = (condition, message) => { if (!condition) errors.push(message); };
const names = ['Event', 'ErrorEvent', 'NavigateEvent', 'NavigationCurrentEntryChangeEvent', 'AbortController'];
const observedNames = [...names, 'AbortSignal', 'NavigationHistoryEntry'];
const originals = new Map([window, child].map(win =>
[win, Object.fromEntries(observedNames.map(name => [name, win[name]]))]));
for (const win of [window, child]) win.addEventListener('error', event => errors.push('uncaught:' + event.message));
function tamper() {
if (__CONSTRUCTORS__ !== 'overwritten') return;
for (const win of [window, child]) for (const name of names) {
Object.defineProperty(win, name, {configurable: true, get() {
reads++;
throw new Error('Public constructor accessed: ' + name);
}});
}
}
function record(win, receiver, event, constructor) {
events.push(event);
const original = originals.get(win)[constructor];
check(event.constructor === original, event.type + ': constructor realm');
check(Object.getPrototypeOf(event) === original.prototype, event.type + ': prototype realm');
check(event.target === receiver && event.currentTarget === receiver, event.type + ': target');
check(event.isTrusted && !event.bubbles && !event.composed, event.type + ': native flags');
if (event.type === 'navigate') {
check(event.destination instanceof win.Object, 'navigate: destination realm');
check(Object.getPrototypeOf(event.signal) === originals.get(win).AbortSignal.prototype,
'navigate: signal realm');
if (scenario === 'joint-cancel' || scenario === 'reject')
event.signal.onabort = abort => record(win, event.signal, abort, 'Event');
}
if (event.type === 'currententrychange')
check(Object.getPrototypeOf(event.from) === originals.get(win).NavigationHistoryEntry.prototype,
'currententrychange: from realm');
if (event.type !== 'navigate' && event.type !== 'navigateerror')
check(!event.cancelable, event.type + ': cancelable');
}
function observe(win) {
const nav = win.navigation;
for (const [type, constructor] of [
['navigate', 'NavigateEvent'], ['currententrychange', 'NavigationCurrentEntryChangeEvent'],
['navigatesuccess', 'Event'], ['navigateerror', 'ErrorEvent']
]) nav.addEventListener(type, event => record(win, nav, event, constructor));
}
if (scenario === 'joint-cancel') {
await child.navigation.navigate('#child').finished;
await navigation.navigate('#parent').finished;
const topEntry = navigation.currentEntry;
const childEntry = child.navigation.currentEntry;
observe(window);
let childEvents = 0;
child.navigation.onnavigate = child.navigation.onnavigateerror = () => childEvents++;
navigation.onnavigate = event => event.preventDefault();
tamper();
const result = child.navigation.traverseTo(child.navigation.entries()[0].key);
const rejected = await Promise.all([result.committed, result.finished].map(promise =>
promise.then(() => null, error => error)));
check(rejected.every(error => error && error.name === 'AbortError' &&
error.constructor === child.DOMException), 'joint cancellation: child promise error realm');
check(rejected[0] === rejected[1], 'joint cancellation: promise error identity');
const errorEvent = events.find(event => event.type === 'navigateerror');
check(errorEvent && errorEvent.error.name === 'AbortError', 'joint cancellation: event error');
check(errorEvent && errorEvent.filename === location.href, 'joint cancellation: filename');
check(childEvents === 0, 'joint cancellation: child received an event');
check(navigation.currentEntry === topEntry && child.navigation.currentEntry === childEntry,
'joint cancellation: current entry changed');
} else if (scenario === 'joint-prune') {
await child.navigation.navigate('#child').finished;
await navigation.navigate('#one').finished;
await navigation.navigate('#two').finished;
const disposed = navigation.entries().slice(1);
await child.navigation.back().finished;
const disposedEvents = Promise.all(disposed.map(entry => new Promise(resolve => {
entry.ondispose = event => { record(window, entry, event, 'Event'); resolve(); };
})));
tamper();
await child.navigation.navigate('#new').finished;
await disposedEvents;
check(navigation.entries().length === 1, 'joint pruning: parent forward entries retained');
check(child.navigation.entries().length === 2, 'joint pruning: child entries');
} else {
const nav = target.navigation;
const methods = caller.Navigation.prototype;
observe(target);
tamper();
if (scenario === 'cross-document') {
const committed = new Promise(resolve => frame.onload = () => setTimeout(resolve, 0));
child.location.href = '/static?navigation-event-destination';
await committed;
check(child.location.pathname === '/static' && child.location.search === '?navigation-event-destination',
'cross-document: destination URL');
} else if (scenario === 'reject') {
const reason = new caller.TypeError('cross-window rejection');
nav.onnavigate = event => event.intercept({handler: () => Promise.reject(reason)});
const result = methods.navigate.call(nav, '#rejected');
await result.committed;
const rejection = await result.finished.then(() => null, error => error);
const errorEvent = events.find(event => event.type === 'navigateerror');
check(rejection === reason, 'rejection: promise error identity');
check(errorEvent && errorEvent.error === reason, 'rejection: event error identity');
check(errorEvent && errorEvent.message.includes('cross-window rejection'), 'rejection: message');
} else {
const initial = nav.currentEntry;
await methods.navigate.call(nav, '#one').finished;
const replaced = nav.currentEntry;
check(Object.getPrototypeOf(replaced) === originals.get(target).NavigationHistoryEntry.prototype,
'navigate: entry realm');
replaced.ondispose = event => record(target, replaced, event, 'Event');
await methods.navigate.call(nav, '#two', {history: 'replace'}).finished;
methods.updateCurrentEntry.call(nav, {state: {saved: true}});
check(nav.currentEntry.getState().saved, 'updateCurrentEntry: state');
await methods.back.call(nav).finished;
check(nav.currentEntry === initial, 'traversal: original entry identity');
const changes = events.filter(event => event.type === 'currententrychange');
check(JSON.stringify(changes.map(event => event.navigationType)) ===
JSON.stringify(['push', 'replace', null, 'traverse']), 'currententrychange: navigationType');
check(changes[0] && changes[0].from === initial && changes[1] && changes[1].from === replaced,
'currententrychange: from identity');
}
}
return {errors, reads, events: events.map(event => event.type)};
})().then(result => {
document.body.dataset.result = JSON.stringify(result);
return result;
}, error => {
const result = {exception: String(error)};
document.body.dataset.result = JSON.stringify(result);
return result;
});
</script>
+95
View File
@@ -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<Value> {
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(())
}
@@ -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,
@@ -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(),
@@ -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 =
@@ -726,14 +726,14 @@ pub(super) fn dispatch_navigation_currententrychange<'s>(
from: Option<v8::Local<'s, v8::Object>>,
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::<v8::Function>::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::<v8::Function>::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::<v8::Function>::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::<v8::Function>::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<v8::Local<'s, v8::Value>>,
source_element: Option<v8::Local<'s, v8::Object>>,
) -> 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::<v8::Function>::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::<v8::Function>::try_from(value).ok())
.or_else(|| {
scope
.get_current_context()
.global(scope)
.get(scope, v8str(scope, "NavigateEvent").into())
.and_then(|value| v8::Local::<v8::Function>::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<v8::Local<'s, v8::Value>>,
) -> 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::<v8::Function>::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::<v8::Function>::try_from(value).ok())
let Ok(controller_ctor) =
super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "AbortController")
else {
return v8::null(scope).into();
};
@@ -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,
@@ -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,
@@ -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,
@@ -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(),
@@ -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::<v8::Function>::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());