mirror of
https://github.com/lexmount/moli.git
synced 2026-10-17 08:01:26 +00:00
fix(renderer): complete intercepted top-level hyperlink navigation
This commit is contained in:
@@ -546,51 +546,12 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
}
|
||||
cancel_pending_same_document_navigation_finishes(scope, navigation);
|
||||
if outcome.intercepted {
|
||||
let effective_href = outcome
|
||||
.redirected_url
|
||||
.as_deref()
|
||||
.unwrap_or(resolved.as_str())
|
||||
.to_owned();
|
||||
let effective_kind = outcome
|
||||
.redirected_history
|
||||
.as_deref()
|
||||
.map(|history| match history {
|
||||
"replace" => LocationNavigationKind::Replace,
|
||||
_ => LocationNavigationKind::Assign,
|
||||
})
|
||||
.unwrap_or(kind);
|
||||
let transition_resolver = navigation_current_entry(scope, owner).and_then(|from| {
|
||||
install_navigation_transition(
|
||||
scope,
|
||||
navigation,
|
||||
from,
|
||||
outcome.destination,
|
||||
match effective_kind {
|
||||
LocationNavigationKind::Assign => "push",
|
||||
LocationNavigationKind::Replace => "replace",
|
||||
LocationNavigationKind::Reload => "reload",
|
||||
},
|
||||
)
|
||||
});
|
||||
sync_location_object(scope, location, &effective_href);
|
||||
update_navigation_current_entry_for_same_document(
|
||||
scope,
|
||||
owner,
|
||||
&effective_href,
|
||||
effective_kind,
|
||||
);
|
||||
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope)
|
||||
&& let Ok(url) = url::Url::parse(&effective_href)
|
||||
{
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
host.set_document_url(url.clone());
|
||||
host.record_same_document_navigation(&url, "fragment");
|
||||
}
|
||||
settle_location_intercepted_same_document_navigation(
|
||||
commit_intercepted_location_navigation(
|
||||
scope,
|
||||
navigation,
|
||||
outcome,
|
||||
transition_resolver,
|
||||
resolved.as_str(),
|
||||
kind,
|
||||
¤t_href,
|
||||
);
|
||||
return;
|
||||
@@ -847,6 +808,8 @@ fn window_for_child_cross_document_location_navigation<'s>(
|
||||
unsafe { &mut *host_ptr }.existing_child_browsing_context_window_wrapper(scope, handle)
|
||||
}
|
||||
|
||||
/// Returns whether the caller should queue a cross-document navigation.
|
||||
/// Intercepted navigations commit and settle here without redispatching the event.
|
||||
pub(crate) fn dispatch_top_level_navigation_event_with_source_element<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
@@ -889,7 +852,23 @@ pub(crate) fn dispatch_top_level_navigation_event_with_source_element<'s>(
|
||||
return false;
|
||||
}
|
||||
cancel_pending_same_document_navigation_finishes(scope, navigation);
|
||||
!outcome.intercepted
|
||||
if outcome.intercepted {
|
||||
let kind = match navigation_type {
|
||||
"replace" => LocationNavigationKind::Replace,
|
||||
"reload" => LocationNavigationKind::Reload,
|
||||
_ => LocationNavigationKind::Assign,
|
||||
};
|
||||
commit_intercepted_location_navigation(
|
||||
scope,
|
||||
navigation,
|
||||
outcome,
|
||||
href,
|
||||
kind,
|
||||
¤t_href,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_top_level_form_navigation_event<'s>(
|
||||
@@ -957,6 +936,66 @@ fn finish_location_navigation_canceled<'s>(
|
||||
finish_navigation_error_events(scope, navigation, error, href);
|
||||
}
|
||||
|
||||
fn commit_intercepted_location_navigation<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
navigation: v8::Local<'s, v8::Object>,
|
||||
outcome: NavigationDispatchOutcome<'s>,
|
||||
href: &str,
|
||||
kind: LocationNavigationKind,
|
||||
current_href: &str,
|
||||
) {
|
||||
let context = navigation
|
||||
.get_creation_context(scope)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let owner = runtime_window_owner(scope, navigation);
|
||||
let effective_href = outcome.redirected_url.as_deref().unwrap_or(href).to_owned();
|
||||
let effective_kind = outcome
|
||||
.redirected_history
|
||||
.as_deref()
|
||||
.map(|history| match history {
|
||||
"replace" => LocationNavigationKind::Replace,
|
||||
_ => LocationNavigationKind::Assign,
|
||||
})
|
||||
.unwrap_or(kind);
|
||||
let transition_resolver = navigation_current_entry(scope, owner).and_then(|from| {
|
||||
install_navigation_transition(
|
||||
scope,
|
||||
navigation,
|
||||
from,
|
||||
outcome.destination,
|
||||
match effective_kind {
|
||||
LocationNavigationKind::Assign => "push",
|
||||
LocationNavigationKind::Replace => "replace",
|
||||
LocationNavigationKind::Reload => "reload",
|
||||
},
|
||||
)
|
||||
});
|
||||
if let Some(location) = window_location_for_holder(scope, owner) {
|
||||
sync_location_object(scope, location, &effective_href);
|
||||
}
|
||||
update_navigation_current_entry_for_same_document(
|
||||
scope,
|
||||
owner,
|
||||
&effective_href,
|
||||
effective_kind,
|
||||
);
|
||||
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope)
|
||||
&& let Ok(url) = url::Url::parse(&effective_href)
|
||||
{
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
host.set_document_url(url.clone());
|
||||
host.record_same_document_navigation(&url, "fragment");
|
||||
}
|
||||
settle_location_intercepted_same_document_navigation(
|
||||
scope,
|
||||
navigation,
|
||||
outcome,
|
||||
transition_resolver,
|
||||
current_href,
|
||||
);
|
||||
}
|
||||
|
||||
fn settle_location_intercepted_same_document_navigation<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
navigation: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -94,7 +94,8 @@ fn child_navigation_to_top_fires_once_and_honors_cancel_or_intercept() {
|
||||
.eval(&format!(
|
||||
r#"
|
||||
(() => {{
|
||||
const seen = [];
|
||||
const seen = globalThis.__lmTargetNavigationLog = [];
|
||||
const from = navigation.currentEntry;
|
||||
let source = document;
|
||||
for (let i = 0; i < {depth}; ++i) {{
|
||||
const frame = source.createElement('iframe');
|
||||
@@ -110,6 +111,7 @@ fn child_navigation_to_top_fires_once_and_honors_cancel_or_intercept() {
|
||||
anchor.href = 'https://anchor-navigation-target.test/destination';
|
||||
anchor.target = {target:?};
|
||||
source.body.appendChild(anchor);
|
||||
const initialHistoryLength = history.length;
|
||||
navigation.onnavigate = event => {{
|
||||
seen.push([
|
||||
'top',
|
||||
@@ -122,9 +124,23 @@ fn child_navigation_to_top_fires_once_and_honors_cancel_or_intercept() {
|
||||
]);
|
||||
event.signal.onabort = () => seen.push('top-abort');
|
||||
if ({action:?} === 'cancel') event.preventDefault();
|
||||
if ({action:?} === 'intercept') event.intercept({{ handler() {{}} }});
|
||||
if ({action:?} === 'intercept') event.intercept({{
|
||||
handler() {{
|
||||
seen.push([
|
||||
'handler',
|
||||
location.href,
|
||||
navigation.currentEntry.url,
|
||||
navigation.transition instanceof NavigationTransition,
|
||||
navigation.transition.from === from,
|
||||
navigation.transition.navigationType,
|
||||
history.length === initialHistoryLength + 1
|
||||
]);
|
||||
return Promise.resolve().then(() => seen.push('handler-complete'));
|
||||
}}
|
||||
}});
|
||||
}};
|
||||
navigation.onnavigateerror = () => seen.push('top-error');
|
||||
navigation.onnavigatesuccess = () => seen.push('top-success');
|
||||
const realm = {use_child_realm} ? source.defaultView : window;
|
||||
if ({api:?} === 'click') realm.HTMLElement.prototype.click.call(anchor);
|
||||
else realm.open.call(source.defaultView, anchor.href, anchor.target);
|
||||
@@ -148,12 +164,52 @@ fn child_navigation_to_top_fires_once_and_honors_cancel_or_intercept() {
|
||||
serde_json::json!("top-abort"),
|
||||
serde_json::json!("top-error"),
|
||||
]);
|
||||
} else if action == "intercept" {
|
||||
expected.push(serde_json::json!([
|
||||
"handler",
|
||||
expected_url,
|
||||
expected_url,
|
||||
true,
|
||||
true,
|
||||
"push",
|
||||
true
|
||||
]));
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(&result).unwrap(),
|
||||
serde_json::json!(expected),
|
||||
"{description}"
|
||||
);
|
||||
if action == "intercept" {
|
||||
expected.extend([
|
||||
serde_json::json!("handler-complete"),
|
||||
serde_json::json!("top-success"),
|
||||
]);
|
||||
}
|
||||
if action != "allow" {
|
||||
let settled = vm
|
||||
.eval(
|
||||
"JSON.stringify({ log: globalThis.__lmTargetNavigationLog, \
|
||||
href: location.href, entryUrl: navigation.currentEntry.url, \
|
||||
transition: navigation.transition })",
|
||||
)
|
||||
.expect(&description);
|
||||
let committed_url = if action == "intercept" {
|
||||
expected_url
|
||||
} else {
|
||||
"https://anchor-navigation-target.test/source"
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(&settled).unwrap(),
|
||||
serde_json::json!({
|
||||
"log": expected,
|
||||
"href": committed_url,
|
||||
"entryUrl": committed_url,
|
||||
"transition": null
|
||||
}),
|
||||
"{description}"
|
||||
);
|
||||
}
|
||||
let pending = vm.take_pending_location_navigation_with_seed();
|
||||
if action == "allow" {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user