mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 00:00:44 +00:00
fix(renderer): isolate stale import completions
This commit is contained in:
@@ -93,8 +93,8 @@ pub(crate) use stylesheet_runtime::attribute_reprocesses_connected_stylesheet;
|
||||
use stylesheet_runtime::{ConnectedLinkReadinessFetchOptions, ConnectedLoadParameters};
|
||||
pub(crate) use stylesheet_runtime::{
|
||||
ConnectedLoadCompletion, LinkedStylesheetImportGraphCompletion,
|
||||
LiveStylesheetImportLoadCompletion, fetch_complete_stylesheet_import_graph,
|
||||
live_stylesheet_import_responses,
|
||||
LiveStylesheetImportLoadCompletion, StylesheetImportCompletionAuthority,
|
||||
fetch_complete_stylesheet_import_graph, live_stylesheet_import_responses,
|
||||
};
|
||||
use stylesheet_runtime::{
|
||||
ConnectedLoadOperation, LinkedStylesheetImportGraphs, QueuedConnectedStyleLoad,
|
||||
|
||||
@@ -1079,6 +1079,131 @@ document.head.append(retired);
|
||||
.expect("stale stylesheet Networking completion test should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn stale_linked_import_completion_does_not_repopulate_replacement_graph() {
|
||||
run_page_vm_async_test(async move {
|
||||
let (base_url, server) = spawn_path_response_http_server(vec![
|
||||
(
|
||||
"/stale-root.css",
|
||||
"HTTP/1.1 200 OK",
|
||||
"@import './stale-child.css';".to_owned(),
|
||||
Duration::ZERO,
|
||||
),
|
||||
(
|
||||
"/stale-child.css",
|
||||
"HTTP/1.1 200 OK",
|
||||
".retired-import { color: red; }".to_owned(),
|
||||
Duration::from_millis(100),
|
||||
),
|
||||
])
|
||||
.await;
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let document_url = Url::parse(&format!("{base_url}/page.html"))?;
|
||||
let (mut page_vm, _resource_source, mut wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(&loader, document_url);
|
||||
page_vm.vm_mut().eval(&format!(
|
||||
r#"
|
||||
globalThis.__staleLinkedImportEvents = [];
|
||||
const retired = document.createElement("link");
|
||||
retired.rel = "stylesheet";
|
||||
retired.href = "{base_url}/stale-root.css";
|
||||
retired.addEventListener("load", () => __staleLinkedImportEvents.push("load"));
|
||||
retired.addEventListener("error", () => __staleLinkedImportEvents.push("error"));
|
||||
document.head.append(retired);
|
||||
"queued"
|
||||
"#,
|
||||
))?;
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.prime_document_lifecycle_processing_and_record_stylesheet_network_results();
|
||||
wait_for_stylesheet_source(&mut wake_rx, RendererOwnerWakeSource::NetworkingTask).await;
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::StylesheetCompletion,
|
||||
&loader,
|
||||
)
|
||||
.await?,
|
||||
"the root stylesheet terminal should start its linked import graph"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm
|
||||
.vm()
|
||||
.document_runtime
|
||||
.linked_stylesheet_import_graph_count_for_test(),
|
||||
1,
|
||||
"Document A should retain its in-flight linked import graph"
|
||||
);
|
||||
let _ = page_vm.vm_mut().take_network_output();
|
||||
|
||||
wait_for_stylesheet_source(&mut wake_rx, RendererOwnerWakeSource::NetworkingTask).await;
|
||||
let stale_task = page_vm
|
||||
.take_stylesheet_networking_body_task_for_test()
|
||||
.expect("the delayed linked import terminal should remain selectable");
|
||||
|
||||
page_vm.vm_mut().eval(
|
||||
"document.open(); document.write('<!doctype html><p>replacement</p>'); document.close();",
|
||||
)?;
|
||||
assert_eq!(
|
||||
page_vm
|
||||
.vm()
|
||||
.document_runtime
|
||||
.linked_stylesheet_import_graph_count_for_test(),
|
||||
0,
|
||||
"document.open() should install an empty stylesheet graph state for Document B"
|
||||
);
|
||||
assert!(!page_vm.vm().document_runtime.has_pending_style_loads());
|
||||
|
||||
let outcome = page_vm.apply_selected_page_stylesheet_networking_turn(stale_task)?;
|
||||
assert_eq!(
|
||||
outcome.action.target_effect,
|
||||
PageStylesheetNetworkingTargetEffect::RecordedForStaleOwner
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm
|
||||
.vm()
|
||||
.document_runtime
|
||||
.linked_stylesheet_import_graph_count_for_test(),
|
||||
0,
|
||||
"Document A's terminal must not create an entry in Document B's graph map"
|
||||
);
|
||||
assert!(!page_vm.vm().document_runtime.has_pending_style_loads());
|
||||
assert!(
|
||||
!page_vm.has_ready_dom_manipulation_task_for_test(),
|
||||
"the stale graph must not publish a link event into Document B"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval("__staleLinkedImportEvents.join(',')")?,
|
||||
""
|
||||
);
|
||||
|
||||
let (records, _, _) = split_network_output_items(page_vm.vm_mut().take_network_output());
|
||||
assert_eq!(records.len(), 1, "the child fetch remains observable");
|
||||
let stale_child_url = Url::parse(&format!("{base_url}/stale-child.css"))?;
|
||||
assert_eq!(
|
||||
records[0].url(),
|
||||
&stale_child_url,
|
||||
"historical network accounting should retain the stale import request"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm.pending_subresource_request_count(),
|
||||
0,
|
||||
"a stale import terminal must not derive work in Document B"
|
||||
);
|
||||
page_vm
|
||||
.finish_selected_page_networking_task(
|
||||
PageNetworkingTurnAction::StylesheetCompletion(outcome.action),
|
||||
&loader,
|
||||
)
|
||||
.await?;
|
||||
server.await.expect("stale linked import fixture server");
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("stale linked import completion test should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn connected_style_body_settles_its_lease_but_leaves_reactions_for_task_completion() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::ScriptVm;
|
||||
use crate::{
|
||||
document_runtime::StylesheetImportCompletionAuthority,
|
||||
page_task_queue::{
|
||||
PageConnectedStyleEventTargetEffect, PageConnectedStyleLoadDelayEffect,
|
||||
PageStylesheetNetworkingTargetEffect, RendererPageConnectedStyleEventTask,
|
||||
@@ -32,6 +33,11 @@ impl ScriptVm {
|
||||
) -> PageStylesheetNetworkingTargetEffect {
|
||||
let owner = task.owner();
|
||||
let current = self.stylesheet_task_owner_is_current(root_document, owner);
|
||||
let import_authority = if current {
|
||||
StylesheetImportCompletionAuthority::CurrentDocument
|
||||
} else {
|
||||
StylesheetImportCompletionAuthority::HistoricalOnly
|
||||
};
|
||||
match task.into_completion() {
|
||||
RendererPageStylesheetCompletion::Blocking(completion) => self
|
||||
.document_runtime
|
||||
@@ -41,10 +47,10 @@ impl ScriptVm {
|
||||
.apply_connected_style_load_completion(completion),
|
||||
RendererPageStylesheetCompletion::LinkedImport(completion) => self
|
||||
.document_runtime
|
||||
.apply_linked_stylesheet_import_graph_completion(completion),
|
||||
.apply_linked_stylesheet_import_graph_completion(completion, import_authority),
|
||||
RendererPageStylesheetCompletion::LiveImport(completion) => self
|
||||
.document_runtime
|
||||
.apply_live_stylesheet_import_load_completion(completion, current),
|
||||
.apply_live_stylesheet_import_load_completion(completion, import_authority),
|
||||
}
|
||||
self.record_ready_stylesheet_network_results();
|
||||
if current {
|
||||
|
||||
@@ -21,13 +21,19 @@ pub(crate) struct LiveStylesheetImportLoadCompletion {
|
||||
pub(in crate::document_runtime) network_results: Vec<ConnectedLoadNetworkResult>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum StylesheetImportCompletionAuthority {
|
||||
CurrentDocument,
|
||||
HistoricalOnly,
|
||||
}
|
||||
|
||||
impl DocumentRuntime {
|
||||
pub(crate) fn apply_live_stylesheet_import_load_completion(
|
||||
&mut self,
|
||||
mut completion: LiveStylesheetImportLoadCompletion,
|
||||
install_authority: bool,
|
||||
authority: StylesheetImportCompletionAuthority,
|
||||
) {
|
||||
if !install_authority {
|
||||
if authority == StylesheetImportCompletionAuthority::HistoricalOnly {
|
||||
for result in &mut completion.network_results {
|
||||
result.import_roots.clear();
|
||||
result.source_owners.clear();
|
||||
|
||||
@@ -451,12 +451,23 @@ impl DocumentRuntime {
|
||||
pub(crate) fn apply_linked_stylesheet_import_graph_completion(
|
||||
&mut self,
|
||||
completion: LinkedStylesheetImportGraphCompletion,
|
||||
authority: StylesheetImportCompletionAuthority,
|
||||
) {
|
||||
let LinkedStylesheetImportGraphCompletion {
|
||||
fetch,
|
||||
graph,
|
||||
mut network_results,
|
||||
} = completion;
|
||||
if authority == StylesheetImportCompletionAuthority::HistoricalOnly {
|
||||
for result in &mut network_results {
|
||||
result.import_roots.clear();
|
||||
result.source_owners.clear();
|
||||
}
|
||||
self.stylesheet_lifecycle
|
||||
.ready_connected_load_network_results
|
||||
.extend(network_results);
|
||||
return;
|
||||
}
|
||||
let roots = self
|
||||
.linked_stylesheet_import_roots(&fetch)
|
||||
.into_iter()
|
||||
@@ -479,6 +490,14 @@ impl DocumentRuntime {
|
||||
.extend(network_results);
|
||||
self.note_stylesheet_import_graph_completion(&fetch, graph);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn linked_stylesheet_import_graph_count_for_test(&self) -> usize {
|
||||
self.stylesheet_lifecycle
|
||||
.linked_stylesheet_import_graphs
|
||||
.entries
|
||||
.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -29,7 +29,10 @@ mod test_driver;
|
||||
pub(crate) use attributes::attribute_reprocesses_connected_stylesheet;
|
||||
pub(crate) use blocking::OwnerlessStylesheetAdmissionError;
|
||||
pub(super) use client_index::StylesheetLinkClientIndex;
|
||||
pub(crate) use completion::{ConnectedLoadCompletion, LiveStylesheetImportLoadCompletion};
|
||||
pub(crate) use completion::{
|
||||
ConnectedLoadCompletion, LiveStylesheetImportLoadCompletion,
|
||||
StylesheetImportCompletionAuthority,
|
||||
};
|
||||
pub(crate) use connected::{ConnectedStyleLoadPrimeResult, PreparedConnectedStyleLoad};
|
||||
pub(crate) use import_graph::fetch_complete_stylesheet_import_graph;
|
||||
pub(super) use import_graph::{ConnectedStyleImportReadiness, connected_style_import_readiness};
|
||||
|
||||
@@ -18,10 +18,9 @@ impl DocumentRuntime {
|
||||
let Some(task) = task else {
|
||||
return false;
|
||||
};
|
||||
// Consume stale tasks too. Production records their network result but
|
||||
// the exact owner checks prevent them from installing stylesheet state
|
||||
// into a replacement Document; leaving the old task at the test source
|
||||
// head would model neither behavior.
|
||||
// This standalone fixture has no Page-level exact-owner arbiter, so
|
||||
// completions are applied with current-owner authority. PageVm tests
|
||||
// cover stale task handling through the production owner boundary.
|
||||
match task.into_completion() {
|
||||
crate::page_task_queue::RendererPageStylesheetCompletion::Blocking(completion) => {
|
||||
self.apply_blocking_stylesheet_completion(completion);
|
||||
@@ -30,10 +29,16 @@ impl DocumentRuntime {
|
||||
self.apply_connected_style_load_completion(completion);
|
||||
}
|
||||
crate::page_task_queue::RendererPageStylesheetCompletion::LinkedImport(completion) => {
|
||||
self.apply_linked_stylesheet_import_graph_completion(completion);
|
||||
self.apply_linked_stylesheet_import_graph_completion(
|
||||
completion,
|
||||
StylesheetImportCompletionAuthority::CurrentDocument,
|
||||
);
|
||||
}
|
||||
crate::page_task_queue::RendererPageStylesheetCompletion::LiveImport(completion) => {
|
||||
self.apply_live_stylesheet_import_load_completion(completion, true);
|
||||
self.apply_live_stylesheet_import_load_completion(
|
||||
completion,
|
||||
StylesheetImportCompletionAuthority::CurrentDocument,
|
||||
);
|
||||
}
|
||||
}
|
||||
true
|
||||
|
||||
Reference in New Issue
Block a user