mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 08:00:59 +00:00
fix(html): honor base targets across frame realms
Source-commit: 02e4964a24
This commit is contained in:
+22
-7
@@ -172,13 +172,28 @@ fn bind_frame_element<'s>(
|
||||
handle: crate::document_runtime::DomHandle,
|
||||
) {
|
||||
let host_ptr = host as *mut JsContextHost;
|
||||
if let Some(frame_element) = host
|
||||
.native_bridge_mut()
|
||||
.wrap_handle(scope, host_ptr, handle)
|
||||
.map(Into::into)
|
||||
{
|
||||
set_private_value(scope, global, "__moliWindowFrameElement", frame_element);
|
||||
}
|
||||
let Some(owner_context) =
|
||||
crate::native_bridge::node::node_owner_document_relevant_context(scope, host_ptr, handle)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let frame_element = {
|
||||
let owner_scope = &mut v8::ContextScope::new(scope, owner_context);
|
||||
let Some(frame_element) =
|
||||
host.native_bridge_mut()
|
||||
.wrap_handle(owner_scope, host_ptr, handle)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
v8::Global::new(owner_scope, frame_element)
|
||||
};
|
||||
let frame_element = v8::Local::new(scope, &frame_element);
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
"__moliWindowFrameElement",
|
||||
frame_element.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn bind_navigation<'s>(
|
||||
|
||||
@@ -1890,14 +1890,22 @@ fn anchor_click_default_action(
|
||||
None,
|
||||
);
|
||||
}
|
||||
let declared_target_name =
|
||||
element_attribute(runtime, handle, "target").filter(|value| !value.is_empty());
|
||||
let effective_target_name = element_attribute(runtime, handle, "target")
|
||||
.filter(|value| !value.is_empty())
|
||||
.or_else(|| {
|
||||
let document = runtime.dom_host().owner_document_handle(handle)?;
|
||||
runtime
|
||||
.dom_host()
|
||||
.document_base_target_for_handle(document)
|
||||
.filter(|target| !target.is_empty())
|
||||
.map(str::to_owned)
|
||||
});
|
||||
let (target_name, popup_disposition) = match navigation_policy {
|
||||
HyperlinkNavigationPolicy::Auxiliary(disposition) => {
|
||||
(Some("_blank".to_owned()), disposition)
|
||||
}
|
||||
HyperlinkNavigationPolicy::Current => {
|
||||
(declared_target_name, RendererPopupDisposition::Foreground)
|
||||
(effective_target_name, RendererPopupDisposition::Foreground)
|
||||
}
|
||||
HyperlinkNavigationPolicy::Download => {
|
||||
unreachable!("download hyperlink policy returned before target selection")
|
||||
|
||||
@@ -1346,6 +1346,9 @@ pub(super) fn node_owner_document_relevant_context<'s>(
|
||||
let document_handle = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.owner_document_handle(handle)?;
|
||||
if document_handle == unsafe { &*runtime_ptr }.document_handle() {
|
||||
return unsafe { &*runtime_ptr }.page_default_context(scope);
|
||||
}
|
||||
if let Some(child_handle) =
|
||||
unsafe { &*runtime_ptr }.child_browsing_context_host_for_document_handle(document_handle)
|
||||
&& let Some(context) =
|
||||
|
||||
+105
@@ -675,6 +675,111 @@ fn child_webassembly_constructors_use_newtarget_child_realm_default_prototype()
|
||||
r#"{"namespaceIsSeparate":true,"constructorFunctionPrototype":true,"moduleDirect":true,"moduleBound":true,"moduleProxy":true,"moduleProxyBound":true,"memoryProxy":true,"compileErrorDirect":true,"moduleTopRealm":true,"moduleIntrinsicSurvivesNamespaceReplacement":true}"#
|
||||
);
|
||||
}
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn base_target_navigation_exposes_replacement_document_before_iframe_load() {
|
||||
const HOST: &str = "anchor-base-target.test";
|
||||
|
||||
let server = StaticHttpServer::spawn(3).await;
|
||||
let top_url = server.url_for_host(HOST, "/path/page.html");
|
||||
let replacement_url = server.url_for_host(HOST, "/replacement.html");
|
||||
let loader = static_http_loader([server.resolve_entry(HOST)]);
|
||||
let mut vm = new_storage_page_task_executor_test_vm_with_loader(top_url.as_str(), &loader);
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
globalThis.__targetLoadCount = 0;
|
||||
globalThis.__targetLoadAccess = "pending";
|
||||
globalThis.__frameLoadCount = 0;
|
||||
globalThis.__baseTargetOwnerRealm = "pending";
|
||||
|
||||
const root = document.documentElement || document.appendChild(document.createElement('html'));
|
||||
const body = document.body || root.appendChild(document.createElement('body'));
|
||||
document.addEventListener('load', () => { __frameLoadCount += 1; }, true);
|
||||
body.innerHTML = `
|
||||
<iframe id="base-target-source" name="sourceFrame" src="/source.html"></iframe>
|
||||
<iframe id="base-target-target" name="targetFrame" src="/target.html"></iframe>
|
||||
`;
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("base-target network child setup should evaluate");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__frameLoadCount)",
|
||||
"2",
|
||||
"initial parser-created base-target child documents should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const source = document.getElementById('base-target-source');
|
||||
const target = document.getElementById('base-target-target');
|
||||
__baseTargetOwnerRealm = [
|
||||
source instanceof HTMLIFrameElement,
|
||||
target instanceof HTMLIFrameElement,
|
||||
target instanceof target.contentWindow.HTMLIFrameElement,
|
||||
target.contentWindow.frameElement === target
|
||||
].join('|');
|
||||
|
||||
const doc = source.contentDocument;
|
||||
const firstBase = doc.createElement('base');
|
||||
firstBase.target = 'targetFrame';
|
||||
const secondBase = doc.createElement('base');
|
||||
secondBase.target = '_self';
|
||||
doc.head.append(firstBase, secondBase);
|
||||
|
||||
const link = doc.createElement('a');
|
||||
link.href = '/replacement.html';
|
||||
link.setAttribute('target', '');
|
||||
doc.body.appendChild(link);
|
||||
|
||||
target.addEventListener('load', () => {
|
||||
__targetLoadCount += 1;
|
||||
try {
|
||||
__targetLoadAccess = target.contentDocument.location.href;
|
||||
} catch (error) {
|
||||
__targetLoadAccess = `${error && error.name}:${error && error.message}`;
|
||||
}
|
||||
}, true);
|
||||
link.click();
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("base-target replacement navigation should start");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__targetLoadCount)",
|
||||
"1",
|
||||
"base-target replacement document should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
vm.eval("__baseTargetOwnerRealm")
|
||||
.expect("base-target frame owner realm result should evaluate"),
|
||||
"true|true|false|true",
|
||||
"parser-created frame elements and window.frameElement must use the owner Document realm"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.eval("__targetLoadAccess")
|
||||
.expect("target load callback access result should evaluate"),
|
||||
replacement_url.as_str(),
|
||||
"the replacement Document must be same-origin accessible during the iframe load callback"
|
||||
);
|
||||
|
||||
let mut targets = server.finish_targets().await;
|
||||
targets.sort();
|
||||
assert_eq!(
|
||||
targets,
|
||||
["/replacement.html", "/source.html", "/target.html"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn targeted_anchor_click_reports_same_document_hash_change_for_child_window() {
|
||||
let mut vm = new_storage_test_vm("https://targeted-child-hash.test/page.html");
|
||||
|
||||
Reference in New Issue
Block a user