mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 00:01:00 +00:00
fix(history): count child pushes in joint session history
This commit is contained in:
@@ -306,7 +306,7 @@ async fn assert_child_location_navigation_stays_window_local(
|
||||
page.serialize_html_async()
|
||||
.await
|
||||
.unwrap()
|
||||
.contains("data-top-history-unchanged=\"false\""),
|
||||
.contains("data-top-history-unchanged=\"true\""),
|
||||
"{}",
|
||||
page.serialize_html_async().await.unwrap()
|
||||
);
|
||||
@@ -314,13 +314,13 @@ async fn assert_child_location_navigation_stays_window_local(
|
||||
page.serialize_html_async()
|
||||
.await
|
||||
.unwrap()
|
||||
.contains("data-child-history-advanced=\"true\"")
|
||||
.contains("data-child-history-advanced=\"false\"")
|
||||
);
|
||||
assert!(
|
||||
page.serialize_html_async()
|
||||
.await
|
||||
.unwrap()
|
||||
.contains("data-child-current-entry-index=\"1\""),
|
||||
.contains("data-child-current-entry-index=\"0\""),
|
||||
"{}",
|
||||
page.serialize_html_async().await.unwrap()
|
||||
);
|
||||
|
||||
@@ -8,38 +8,22 @@ pub(crate) fn increment_top_level_history_length_for_runtime_owner<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
if !runtime_window_uses_top_level_history_model(scope, owner)
|
||||
&& let Some(child_history) = window_history_for_holder(scope, owner)
|
||||
&& let Some(child_length) = history_length_number(scope, child_history)
|
||||
{
|
||||
set_top_level_history_length_at_least_for_runtime_owner(
|
||||
scope,
|
||||
owner,
|
||||
child_length.max(0.0),
|
||||
);
|
||||
let child_history = (!runtime_window_uses_top_level_history_model(scope, owner))
|
||||
.then(|| window_history_for_holder(scope, owner))
|
||||
.flatten();
|
||||
let top_window = runtime_top_window_owner(scope, owner);
|
||||
let Some(history) = window_history_for_holder(scope, top_window) else {
|
||||
return;
|
||||
};
|
||||
let current_length = history_length_number(scope, history)
|
||||
.unwrap_or(0.0)
|
||||
.max(0.0);
|
||||
let next_length = current_length + 1.0;
|
||||
set_history_length(scope, history, next_length);
|
||||
if let Some(child_history) = child_history {
|
||||
let child_length = history_length_number(scope, child_history)
|
||||
.unwrap_or(0.0)
|
||||
.max(0.0);
|
||||
set_history_length(scope, child_history, child_length.max(next_length));
|
||||
}
|
||||
let top_window = runtime_top_window_owner(scope, owner);
|
||||
let Some(history) = window_history_for_holder(scope, top_window) else {
|
||||
return;
|
||||
};
|
||||
let current_length = history_length_number(scope, history)
|
||||
.unwrap_or(0.0)
|
||||
.max(0.0);
|
||||
set_history_length(scope, history, current_length + 1.0);
|
||||
}
|
||||
|
||||
pub(crate) fn set_top_level_history_length_at_least_for_runtime_owner<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
length: f64,
|
||||
) {
|
||||
let top_window = runtime_top_window_owner(scope, owner);
|
||||
let Some(history) = window_history_for_holder(scope, top_window) else {
|
||||
return;
|
||||
};
|
||||
let current_length = history_length_number(scope, history)
|
||||
.unwrap_or(0.0)
|
||||
.max(0.0);
|
||||
set_history_length(scope, history, current_length.max(length.max(0.0)));
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ pub(super) fn handle_navigation_navigate_cross_document<'s>(
|
||||
child_handle,
|
||||
next_url.as_str(),
|
||||
entry_seed,
|
||||
matches!(mutation, NavigationHistoryMutation::Push),
|
||||
);
|
||||
host.sync_existing_child_browsing_context_window_state(scope, child_handle);
|
||||
navigation_signal
|
||||
|
||||
@@ -44,7 +44,10 @@ pub(crate) fn apply_local_window_location_navigation<'s>(
|
||||
let _ = next_entries.set_index(scope, next_index, next_entry.into());
|
||||
set_history_entries(scope, history, next_entries);
|
||||
set_history_index(scope, history, next_index);
|
||||
set_history_length_at_least_visible_entries(scope, history, next_entries);
|
||||
// Cross-document pushes do not enter the joint session history
|
||||
// until the new Document commits. Keep the pending child's local
|
||||
// projection current without advancing the traversable yet.
|
||||
set_history_length_from_visible_entries(scope, history, next_entries);
|
||||
set_history_state(scope, history, state);
|
||||
set_navigation_current_entry(scope, navigation, next_entry);
|
||||
dispatch_navigation_currententrychange(scope, navigation, previous_entry, Some("push"));
|
||||
|
||||
@@ -227,7 +227,6 @@ pub(super) fn set_history_length_from_visible_entries<'s>(
|
||||
) {
|
||||
let length = history_length_floor_from_visible_entries(scope, history, entries);
|
||||
set_history_length(scope, history, length);
|
||||
set_top_history_length_at_least(scope, history, length);
|
||||
}
|
||||
|
||||
pub(super) fn set_history_length_at_least_visible_entries<'s>(
|
||||
@@ -239,9 +238,18 @@ pub(super) fn set_history_length_at_least_visible_entries<'s>(
|
||||
let current_length = history_length_number(scope, history)
|
||||
.unwrap_or(0.0)
|
||||
.max(0.0);
|
||||
let length = current_length.max(length);
|
||||
set_history_length(scope, history, length);
|
||||
set_top_history_length_at_least(scope, history, length);
|
||||
let owner = runtime_window_owner(scope, history);
|
||||
if runtime_window_uses_top_level_history_model(scope, owner) {
|
||||
set_history_length(scope, history, current_length.max(length));
|
||||
return;
|
||||
}
|
||||
|
||||
// Same-document child pushes commit synchronously. They add one entry to
|
||||
// the traversable's joint session history even when another child has
|
||||
// already made the top-level length larger than this child's local list.
|
||||
super::increment_top_level_history_length_for_runtime_owner(scope, owner);
|
||||
let joint_length = history_length_floor_from_visible_entries(scope, history, entries);
|
||||
set_history_length(scope, history, current_length.max(length).max(joint_length));
|
||||
}
|
||||
|
||||
fn history_length_floor_from_visible_entries<'s>(
|
||||
@@ -270,22 +278,3 @@ fn history_length_floor_from_visible_entries<'s>(
|
||||
.max(0.0);
|
||||
visible_length.max(top_length)
|
||||
}
|
||||
|
||||
fn set_top_history_length_at_least<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
history: v8::Local<'s, v8::Object>,
|
||||
length: f64,
|
||||
) {
|
||||
let owner = runtime_window_owner(scope, history);
|
||||
if runtime_window_uses_top_level_history_model(scope, owner) {
|
||||
return;
|
||||
}
|
||||
let top_window = runtime_top_window_owner(scope, owner);
|
||||
let Some(top_history) = window_history_for_holder(scope, top_window) else {
|
||||
return;
|
||||
};
|
||||
let current_length = history_length_number(scope, top_history)
|
||||
.unwrap_or(0.0)
|
||||
.max(0.0);
|
||||
set_history_length(scope, top_history, current_length.max(length));
|
||||
}
|
||||
|
||||
@@ -532,5 +532,6 @@ fn queue_child_cross_document_traversal(
|
||||
child_handle,
|
||||
target_url,
|
||||
seed,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -640,6 +640,7 @@ impl JsContextHost {
|
||||
};
|
||||
entry.clear_cached_snapshot();
|
||||
entry.clear_completed_document_network();
|
||||
entry.clear_pending_top_level_history_length_increment();
|
||||
self.reject_replaced_service_worker_child_client_navigation(
|
||||
handle,
|
||||
format!("Cannot navigate to URL: {error}"),
|
||||
@@ -694,6 +695,7 @@ impl JsContextHost {
|
||||
body_activity,
|
||||
};
|
||||
};
|
||||
self.commit_pending_child_joint_history_push(scope, handle);
|
||||
initial_classic_ready_work = install.initial_classic_ready_work;
|
||||
parser_stop_action = install.parser_stop_action;
|
||||
owner_transition = Some(install.owner_transition);
|
||||
|
||||
@@ -29,6 +29,7 @@ impl JsContextHost {
|
||||
entry.replace_navigation_in_entry_seed(&url);
|
||||
} else {
|
||||
entry.apply_navigation_to_entry_seed(&url);
|
||||
entry.mark_pending_top_level_history_length_increment();
|
||||
}
|
||||
}
|
||||
self.sync_existing_child_browsing_context_runtime_surface_from_seed(scope, handle);
|
||||
@@ -51,6 +52,7 @@ impl JsContextHost {
|
||||
entry.replace_navigation_in_entry_seed(&request.url);
|
||||
} else {
|
||||
entry.apply_navigation_to_entry_seed(&request.url);
|
||||
entry.mark_pending_top_level_history_length_increment();
|
||||
}
|
||||
}
|
||||
self.sync_existing_child_browsing_context_runtime_surface_from_seed(scope, handle);
|
||||
@@ -75,6 +77,9 @@ impl JsContextHost {
|
||||
);
|
||||
if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) {
|
||||
entry.apply_queued_navigation_to_entry_seed(&url, replace_current);
|
||||
if !replace_current {
|
||||
entry.mark_pending_top_level_history_length_increment();
|
||||
}
|
||||
}
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url)
|
||||
}
|
||||
@@ -98,6 +103,7 @@ impl JsContextHost {
|
||||
handle: DomHandle,
|
||||
resolved_url: &str,
|
||||
entry_seed: NavigationHistoryEntrySeed,
|
||||
increments_joint_history: bool,
|
||||
) -> bool {
|
||||
if !self.child_browsing_contexts.contains_key(&handle) {
|
||||
return false;
|
||||
@@ -107,6 +113,9 @@ impl JsContextHost {
|
||||
};
|
||||
if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) {
|
||||
entry.replace_navigation_entry_seed_and_clear_pending_history_increment(entry_seed);
|
||||
if increments_joint_history {
|
||||
entry.mark_pending_top_level_history_length_increment();
|
||||
}
|
||||
}
|
||||
if self
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
@@ -134,6 +143,7 @@ impl JsContextHost {
|
||||
};
|
||||
if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) {
|
||||
entry.apply_deferred_navigation_to_entry_seed(&url);
|
||||
entry.mark_pending_top_level_history_length_increment();
|
||||
}
|
||||
if self
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
@@ -162,6 +172,7 @@ impl JsContextHost {
|
||||
);
|
||||
if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) {
|
||||
entry.apply_deferred_navigation_to_entry_seed(&request.url);
|
||||
entry.mark_pending_top_level_history_length_increment();
|
||||
}
|
||||
if self
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
|
||||
@@ -416,10 +416,6 @@ impl JsContextHost {
|
||||
self.sync_existing_child_browsing_context_window_state(scope, handle);
|
||||
return None;
|
||||
}
|
||||
let increment_top_level_history_length = self
|
||||
.child_browsing_contexts
|
||||
.get_mut(&handle)
|
||||
.is_some_and(|entry| entry.take_pending_top_level_history_length_increment());
|
||||
self.clear_child_browsing_context_pending_navigation(handle);
|
||||
self.clear_pending_form_submission_child_target(handle);
|
||||
let commit_result = self.commit_child_document_bootstrap_or_start_load(
|
||||
@@ -429,13 +425,30 @@ impl JsContextHost {
|
||||
navigation_load,
|
||||
ChildDocumentNavigationInitiator::BrowsingContext,
|
||||
);
|
||||
if commit_result
|
||||
.as_ref()
|
||||
.is_some_and(|result| result.state == ChildDocumentCommitState::Ready)
|
||||
{
|
||||
self.commit_pending_child_joint_history_push(scope, handle);
|
||||
}
|
||||
self.sync_existing_child_browsing_context_window_state(scope, handle);
|
||||
if increment_top_level_history_length
|
||||
commit_result
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn commit_pending_child_joint_history_push(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
handle: DomHandle,
|
||||
) {
|
||||
let increments_joint_history = self
|
||||
.child_browsing_contexts
|
||||
.get_mut(&handle)
|
||||
.is_some_and(|entry| entry.take_pending_top_level_history_length_increment());
|
||||
if increments_joint_history
|
||||
&& let Some(window) = self.child_browsing_context_window_wrapper(scope, handle)
|
||||
{
|
||||
increment_top_level_history_length_for_runtime_owner(scope, window);
|
||||
}
|
||||
commit_result
|
||||
}
|
||||
|
||||
pub(crate) fn queue_child_browsing_context_javascript_url_execution(
|
||||
|
||||
@@ -22,11 +22,7 @@ impl JsContextHost {
|
||||
&self,
|
||||
handle: DomHandle,
|
||||
) -> bool {
|
||||
let seed_is_initial_about_blank = self
|
||||
.child_browsing_contexts
|
||||
.get(&handle)
|
||||
.is_some_and(ChildBrowsingContextEntry::navigation_seed_is_initial_about_blank_commit);
|
||||
seed_is_initial_about_blank
|
||||
self.child_current_document_is_initial_empty(handle)
|
||||
&& self
|
||||
.child_browsing_context_current_url(handle)
|
||||
.is_some_and(|url| moli_url::is_about_blank(&url))
|
||||
|
||||
@@ -11830,6 +11830,64 @@ fn no_src_iframe_initial_about_blank_has_a_quirks_empty_document() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_joint_history_pushes_accumulate_across_distinct_frames() {
|
||||
let mut vm = new_storage_test_vm("https://joint-child-length.test/page.html");
|
||||
|
||||
vm.exec(
|
||||
r#"
|
||||
const first = document.createElement('iframe');
|
||||
first.srcdoc = '<p>first</p>';
|
||||
(document.body || document.documentElement || document).appendChild(first);
|
||||
globalThis.__firstJointLengthFrame = first;
|
||||
"#,
|
||||
None,
|
||||
)
|
||||
.expect("first child setup should evaluate");
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const child = __firstJointLengthFrame.contentWindow;
|
||||
const before = history.length;
|
||||
child.history.pushState(null, '', '#first');
|
||||
return [before, history.length, child.history.length].join('|');
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("first child history push should evaluate"),
|
||||
"1|2|2"
|
||||
);
|
||||
|
||||
vm.exec(
|
||||
r#"
|
||||
__firstJointLengthFrame.remove();
|
||||
const second = document.createElement('iframe');
|
||||
second.srcdoc = '<p>second</p>';
|
||||
(document.body || document.documentElement || document).appendChild(second);
|
||||
globalThis.__secondJointLengthFrame = second;
|
||||
"#,
|
||||
None,
|
||||
)
|
||||
.expect("second child setup should evaluate");
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const child = __secondJointLengthFrame.contentWindow;
|
||||
const before = history.length;
|
||||
child.history.pushState(null, '', '#second');
|
||||
return [before, history.length, child.history.length].join('|');
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("second child history push should evaluate"),
|
||||
"2|3|3"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn top_history_back_routes_to_child_joint_history_entry() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
|
||||
Reference in New Issue
Block a user