mirror of
https://github.com/lexmount/moli.git
synced 2026-09-23 08:01:28 +00:00
fix(history): preserve forward entries on cross-document replacement
This commit is contained in:
@@ -442,10 +442,9 @@ pub fn cross_document_navigation_seed(
|
||||
.iter()
|
||||
.find(|entry| entry.history_index == current_index)
|
||||
.cloned();
|
||||
entries.retain(|entry| entry.history_index <= current_index);
|
||||
|
||||
let (destination_entry, destination_index) = match mutation {
|
||||
NavigationHistoryMutation::Push => {
|
||||
entries.retain(|entry| entry.history_index <= current_index);
|
||||
let next_index = current_index + 1;
|
||||
let entry = navigation_history_entry(
|
||||
destination_url.as_str(),
|
||||
@@ -937,6 +936,46 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_document_navigation_seed_replace_preserves_forward_history() {
|
||||
let entries = (0..4)
|
||||
.map(|index| {
|
||||
let mut entry = navigation_history_entry(
|
||||
&format!("https://example.test/page-{index}"),
|
||||
index,
|
||||
index,
|
||||
NavigationHistoryDocumentId::allocate(),
|
||||
NavigationHistoryEntryId::allocate(),
|
||||
NavigationHistoryEntryKey::allocate(),
|
||||
Some(format!("{{\"classic\":{index}}}")),
|
||||
Some(format!("{{\"navigation\":{index}}}")),
|
||||
);
|
||||
entry.referrer_policy = Some("no-referrer".to_owned());
|
||||
entry
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for destination in [
|
||||
"https://example.test/replaced",
|
||||
"https://other.test/replaced",
|
||||
] {
|
||||
let seed = cross_document_navigation_seed(
|
||||
entries.clone(),
|
||||
1,
|
||||
1,
|
||||
&Url::parse(destination).unwrap(),
|
||||
NavigationHistoryMutation::Replace,
|
||||
);
|
||||
assert_eq!(seed.current_index, 1);
|
||||
assert_eq!(seed.entries.len(), entries.len());
|
||||
assert_eq!(seed.entries[0], entries[0]);
|
||||
assert_eq!(seed.entries[2..], entries[2..]);
|
||||
assert_eq!(seed.entries[1].url, destination);
|
||||
assert_ne!(seed.entries[1].document_id, entries[1].document_id);
|
||||
assert_ne!(seed.entries[1].id, entries[1].id);
|
||||
assert_eq!(seed.activation.as_ref().unwrap().entry, seed.entries[1]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_navigation_seed_activates_current_entry_from_itself() {
|
||||
let entries = vec![navigation_history_entry(
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use super::*;
|
||||
|
||||
const HISTORY_REPLACE_FORWARD: &str =
|
||||
include_str!("../../../tests/fixtures/history-replace-forward.js");
|
||||
|
||||
#[tokio::test]
|
||||
async fn cross_document_replace_preserves_forward_entries_and_states() {
|
||||
for method in ["location", "navigation"] {
|
||||
let server = StaticHttpServer::spawn(7).await;
|
||||
let base = server.base_url().origin().ascii_serialization();
|
||||
let loader = static_http_loader([]);
|
||||
let mut vm =
|
||||
new_storage_page_task_executor_test_vm_with_loader(&format!("{base}/parent"), &loader);
|
||||
vm.eval(&format!(
|
||||
"{HISTORY_REPLACE_FORWARD}\n\
|
||||
globalThis.replaceForwardResult = 'pending';\n\
|
||||
historyReplaceForward({base:?}, {method:?}).then(\n\
|
||||
value => replaceForwardResult = value,\n\
|
||||
error => replaceForwardResult = String(error));"
|
||||
))
|
||||
.unwrap();
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(replaceForwardResult !== 'pending')",
|
||||
"true",
|
||||
method,
|
||||
)
|
||||
.await;
|
||||
let result: serde_json::Value =
|
||||
serde_json::from_str(&vm.eval("JSON.stringify(replaceForwardResult)").unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!({
|
||||
"paths": ["/a", "/replaced", "/c"],
|
||||
"backwardIdentity": true,
|
||||
"replacementIdentity": true,
|
||||
"forwardIdentity": true,
|
||||
"savedState": {"navigation": "forward"},
|
||||
"forwardPath": "/c",
|
||||
"classicState": {"classic": "forward"},
|
||||
"navigationState": {"navigation": "forward"},
|
||||
"replacementPath": "/replaced",
|
||||
}),
|
||||
"{method}"
|
||||
);
|
||||
assert_eq!(
|
||||
server.finish_targets().await,
|
||||
["/a", "/b", "/c", "/b", "/replaced", "/c", "/replaced"],
|
||||
"{method}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15711,6 +15711,7 @@ mod fetch_request_guard;
|
||||
mod frame_element_security;
|
||||
mod headers_list;
|
||||
mod history_referrer;
|
||||
mod history_replace_forward;
|
||||
mod http_fixture;
|
||||
mod indexed_db;
|
||||
mod inspector_unwrap;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
async function historyReplaceForward(base, method) {
|
||||
const frame = document.createElement('iframe');
|
||||
const loaded = () => new Promise(resolve => frame.addEventListener('load', resolve, {once: true}));
|
||||
const settle = () => new Promise(resolve => setTimeout(resolve, 0));
|
||||
const initial = loaded();
|
||||
frame.src = base + '/a';
|
||||
document.body.appendChild(frame);
|
||||
await initial;
|
||||
async function navigate(action) {
|
||||
await settle();
|
||||
const completion = loaded();
|
||||
action(frame.contentWindow);
|
||||
await completion;
|
||||
await settle();
|
||||
}
|
||||
await navigate(w => w.location.href = base + '/b');
|
||||
await navigate(w => w.location.href = base + '/c');
|
||||
frame.contentWindow.history.replaceState({classic: 'forward'}, '');
|
||||
frame.contentWindow.navigation.updateCurrentEntry({state: {navigation: 'forward'}});
|
||||
const before = frame.contentWindow.navigation.entries().map(entry => ({
|
||||
id: entry.id, key: entry.key, url: entry.url,
|
||||
}));
|
||||
await navigate(w => w.history.back());
|
||||
await navigate(w => {
|
||||
if (method === 'location') w.location.replace(base + '/replaced');
|
||||
else w.navigation.navigate(base + '/replaced', {history: 'replace'});
|
||||
});
|
||||
const entries = frame.contentWindow.navigation.entries();
|
||||
const result = {
|
||||
paths: entries.map(entry => new URL(entry.url).pathname),
|
||||
backwardIdentity: entries[0].id === before[0].id && entries[0].key === before[0].key,
|
||||
replacementIdentity: entries[1].id !== before[1].id && entries[1].key === before[1].key,
|
||||
forwardIdentity: entries.length === 3 && entries[2].id === before[2].id && entries[2].key === before[2].key,
|
||||
};
|
||||
if (!result.forwardIdentity) {
|
||||
frame.remove();
|
||||
return result;
|
||||
}
|
||||
result.savedState = entries[2].getState();
|
||||
await navigate(w => w.history.forward());
|
||||
result.forwardPath = new URL(frame.contentWindow.location.href).pathname;
|
||||
result.classicState = frame.contentWindow.history.state;
|
||||
result.navigationState = frame.contentWindow.navigation.currentEntry.getState();
|
||||
await navigate(w => w.history.back());
|
||||
result.replacementPath = new URL(frame.contentWindow.location.href).pathname;
|
||||
frame.remove();
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user