mirror of
https://github.com/lexmount/moli.git
synced 2026-11-04 16:06:44 +00:00
fix(runtime): integrate WPT fixes with current main
Route existing Window.open targets through native navigation without the Location-specific before-load replacement rule. Run form event assertions through queued submission tasks using fully loaded child Documents, and retain target-realm FormData and request payload checks. Reconcile the expanded FontFace and IndexedDB fixtures, restore the SVG precision test registration, and adapt ignored-response assertions to the NoDocument outcome while retaining response metadata checks.
This commit is contained in:
@@ -23,7 +23,7 @@ async fn font_face_binary_sources_validate_payloads_and_buffer_conversion_order(
|
||||
.context("binary FontFace results")?,
|
||||
)?;
|
||||
assert_eq!(value["failures"], serde_json::json!([]), "{value}");
|
||||
assert_eq!(value["rows"].as_array().unwrap().len(), 52, "{value}");
|
||||
assert_eq!(value["rows"].as_array().unwrap().len(), 58, "{value}");
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
use super::event_dispatch::run_probe;
|
||||
use super::*;
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn indexed_db_key_ranges_protect_bounds_and_validate_receivers() -> Result<()> {
|
||||
let server = FixtureServer::spawn().await?;
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
let fixture = include_str!("fixtures/indexeddb-key-range.js");
|
||||
for target in ["window", "child", "worker"] {
|
||||
let source = format!(
|
||||
"{fixture}\nkeyRangeProbe('key-range-{target}').then(finish, error => finish({{state: 'error', error: String(error), checks: rangeChecks}}));"
|
||||
);
|
||||
let result = run_probe(&browser, &server, target, &source).await?;
|
||||
assert_eq!(result["state"], "pass", "{target}: {result}");
|
||||
assert_eq!(
|
||||
result["checks"].as_array().unwrap().len(),
|
||||
if target == "worker" { 301 } else { 313 },
|
||||
"{target}: {result}"
|
||||
);
|
||||
}
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn indexed_db_database_names_preserve_utf16_identity_across_realms_and_queues() -> Result<()>
|
||||
{
|
||||
@@ -308,7 +287,7 @@ async fn indexed_db_key_ranges_protect_bounds_and_validate_receivers() -> Result
|
||||
assert_eq!(result["state"], "pass", "{target}: {result}");
|
||||
assert_eq!(
|
||||
result["checks"].as_array().unwrap().len(),
|
||||
if target == "worker" { 317 } else { 329 },
|
||||
if target == "worker" { 341 } else { 353 },
|
||||
"{target}: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1612,6 +1612,7 @@ fn no_content_navigation_retains_response_metadata_with_live_progress() {
|
||||
request_load_policy: NavigationRequestLoadPolicy::BrowserInitiated,
|
||||
timestamp: 12.5,
|
||||
source_document_security: Default::default(),
|
||||
navigation_history: None,
|
||||
};
|
||||
let (live_source, _receiver) = live_progress_source();
|
||||
let source = MainDocumentBodyProgressSource::from_live_source(
|
||||
@@ -1625,14 +1626,12 @@ fn no_content_navigation_retains_response_metadata_with_live_progress() {
|
||||
final_url: Url::parse("http://example.test/final").unwrap(),
|
||||
network_events: source.completed_body_network_events(events),
|
||||
};
|
||||
let MaterializedNavigationLoadOutcome::Failed(mut failed) =
|
||||
materialize_no_content_navigation_progress(&conn, &state, navigation)
|
||||
let MaterializedNavigationLoadOutcome::NoDocument(mut progress_gate) =
|
||||
materialize_no_content_navigation_progress(&mut conn, &state, navigation)
|
||||
else {
|
||||
panic!("ignored responses must retain main's failed-navigation projection");
|
||||
panic!("ignored responses must preserve the committed Document");
|
||||
};
|
||||
let out = drain_gate_until_body_finished_visible_into_protocol_messages(
|
||||
&mut failed.progress_gate,
|
||||
);
|
||||
let out = drain_gate_until_body_finished_visible_into_protocol_messages(&mut progress_gate);
|
||||
let response = out
|
||||
.iter()
|
||||
.find(|event| event["method"] == "Network.responseReceived")
|
||||
@@ -1651,9 +1650,9 @@ fn no_content_navigation_retains_response_metadata_with_live_progress() {
|
||||
failure["params"]["errorText"],
|
||||
json!(moli_fetch::NET_ERR_ABORTED_ERROR_TEXT)
|
||||
);
|
||||
assert_eq!(
|
||||
failed.document_policy,
|
||||
FailedNavigationDocumentPolicy::PreserveCommittedDocument
|
||||
assert!(
|
||||
!out.iter()
|
||||
.any(|event| event["method"] == "Page.frameNavigated")
|
||||
);
|
||||
assert!(
|
||||
!out.iter()
|
||||
|
||||
@@ -105,7 +105,7 @@ mod navigation_traversal_plan;
|
||||
mod navigation_window;
|
||||
pub(crate) use navigation_window::{
|
||||
navigation_unload_event_active, replace_navigation_unload_event_active,
|
||||
runtime_window_dispatch_scope, window_location_for_holder,
|
||||
window_location_for_holder,
|
||||
};
|
||||
mod navigator_runtime;
|
||||
mod platform_object_worlds;
|
||||
|
||||
@@ -12,7 +12,7 @@ use super::{
|
||||
parse_cursor_direction, parse_cursor_direction_with_context, parse_idb_key_path,
|
||||
parse_key_or_range, parse_key_range_from_value, parse_optional_count,
|
||||
prepare_object_store_write, remove_database_index_metadata, request_error_object,
|
||||
require_idb_key, scan_index_entries, scan_object_store_entries, set_database_index_metadata,
|
||||
scan_index_entries, scan_object_store_entries, set_database_index_metadata,
|
||||
store_request_error, store_request_success, submit_cursor_open_operation,
|
||||
transaction_handle_from_value, with_indexed_db_manager,
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ use super::navigation_entry_state::clone_navigation_entry_state;
|
||||
use super::navigation_events::{
|
||||
NavigationDispatchOutcome, cancel_active_navigation_event,
|
||||
dispatch_cross_document_navigation_navigate_event_for_window_with_type_and_form_data,
|
||||
dispatch_navigation_navigate_event_with_form_data_and_outcome,
|
||||
dispatch_navigation_navigate_event_with_outcome, dispatch_popstate_event,
|
||||
finish_navigation_precommit, queue_hash_change_for_runtime_owner,
|
||||
};
|
||||
|
||||
@@ -36,8 +36,8 @@ use super::super::{
|
||||
set_reflected_boolean_attribute, submit_form_with_submit_event, update_focus,
|
||||
};
|
||||
use super::targets::{
|
||||
SpecialBrowsingContextTarget, named_iframe_target_handle_for_navigation,
|
||||
navigate_element_target_browsing_context,
|
||||
SpecialBrowsingContextTarget, browsing_context_window_for_dispatch_scope,
|
||||
named_iframe_target_handle_for_navigation, navigate_element_target_browsing_context,
|
||||
};
|
||||
|
||||
fn array_like_length(scope: &mut v8::PinScope<'_, '_>, object: v8::Local<'_, v8::Object>) -> u32 {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
RendererPendingPopupActivation, RendererPendingWindowOpenEvent, RendererPopupDisposition,
|
||||
document_runtime::{DocumentPolicyContainer, DomHandle},
|
||||
util::v8str,
|
||||
};
|
||||
|
||||
use super::super::super::JsContextHost;
|
||||
@@ -35,37 +34,6 @@ impl SpecialBrowsingContextTarget {
|
||||
}
|
||||
}
|
||||
|
||||
fn navigate_target_window_location(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
resolved_url: &str,
|
||||
) -> bool {
|
||||
let Some(value) = crate::util::v8_string(scope, resolved_url) else {
|
||||
return false;
|
||||
};
|
||||
window
|
||||
.set(scope, v8str(scope, "location").into(), value.into())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn queue_top_level_location_navigation(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
resolved_url: &str,
|
||||
source: crate::native_bridge::OwnerDispatchScope,
|
||||
) -> bool {
|
||||
let Ok(url) = url::Url::parse(resolved_url) else {
|
||||
return false;
|
||||
};
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let about_document_state = runtime.capture_about_document_state(source, &url, None);
|
||||
if url.scheme() != "javascript" && !runtime.dispatch_main_document_tree_beforeunload(scope) {
|
||||
return false;
|
||||
}
|
||||
runtime.record_pending_location_navigation(url, None, about_document_state);
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct ElementPopupRelations {
|
||||
suppress_opener: bool,
|
||||
@@ -646,8 +614,17 @@ pub(crate) fn navigate_existing_browsing_context_target<'s>(
|
||||
let Some(resolved_url) = resolved_url else {
|
||||
return Some(target_window);
|
||||
};
|
||||
let navigated = navigate_target_window_location(scope, target_window, resolved_url);
|
||||
if navigated { Some(target_window) } else { None }
|
||||
let location = crate::context_bootstrap::window_location_for_holder(scope, target_window)?;
|
||||
// Window.open navigates directly. The before-load replacement rule belongs
|
||||
// to Location-object navigation and must not affect this history handling.
|
||||
crate::context_bootstrap::navigate_location_object_with_source_element(
|
||||
scope,
|
||||
location,
|
||||
crate::context_bootstrap::LocationNavigationKind::Assign,
|
||||
Some(resolved_url.to_owned()),
|
||||
None,
|
||||
);
|
||||
Some(target_window)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
+105
-27
@@ -355,32 +355,65 @@ fn hyperlink_top_navigation_checks_source_document_sandbox_across_realms() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn form_navigation_events_use_resolved_top_window_across_realms() {
|
||||
async fn load_form_navigation_frames(
|
||||
vm: &mut crate::runtime::PageVmTaskExecutorTestHarness,
|
||||
loader: &ResourceRequestClient,
|
||||
depth: usize,
|
||||
) {
|
||||
vm.eval("globalThis.formNavigationSource = document; globalThis.formNavigationFrames = [];")
|
||||
.unwrap();
|
||||
for _ in 0..depth {
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.formNavigationFrame = formNavigationSource.createElement('iframe');
|
||||
formNavigationFrame.srcdoc = '<body></body>';
|
||||
formNavigationSource.body.appendChild(formNavigationFrame);
|
||||
formNavigationFrames.push(formNavigationFrame);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
vm,
|
||||
loader,
|
||||
"String(formNavigationFrame.contentDocument.URL === 'about:srcdoc' && \
|
||||
formNavigationFrame.contentDocument.readyState === 'complete')",
|
||||
"true",
|
||||
"form navigation source frame",
|
||||
)
|
||||
.await;
|
||||
vm.eval("formNavigationSource = formNavigationFrame.contentDocument;")
|
||||
.unwrap();
|
||||
}
|
||||
vm.drain_ready_page_task_executor_turns_for_setup(loader, 100)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn form_navigation_events_use_resolved_top_window_across_realms() {
|
||||
for (depth, target) in [(1, "_top"), (1, "_PARENT"), (2, "_ToP")] {
|
||||
for method in ["get", "post"] {
|
||||
for api in ["submit", "requestSubmit"] {
|
||||
for use_child_realm in [true, false] {
|
||||
for cancel_top in [true, false] {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
let loader = static_http_loader([]);
|
||||
let mut vm = new_storage_page_task_executor_test_vm_with_loader(
|
||||
"https://form-navigation-target.test/source",
|
||||
"<!doctype html><body></body>",
|
||||
&loader,
|
||||
);
|
||||
let description = format!(
|
||||
"depth={depth} target={target} method={method} api={api} \
|
||||
child_realm={use_child_realm} cancel_top={cancel_top}"
|
||||
);
|
||||
load_form_navigation_frames(&mut vm, &loader, depth).await;
|
||||
let result = vm
|
||||
.eval(&format!(
|
||||
r#"
|
||||
(() => {{
|
||||
const seen = [];
|
||||
let source = document;
|
||||
const seen = globalThis.__lmFormNavigationLog = [];
|
||||
const source = formNavigationSource;
|
||||
for (let i = 0; i < {depth}; ++i) {{
|
||||
const frame = source.createElement('iframe');
|
||||
source.body.appendChild(frame);
|
||||
frame.srcdoc = '<body></body>';
|
||||
source = frame.contentDocument;
|
||||
const frame = formNavigationFrames[i];
|
||||
frame.contentWindow.navigation.onnavigate = event => {{
|
||||
seen.push('child:' + i);
|
||||
event.preventDefault();
|
||||
@@ -420,6 +453,21 @@ fn form_navigation_events_use_resolved_top_window_across_realms() {
|
||||
"#,
|
||||
))
|
||||
.expect(&description);
|
||||
assert_eq!(result, "[]", "{description}");
|
||||
assert!(vm.take_pending_location_navigation_with_seed().is_none());
|
||||
// Execute the queued submission body before inspecting
|
||||
// its request, which task-end reconciliation consumes.
|
||||
assert!(
|
||||
vm.run_one_dom_manipulation_body_for_test(
|
||||
crate::runtime::PageDomManipulationTestFamily::FormNavigation,
|
||||
)
|
||||
.await
|
||||
.expect(&description),
|
||||
"{description}"
|
||||
);
|
||||
let result = vm
|
||||
.eval("JSON.stringify(__lmFormNavigationLog)")
|
||||
.expect(&description);
|
||||
let expected_url = if method == "get" {
|
||||
"https://form-navigation-target.test/submitted?value=b"
|
||||
} else {
|
||||
@@ -431,7 +479,7 @@ fn form_navigation_events_use_resolved_top_window_across_realms() {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"replace",
|
||||
"push",
|
||||
expected_url,
|
||||
true,
|
||||
true,
|
||||
@@ -473,38 +521,53 @@ fn form_navigation_events_use_resolved_top_window_across_realms() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_form_navigation_uses_target_intrinsic_form_data_constructor() {
|
||||
#[tokio::test]
|
||||
async fn post_form_navigation_uses_target_intrinsic_form_data_constructor() {
|
||||
for (depth, target) in [(1, "_top"), (1, "_self"), (2, "_parent"), (1, "receiver")] {
|
||||
for api in ["submit", "requestSubmit"] {
|
||||
for use_child_realm in [true, false] {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
let loader = static_http_loader([]);
|
||||
let mut vm = new_storage_page_task_executor_test_vm_with_loader(
|
||||
"https://form-navigation-target.test/source",
|
||||
"<!doctype html><body></body>",
|
||||
&loader,
|
||||
);
|
||||
let description = format!(
|
||||
"depth={depth} target={target} api={api} child_realm={use_child_realm}"
|
||||
);
|
||||
load_form_navigation_frames(&mut vm, &loader, depth).await;
|
||||
if target == "receiver" {
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.formNavigationReceiver = document.createElement('iframe');
|
||||
formNavigationReceiver.name = 'receiver';
|
||||
formNavigationReceiver.srcdoc = '<body></body>';
|
||||
document.body.appendChild(formNavigationReceiver);
|
||||
"#,
|
||||
)
|
||||
.expect(&description);
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(formNavigationReceiver.contentDocument.URL === 'about:srcdoc' && \
|
||||
formNavigationReceiver.contentDocument.readyState === 'complete')",
|
||||
"true",
|
||||
&description,
|
||||
)
|
||||
.await;
|
||||
vm.drain_ready_page_task_executor_turns_for_setup(&loader, 100)
|
||||
.await
|
||||
.expect(&description);
|
||||
}
|
||||
let result = vm
|
||||
.eval(&format!(
|
||||
r#"
|
||||
(() => {{
|
||||
const seen = [];
|
||||
let source = document;
|
||||
for (let i = 0; i < {depth}; ++i) {{
|
||||
const frame = source.createElement('iframe');
|
||||
source.body.appendChild(frame);
|
||||
frame.srcdoc = '<body></body>';
|
||||
source = frame.contentDocument;
|
||||
}}
|
||||
const source = formNavigationSource;
|
||||
const child = source.defaultView;
|
||||
let targetWindow = {target:?} === '_self' ? child : child.parent;
|
||||
if ({target:?} === 'receiver') {{
|
||||
const receiver = document.createElement('iframe');
|
||||
receiver.name = 'receiver';
|
||||
document.body.appendChild(receiver);
|
||||
receiver.srcdoc = '<body></body>';
|
||||
targetWindow = receiver.contentWindow;
|
||||
targetWindow = formNavigationReceiver.contentWindow;
|
||||
}}
|
||||
const TargetFormData = targetWindow.FormData;
|
||||
const ChildFormData = child.FormData;
|
||||
@@ -536,12 +599,27 @@ fn post_form_navigation_uses_target_intrinsic_form_data_constructor() {
|
||||
if (targetWindow !== window) event.preventDefault();
|
||||
}};
|
||||
const realm = {use_child_realm} ? child : window;
|
||||
globalThis.__lmFormDataNavigationSnapshot = () =>
|
||||
JSON.stringify([seen, constructorReads]);
|
||||
realm.HTMLFormElement.prototype[{api:?}].call(form);
|
||||
return JSON.stringify([seen, constructorReads]);
|
||||
}})()
|
||||
"#,
|
||||
))
|
||||
.expect(&description);
|
||||
assert_eq!(result, "[[],0]", "{description}");
|
||||
assert!(vm.take_pending_location_navigation_with_seed().is_none());
|
||||
assert!(
|
||||
vm.run_one_dom_manipulation_body_for_test(
|
||||
crate::runtime::PageDomManipulationTestFamily::FormNavigation,
|
||||
)
|
||||
.await
|
||||
.expect(&description),
|
||||
"{description}"
|
||||
);
|
||||
let result = vm
|
||||
.eval("__lmFormDataNavigationSnapshot()")
|
||||
.expect(&description);
|
||||
assert_eq!(
|
||||
result, r#"[[[true,true,true,"b","from-event"]],0]"#,
|
||||
"{description}"
|
||||
|
||||
@@ -2334,6 +2334,7 @@ mod storage_dense_name_arrays;
|
||||
mod streams;
|
||||
mod string_timers;
|
||||
mod svg_animated_number;
|
||||
mod svg_computed_path_precision;
|
||||
mod svg_cors_attributes;
|
||||
mod svg_element_receivers;
|
||||
mod svg_filter_enumerations;
|
||||
|
||||
Reference in New Issue
Block a user