mirror of
https://github.com/lexmount/moli.git
synced 2026-09-28 00:01:34 +00:00
fix(history): guard browser-owned traversal with sandbox policy
Check the History receiver's active document permission before handing an opaque history destination back to the top-level browser controller. Cover browser-owned steps in both directions, back/forward/go, dynamic sandbox attributes, and a child method borrowed by top-level History.
This commit is contained in:
@@ -538,6 +538,17 @@ fn queue_browser_owned_top_level_history_traversal<'s>(
|
||||
{
|
||||
return;
|
||||
}
|
||||
let top_owner = super::navigation_window::runtime_top_window_owner(scope, owner);
|
||||
if let Some(source_handle) =
|
||||
super::navigation_window::child_browsing_context_handle_for_runtime_owner(scope, owner)
|
||||
&& super::location_navigation::sandbox_blocks_ancestor_or_top_navigation_from_source(
|
||||
scope,
|
||||
source_handle,
|
||||
top_owner,
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
unsafe { &mut *host_ptr }.record_pending_top_level_history_traversal(delta);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,94 @@ fn top_level_navigation_intents_keep_one_last_writer_state() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn browser_owned_history_traversal_checks_active_source_sandbox() {
|
||||
use moli_session_history::{
|
||||
JointSessionHistory, SessionHistoryContextId, SessionHistoryEntry, SessionHistoryPosition,
|
||||
};
|
||||
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).unwrap();
|
||||
let url = "https://browser-owned-history.test/current";
|
||||
let restricted = "allow-scripts allow-same-origin";
|
||||
let permitted = "allow-scripts allow-same-origin allow-top-navigation";
|
||||
for (initial, changed, allowed) in [
|
||||
(None, Some(restricted), true),
|
||||
(Some(permitted), Some(restricted), true),
|
||||
(Some(restricted), None, false),
|
||||
(Some(restricted), Some(permitted), false),
|
||||
] {
|
||||
let mut vm = new_storage_page_task_executor_test_vm_with_loader(url, &loader);
|
||||
let mut seed = moli_page_types::initial_navigation_history_seed(false, url);
|
||||
let mut traversable = JointSessionHistory::new(SessionHistoryPosition::new(1, 3).unwrap());
|
||||
traversable.attach(
|
||||
SessionHistoryContextId::ROOT,
|
||||
None,
|
||||
SessionHistoryEntry {
|
||||
key: seed.entries[0].key.clone(),
|
||||
document: seed.entries[0].document_id.clone(),
|
||||
},
|
||||
);
|
||||
seed.session_history.traversable = Some(Box::new(traversable));
|
||||
vm.install_navigation_bootstrap_entry(Some(seed));
|
||||
let initial = serde_json::to_string(&initial).unwrap();
|
||||
let changed = serde_json::to_string(&changed).unwrap();
|
||||
vm.eval(&format!(
|
||||
r#"
|
||||
globalThis.frame = document.createElement('iframe');
|
||||
if ({initial} !== null) frame.setAttribute('sandbox', {initial});
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
frame.contentWindow;
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
vm.drain_ready_page_task_executor_turns_for_setup(&loader, 128)
|
||||
.await
|
||||
.unwrap();
|
||||
vm.eval(&format!(
|
||||
"if ({changed} === null) frame.removeAttribute('sandbox'); else frame.setAttribute('sandbox', {changed});"
|
||||
))
|
||||
.unwrap();
|
||||
{
|
||||
let mut host = vm._context_host.borrow_mut();
|
||||
let model = host.session_histories.get_mut(None);
|
||||
// Both destinations exist in browser history but have no renderer ROOT entry.
|
||||
for delta in [-1, 1] {
|
||||
let step = model.step_by_delta(delta).unwrap();
|
||||
assert!(
|
||||
!model
|
||||
.entries_at(step)
|
||||
.unwrap()
|
||||
.contains_key(&SessionHistoryContextId::ROOT)
|
||||
);
|
||||
}
|
||||
}
|
||||
for (call, delta) in [
|
||||
("back()", -1),
|
||||
("forward()", 1),
|
||||
("go(-1)", -1),
|
||||
("go(1)", 1),
|
||||
] {
|
||||
vm.eval(&format!("frame.contentWindow.history.{call}"))
|
||||
.unwrap();
|
||||
let pending = vm
|
||||
.take_pending_top_level_history_traversal()
|
||||
.map(|pending| pending.delta);
|
||||
assert_eq!(
|
||||
pending,
|
||||
allowed.then_some(delta),
|
||||
"initial={initial}, changed={changed}, call={call}"
|
||||
);
|
||||
}
|
||||
// A method borrowed from the child still uses its top-level History receiver.
|
||||
vm.eval("frame.contentWindow.history.back.call(history)")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.take_pending_top_level_history_traversal().unwrap().delta,
|
||||
-1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traversal_factories_apply_webidl_boundaries() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
|
||||
Reference in New Issue
Block a user