mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 16:01:01 +00:00
refactor(renderer): unify child Window realm state
This commit is contained in:
@@ -347,7 +347,8 @@ pub(crate) use self::performance_runtime::{
|
||||
ResourcePerformanceEntry, bind_window_performance_seed, current_performance_time_origin,
|
||||
increment_performance_event_count, record_performance_dom_content_loaded_event_end,
|
||||
record_performance_dom_content_loaded_event_start, record_performance_load_event_end,
|
||||
record_performance_load_event_start, record_resource_performance_entry,
|
||||
record_performance_load_event_end_for_window, record_performance_load_event_start,
|
||||
record_performance_load_event_start_for_window, record_resource_performance_entry,
|
||||
run_resource_timing_buffer_full_task,
|
||||
};
|
||||
use self::range::callback_arg_node_object;
|
||||
|
||||
@@ -193,6 +193,43 @@ pub(crate) fn current_performance_time_origin(scope: &mut v8::PinScope<'_, '_>)
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub(crate) fn record_performance_load_event_start_for_window(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
record_performance_load_event_for_window(
|
||||
scope,
|
||||
window,
|
||||
install::record_performance_load_event_start,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn record_performance_load_event_end_for_window(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
record_performance_load_event_for_window(
|
||||
scope,
|
||||
window,
|
||||
install::record_performance_load_event_end,
|
||||
);
|
||||
}
|
||||
|
||||
fn record_performance_load_event_for_window(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
record: fn(&mut v8::PinScope<'_, '_>),
|
||||
) {
|
||||
let Some(relevant_context) = window.get_creation_context(scope) else {
|
||||
return;
|
||||
};
|
||||
if relevant_context == scope.get_current_context() {
|
||||
record(scope);
|
||||
return;
|
||||
}
|
||||
let scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
record(scope);
|
||||
}
|
||||
pub(crate) struct ResourcePerformanceEntry {
|
||||
name: String,
|
||||
initiator_type: String,
|
||||
|
||||
@@ -785,6 +785,12 @@ pub(crate) fn record_performance_dom_content_loaded_event_start(scope: &mut v8::
|
||||
super::window_state::record_pending_dom_content_loaded_start(scope);
|
||||
return;
|
||||
};
|
||||
if performance_lifecycle_timestamp(scope, performance, DOM_CONTENT_LOADED_START_INDEX)
|
||||
.unwrap_or(0.0)
|
||||
> 0.0
|
||||
{
|
||||
return;
|
||||
}
|
||||
let previous = 0.0;
|
||||
let timestamp = monotonic_lifecycle_timestamp(scope, performance, previous);
|
||||
apply_dom_content_loaded_start(scope, performance, timestamp);
|
||||
@@ -819,6 +825,12 @@ pub(crate) fn record_performance_dom_content_loaded_event_end(scope: &mut v8::Pi
|
||||
super::window_state::record_pending_dom_content_loaded_end(scope);
|
||||
return;
|
||||
};
|
||||
if performance_lifecycle_timestamp(scope, performance, DOM_CONTENT_LOADED_END_INDEX)
|
||||
.unwrap_or(0.0)
|
||||
> 0.0
|
||||
{
|
||||
return;
|
||||
}
|
||||
let previous =
|
||||
performance_lifecycle_timestamp(scope, performance, DOM_CONTENT_LOADED_START_INDEX)
|
||||
.unwrap_or(0.0);
|
||||
@@ -853,6 +865,9 @@ pub(crate) fn record_performance_load_event_start(scope: &mut v8::PinScope<'_, '
|
||||
super::window_state::record_pending_load_start(scope);
|
||||
return;
|
||||
};
|
||||
if performance_lifecycle_timestamp(scope, performance, LOAD_START_INDEX).unwrap_or(0.0) > 0.0 {
|
||||
return;
|
||||
}
|
||||
let previous =
|
||||
performance_lifecycle_timestamp(scope, performance, DOM_CONTENT_LOADED_END_INDEX)
|
||||
.unwrap_or(0.0);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::super::fixture_support::child_upstream_fixture_text;
|
||||
use super::super::{ChildBrowsingContextBootstrap, JsContextHost};
|
||||
use crate::document_runtime::DomHandle;
|
||||
use url::Url;
|
||||
@@ -7,7 +8,7 @@ impl JsContextHost {
|
||||
&self,
|
||||
url: &Url,
|
||||
) -> bool {
|
||||
matches!(url.scheme(), "http" | "https")
|
||||
matches!(url.scheme(), "http" | "https") && child_upstream_fixture_text(url).is_none()
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host::child_documents) fn child_document_bootstrap_requires_async_load(
|
||||
|
||||
@@ -9,6 +9,8 @@ use crate::{
|
||||
context_bootstrap::{
|
||||
construct_original_event, dispatch_beforeunload_for_runtime_owner,
|
||||
dispatch_pagehide_for_runtime_owner, dispatch_unload_for_runtime_owner,
|
||||
record_performance_dom_content_loaded_event_end,
|
||||
record_performance_dom_content_loaded_event_start,
|
||||
},
|
||||
detached_event_target::dispatch_detached_simple_event,
|
||||
document_runtime::DomHandle,
|
||||
@@ -712,6 +714,7 @@ impl JsContextHost {
|
||||
{
|
||||
return FrameDocumentLifecycleTaskEffect::NotApplied;
|
||||
}
|
||||
record_performance_dom_content_loaded_event_start(scope);
|
||||
let event_dispatched = self
|
||||
.child_browsing_context_document_wrapper(scope, child_handle)
|
||||
.is_some_and(|document| {
|
||||
@@ -725,6 +728,7 @@ impl JsContextHost {
|
||||
false,
|
||||
)
|
||||
});
|
||||
record_performance_dom_content_loaded_event_end(scope);
|
||||
if self
|
||||
.frame_owner_store
|
||||
.child_document_task_owner_is_current(child_handle, action.owner())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::super::fixture_support::child_upstream_fixture_text;
|
||||
use super::super::{ChildBrowsingContextBootstrap, ChildBrowsingContextSnapshot, JsContextHost};
|
||||
use super::configure_child_document_navigation_request;
|
||||
use crate::document_runtime::{DocumentPolicyContainer, DomHandle};
|
||||
@@ -46,7 +47,13 @@ impl JsContextHost {
|
||||
url.clone(),
|
||||
"<!DOCTYPE html><html><head></head><body></body></html>".into(),
|
||||
)),
|
||||
"http" | "https" => None,
|
||||
"http" | "https" => child_upstream_fixture_text(url).map(|markup| {
|
||||
ChildBrowsingContextSnapshot::new(
|
||||
url.clone(),
|
||||
markup,
|
||||
child_document_content_type_for_url(url),
|
||||
)
|
||||
}),
|
||||
"blob" => {
|
||||
let (body, mime_type) = crate::blob::object_url_body_and_type(url.as_str())?;
|
||||
if !mime_type.is_empty() && !is_html_document_mime(&mime_type) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use url::Url;
|
||||
|
||||
/// Resolve upstream browser-test child documents from the renderer fixture
|
||||
/// corpus. This is intentionally available only to tests and debug builds;
|
||||
/// production HTTP(S) navigations must use the network loader.
|
||||
pub(super) fn child_upstream_fixture_text(url: &Url) -> Option<String> {
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
{
|
||||
let path = url.path().strip_prefix("/src/browser/tests/")?;
|
||||
let relative_path = path.replace("%20", " ");
|
||||
let fixture_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/lightpanda/upstream")
|
||||
.join(relative_path);
|
||||
std::fs::read_to_string(fixture_path).ok()
|
||||
}
|
||||
#[cfg(not(any(test, debug_assertions)))]
|
||||
{
|
||||
let _ = url;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn child_upstream_fixture_lookup_is_path_scoped() {
|
||||
let fixture = Url::parse(
|
||||
"https://example.test/src/browser/tests/frames/support/target_fragment_child.html",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
child_upstream_fixture_text(&fixture)
|
||||
.is_some_and(|source| source.contains("id=\"target\""))
|
||||
);
|
||||
|
||||
let unrelated = Url::parse(
|
||||
"https://example.test/src/browser/test/frames/support/target_fragment_child.html",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(child_upstream_fixture_text(&unrelated).is_none());
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ use crate::{
|
||||
context_bootstrap::{
|
||||
LocationNavigationKind, construct_original_event, construct_original_page_transition_event,
|
||||
meta_refresh_navigation_kind, navigate_location_object_with_child_navigate_event,
|
||||
record_performance_load_event_end_for_window,
|
||||
record_performance_load_event_start_for_window,
|
||||
},
|
||||
document_runtime::{DomHandle, EventTargetHandle, MetaRefreshNavigation},
|
||||
frame_owner_model::{
|
||||
@@ -400,13 +402,20 @@ impl JsContextHost {
|
||||
action: FrameDocumentLoadDeliveryAction,
|
||||
) -> ChildFrameLoadDeliveryPhaseResult {
|
||||
let handle = action.child_handle();
|
||||
let performance_window = self.existing_child_browsing_context_window_wrapper(scope, handle);
|
||||
let Some(event) = construct_original_event(scope, "load") else {
|
||||
self.abort_and_requeue_child_load_delivery(action);
|
||||
return ChildFrameLoadDeliveryPhaseResult::without_callback(None);
|
||||
};
|
||||
if let Some(window) = performance_window {
|
||||
record_performance_load_event_start_for_window(scope, window);
|
||||
}
|
||||
self.enter_child_browsing_context_host_load_dispatch(handle);
|
||||
self.dispatch_child_window_event(scope, handle, "load", event);
|
||||
self.leave_child_browsing_context_host_load_dispatch(handle);
|
||||
if let Some(window) = performance_window {
|
||||
record_performance_load_event_end_for_window(scope, window);
|
||||
}
|
||||
let progress = self
|
||||
.frame_owner_store
|
||||
.finish_current_child_document_load_delivery(action);
|
||||
|
||||
@@ -92,6 +92,7 @@ mod element_toggle_events;
|
||||
mod event_callbacks;
|
||||
mod file_chooser;
|
||||
mod file_entry_file_callbacks;
|
||||
mod fixture_support;
|
||||
mod focus;
|
||||
mod frame_document_ready_routes;
|
||||
mod hash_changes;
|
||||
|
||||
@@ -1054,6 +1054,61 @@ fn performance_navigation_timing_updates_at_load_event_end() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn performance_navigation_lifecycle_timestamps_are_write_once_per_navigation() {
|
||||
let mut vm = new_storage_test_vm("https://performance-navigation-write-once.test/");
|
||||
|
||||
vm.dispatch_document_lifecycle_event("DOMContentLoaded")
|
||||
.expect("first DOMContentLoaded should update navigation timing");
|
||||
vm.dispatch_window_load_event()
|
||||
.expect("first load should update navigation timing");
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.__initialNavigationLifecycleTiming = (() => {
|
||||
const navigation = performance.getEntriesByType("navigation")[0];
|
||||
return [
|
||||
navigation.domInteractive,
|
||||
navigation.domContentLoadedEventStart,
|
||||
navigation.domContentLoadedEventEnd,
|
||||
navigation.domComplete,
|
||||
navigation.loadEventStart,
|
||||
navigation.loadEventEnd
|
||||
];
|
||||
})();
|
||||
"#,
|
||||
)
|
||||
.expect("initial navigation lifecycle timing should be captured");
|
||||
|
||||
vm.dispatch_document_lifecycle_event("DOMContentLoaded")
|
||||
.expect("repeated DOMContentLoaded should remain dispatchable");
|
||||
vm.dispatch_window_load_event()
|
||||
.expect("repeated load should remain dispatchable");
|
||||
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const navigation = performance.getEntriesByType("navigation")[0];
|
||||
const current = [
|
||||
navigation.domInteractive,
|
||||
navigation.domContentLoadedEventStart,
|
||||
navigation.domContentLoadedEventEnd,
|
||||
navigation.domComplete,
|
||||
navigation.loadEventStart,
|
||||
navigation.loadEventEnd
|
||||
];
|
||||
return current.every(
|
||||
(value, index) =>
|
||||
value === globalThis.__initialNavigationLifecycleTiming[index]
|
||||
);
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("repeated navigation lifecycle timing should be compared"),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn materialized_legacy_performance_timing_uses_integer_milliseconds() {
|
||||
const TIME_ORIGIN: f64 = 1_700_000_000_000.75;
|
||||
|
||||
@@ -6381,6 +6381,89 @@ async fn element_matches_delegates_loaded_child_document_elements() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn child_navigation_performance_name_updates_after_iframe_src_change() {
|
||||
let mut vm = new_storage_test_vm("https://child-navigation-performance.test/page.html");
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
globalThis.__childNavigationLoadCount = 0;
|
||||
const frame = document.createElement("iframe");
|
||||
globalThis.__childNavigationFrame = frame;
|
||||
frame.onload = () => {
|
||||
globalThis.__childNavigationLoadCount++;
|
||||
};
|
||||
frame.src = "/src/browser/tests/navigation-timing/resources/blank_page_green.html";
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("child navigation performance setup should evaluate");
|
||||
let loads_before_initial_dispatch = vm
|
||||
.eval("String(globalThis.__childNavigationLoadCount)")
|
||||
.expect("initial child navigation load count should evaluate");
|
||||
assert_eq!(loads_before_initial_dispatch, "0");
|
||||
run_child_navigation_commit_and_host_load_for_test(
|
||||
&mut vm,
|
||||
"initial child navigation performance load",
|
||||
)
|
||||
.await;
|
||||
let first = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = __childNavigationFrame;
|
||||
const entry = frame.contentWindow.performance.getEntriesByType("navigation")[0];
|
||||
const timing = frame.contentWindow.performance.timing;
|
||||
return [
|
||||
entry.name === frame.contentWindow.location.href,
|
||||
entry.name.endsWith("/blank_page_green.html"),
|
||||
globalThis.__childNavigationLoadCount,
|
||||
timing.domInteractive > timing.navigationStart,
|
||||
timing.loadEventStart >= timing.domInteractive,
|
||||
timing.loadEventEnd >= timing.loadEventStart,
|
||||
frame.contentWindow.performance.now() >=
|
||||
timing.loadEventEnd - timing.navigationStart
|
||||
].join(":");
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("first child navigation performance probe should evaluate");
|
||||
assert_eq!(first, "true:true:1:true:true:true:true");
|
||||
|
||||
vm.eval(
|
||||
r#"__childNavigationFrame.src = "/src/browser/tests/navigation-timing/resources/blank_page_yellow.html";"#,
|
||||
)
|
||||
.expect("second child navigation should queue");
|
||||
let loads_before_second_dispatch = vm
|
||||
.eval("String(globalThis.__childNavigationLoadCount)")
|
||||
.expect("second child navigation pre-HostLoad load count should evaluate");
|
||||
assert_eq!(loads_before_second_dispatch, "1");
|
||||
run_child_navigation_commit_and_host_load_for_test(
|
||||
&mut vm,
|
||||
"second child navigation performance load",
|
||||
)
|
||||
.await;
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = __childNavigationFrame;
|
||||
const entry = frame.contentWindow.performance.getEntriesByType("navigation")[0];
|
||||
return [
|
||||
entry.name === frame.contentWindow.location.href,
|
||||
entry.name.endsWith("/blank_page_yellow.html"),
|
||||
globalThis.__childNavigationLoadCount
|
||||
].join(":");
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("second child navigation performance probe should evaluate");
|
||||
|
||||
assert_eq!(result, "true:true:2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn child_content_document_getter_does_not_enumerate_script_wrappers_after_load() {
|
||||
let mut vm = new_storage_test_vm("https://child-content-document-getter-script-state.test/");
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
<!doctype html>
|
||||
<script>parent.postMessage("form navigation", "*");</script>
|
||||
form navigation
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<div id="target"></div>
|
||||
</body>
|
||||
</html>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<!doctype html>
|
||||
<html><body>green</body></html>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<!doctype html>
|
||||
<html><body>yellow</body></html>
|
||||
Reference in New Issue
Block a user