mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 16:01:00 +00:00
fix(forms): resolve navigation keywords from the form owner
This commit is contained in:
@@ -36,9 +36,9 @@ use super::super::{
|
||||
update_focus,
|
||||
};
|
||||
use super::targets::{
|
||||
SpecialBrowsingContextTarget, named_iframe_target_handle_for_navigation,
|
||||
navigate_hyperlink_source_browsing_context, navigate_hyperlink_target_browsing_context,
|
||||
navigate_target_browsing_context,
|
||||
SpecialBrowsingContextTarget, form_navigation_target_document,
|
||||
named_iframe_target_handle_for_navigation, navigate_hyperlink_source_browsing_context,
|
||||
navigate_hyperlink_target_browsing_context, navigate_target_browsing_context,
|
||||
};
|
||||
|
||||
fn array_like_length(scope: &mut v8::PinScope<'_, '_>, object: v8::Local<'_, v8::Object>) -> u32 {
|
||||
@@ -2339,17 +2339,21 @@ pub(in crate::native_bridge) fn navigate_form_target_browsing_context(
|
||||
&& !has_noopener
|
||||
&& (has_opener || special_target != Some(SpecialBrowsingContextTarget::Blank))
|
||||
};
|
||||
if target_name.is_none() || special_target == Some(SpecialBrowsingContextTarget::Current) {
|
||||
let target_document = {
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let document_handle = runtime
|
||||
runtime
|
||||
.dom_host()
|
||||
.node(form_handle)
|
||||
.and_then(Node::owner_document);
|
||||
if let Some(document_handle) = document_handle
|
||||
&& document_handle != runtime.document_handle()
|
||||
&& let Some(child_handle) =
|
||||
.owner_document_handle(form_handle)
|
||||
.and_then(|source| form_navigation_target_document(runtime, source, target_name))
|
||||
};
|
||||
if let Some(document_handle) = target_document {
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
if document_handle != runtime.document_handle() {
|
||||
let Some(child_handle) =
|
||||
runtime.child_browsing_context_handle_by_document_handle(scope, document_handle)
|
||||
{
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
return runtime.navigate_child_browsing_context_to_url(
|
||||
scope,
|
||||
|
||||
@@ -13,11 +13,13 @@ pub(crate) use default_action::{
|
||||
prepare_legacy_activation_for_dispatched_click, replace_contenteditable_selection,
|
||||
scroll_to_url_fragment_or_top, select_contenteditable_contents,
|
||||
};
|
||||
pub(in crate::native_bridge) use targets::named_iframe_target_handle_for_navigation;
|
||||
pub(crate) use targets::{
|
||||
SpecialBrowsingContextTarget, navigate_existing_browsing_context_target,
|
||||
navigate_named_iframe_target,
|
||||
};
|
||||
pub(in crate::native_bridge) use targets::{
|
||||
form_navigation_target_document, named_iframe_target_handle_for_navigation,
|
||||
};
|
||||
pub(in crate::native_bridge::element) use targets::{
|
||||
queue_deferred_named_iframe_target_navigation_from_document,
|
||||
queue_deferred_named_iframe_target_request,
|
||||
|
||||
@@ -37,6 +37,35 @@ impl SpecialBrowsingContextTarget {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve keywords from the native form Document, independently of the realm
|
||||
// from which its submission method was called. Both GET and POST use this.
|
||||
pub(in crate::native_bridge) fn form_navigation_target_document(
|
||||
runtime: &JsContextHost,
|
||||
source: crate::document_runtime::DomHandle,
|
||||
target_name: Option<&str>,
|
||||
) -> Option<crate::document_runtime::DomHandle> {
|
||||
// Lightweight popups retain their existing navigation routing.
|
||||
if source != runtime.document_handle()
|
||||
&& runtime
|
||||
.child_browsing_context_host_for_document_handle(source)
|
||||
.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let target = target_name.and_then(SpecialBrowsingContextTarget::parse);
|
||||
match (target_name, target) {
|
||||
(None, _) | (_, Some(SpecialBrowsingContextTarget::Current)) => Some(source),
|
||||
(_, Some(SpecialBrowsingContextTarget::Top)) => Some(runtime.document_handle()),
|
||||
(_, Some(SpecialBrowsingContextTarget::Parent)) => Some(
|
||||
runtime
|
||||
.child_browsing_context_host_for_document_handle(source)
|
||||
.and_then(|child| runtime.dom_host().owner_document_handle(child))
|
||||
.unwrap_or(source),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn navigate_target_window_location(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
|
||||
@@ -2,7 +2,8 @@ use super::*;
|
||||
use crate::blob;
|
||||
use crate::native_bridge::context_host::ChildBrowsingContextNavigationRequest;
|
||||
use crate::native_bridge::element::activation::{
|
||||
SpecialBrowsingContextTarget, named_iframe_target_handle_for_navigation,
|
||||
SpecialBrowsingContextTarget, form_navigation_target_document,
|
||||
named_iframe_target_handle_for_navigation,
|
||||
};
|
||||
use crate::native_bridge::element::{
|
||||
NodePublicEventDispatchOutcome, TextEditInputType, activate_default_submit_button_via_keyboard,
|
||||
@@ -707,21 +708,27 @@ pub(in crate::native_bridge) fn submit_form_default_action(
|
||||
content_type,
|
||||
form_data_entries,
|
||||
} => {
|
||||
if (target_name.is_none()
|
||||
|| special_target == Some(SpecialBrowsingContextTarget::Current))
|
||||
&& submit_post_form_to_child_self_browsing_context(
|
||||
let target_document = source_document.and_then(|source| {
|
||||
form_navigation_target_document(
|
||||
unsafe { &*runtime_ptr },
|
||||
source,
|
||||
target_name.as_deref(),
|
||||
)
|
||||
});
|
||||
if let Some(target_document) = target_document
|
||||
&& target_document != unsafe { &*runtime_ptr }.document_handle()
|
||||
{
|
||||
return submit_post_form_to_child_browsing_context(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
form_handle,
|
||||
submitter,
|
||||
source_document,
|
||||
resolved_url.clone(),
|
||||
body.clone(),
|
||||
content_type.clone(),
|
||||
target_document,
|
||||
resolved_url,
|
||||
body,
|
||||
content_type,
|
||||
&form_data_entries,
|
||||
)
|
||||
{
|
||||
return true;
|
||||
);
|
||||
}
|
||||
submit_post_form_to_top_level_browsing_context(
|
||||
scope,
|
||||
@@ -960,26 +967,23 @@ fn submit_post_form_to_top_level_browsing_context(
|
||||
true
|
||||
}
|
||||
|
||||
fn submit_post_form_to_child_self_browsing_context(
|
||||
fn submit_post_form_to_child_browsing_context(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
form_handle: DomHandle,
|
||||
submitter: Option<DomHandle>,
|
||||
source_document: Option<DomHandle>,
|
||||
target_document: DomHandle,
|
||||
resolved_url: Url,
|
||||
body: Vec<u8>,
|
||||
content_type: String,
|
||||
form_data_entries: &[(String, v8::Global<v8::Value>)],
|
||||
) -> bool {
|
||||
let Some(source_document) = source_document else {
|
||||
return false;
|
||||
};
|
||||
let Some(child_handle) = ({
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
if source_document == runtime.document_handle() {
|
||||
if target_document == runtime.document_handle() {
|
||||
None
|
||||
} else {
|
||||
runtime.child_browsing_context_host_for_document_handle(source_document)
|
||||
runtime.child_browsing_context_host_for_document_handle(target_document)
|
||||
}
|
||||
}) else {
|
||||
return false;
|
||||
|
||||
@@ -6008,3 +6008,122 @@ fn disconnected_radio_groups_follow_tree_roots_and_form_owners() {
|
||||
"true|true:false|true|true:false|true|true|true:true|false:true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_form_keywords_resolve_from_owner_for_get_and_post() {
|
||||
for depth in 0usize..=2 {
|
||||
for target in ["_self", "_PARENT", "_top"] {
|
||||
for method in ["get", "post"] {
|
||||
for api in ["submit", "requestSubmit"] {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://form-target.test/source",
|
||||
"<!doctype html><body></body>",
|
||||
);
|
||||
vm.eval(&format!(r#"
|
||||
(() => {{
|
||||
let source=document;
|
||||
for(let i=0;i<{depth};i++) {{
|
||||
const frame=source.createElement('iframe');
|
||||
(source.body||source.documentElement||source).appendChild(frame);
|
||||
frame.srcdoc='<body></body>';
|
||||
source=frame.contentDocument;
|
||||
}}
|
||||
const form=source.createElement('form');
|
||||
form.method={method:?};form.target={target:?};
|
||||
form.action='https://form-target.test/submitted';
|
||||
const input=source.createElement('input');input.name='value';input.value='b';
|
||||
form.appendChild(input);source.body.appendChild(form);
|
||||
HTMLFormElement.prototype[{api:?}].call(form);
|
||||
}})()
|
||||
"#)).expect("cross-realm native form submission");
|
||||
let expected_depth = match target {
|
||||
"_top" => 0,
|
||||
"_PARENT" => depth.saturating_sub(1),
|
||||
_ => depth,
|
||||
};
|
||||
let expected_url = if method == "get" {
|
||||
"https://form-target.test/submitted?value=b"
|
||||
} else {
|
||||
"https://form-target.test/submitted"
|
||||
};
|
||||
let description =
|
||||
format!("depth={depth} target={target} method={method} api={api}");
|
||||
let root_pending = vm.take_pending_location_navigation_with_seed();
|
||||
if expected_depth == 0 {
|
||||
let pending = root_pending.expect(&description);
|
||||
assert_eq!(pending.url.as_str(), expected_url, "{description}");
|
||||
assert_eq!(
|
||||
pending.request_method,
|
||||
method.to_ascii_uppercase(),
|
||||
"{description}"
|
||||
);
|
||||
assert_eq!(
|
||||
pending.request_body.as_deref(),
|
||||
(method == "post").then_some(&b"value=b"[..]),
|
||||
"{description}"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
root_pending.is_none(),
|
||||
"{description}: must retain top document"
|
||||
);
|
||||
}
|
||||
let mut parent_handle = None;
|
||||
for index in 0..depth {
|
||||
let host = vm._context_host.borrow();
|
||||
let handle = parent_handle
|
||||
.map_or_else(
|
||||
|| host.child_browsing_context_handle_by_index(0),
|
||||
|parent| {
|
||||
host.child_browsing_context_child_frame_handle_by_index(
|
||||
parent, 0,
|
||||
)
|
||||
},
|
||||
)
|
||||
.expect(&description);
|
||||
parent_handle = Some(handle);
|
||||
let pending =
|
||||
host.child_browsing_context_pending_live_navigation_for_test(handle);
|
||||
if expected_depth != index + 1 {
|
||||
assert!(
|
||||
pending.is_none(),
|
||||
"{description}: unexpected frame {index} navigation: {pending:?}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
use crate::native_bridge::ChildBrowsingContextBootstrap;
|
||||
match pending.expect(&description) {
|
||||
ChildBrowsingContextBootstrap::Url(url) if method == "get" => {
|
||||
assert_eq!(url.as_str(), expected_url, "{description}")
|
||||
}
|
||||
ChildBrowsingContextBootstrap::Request(request) => {
|
||||
assert_eq!(request.url.as_str(), expected_url, "{description}");
|
||||
assert_eq!(
|
||||
request.method,
|
||||
method.to_ascii_uppercase(),
|
||||
"{description}"
|
||||
);
|
||||
assert_eq!(
|
||||
request.body.as_deref(),
|
||||
(method == "post").then_some(&b"value=b"[..]),
|
||||
"{description}"
|
||||
);
|
||||
if method == "post" {
|
||||
assert_eq!(
|
||||
request.request_headers,
|
||||
vec![(
|
||||
"Content-Type".into(),
|
||||
"application/x-www-form-urlencoded".into()
|
||||
)],
|
||||
"{description}"
|
||||
);
|
||||
}
|
||||
}
|
||||
other => panic!("{description}: unexpected request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user