fix(stylesheets): preserve parser event ordering

This commit is contained in:
ldm0
2026-09-27 07:50:23 +08:00
parent 3048d419b1
commit 3bfad58bae
5 changed files with 122 additions and 7 deletions
@@ -3,17 +3,18 @@
<head>
<meta charset="utf-8">
<script>
window.runtimeInsertedHrefMutationStart = Date.now();
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "/assets/blocking_stylesheet_slow.css?stale=1";
link.href =
"/assets/runtime_inserted_stylesheet_href_mutation_stale.css";
document.head.appendChild(link);
link.href = "/assets/chrome_stylesheetlist_1.css?fresh=1";
link.href =
"/assets/runtime_inserted_stylesheet_href_mutation_fresh.css";
</script>
<script
src="/assets/runtime_inserted_stylesheet_href_mutation_probe.js"
></script>
<script>
window.runtimeInsertedHrefMutationParserNotBlocked =
Date.now() - window.runtimeInsertedHrefMutationStart < 50;
window.runtimeInsertedHrefMutationSawLate =
!!document.getElementById("late");
</script>
+10
View File
@@ -830,6 +830,10 @@ async fn runtime_inserted_stylesheet_load_can_trigger_location_replace() -> Resu
#[tokio::test(flavor = "multi_thread")]
async fn runtime_inserted_stylesheet_href_mutation_uses_fresh_fetch_for_parser_blocking_progress()
-> Result<()> {
// The stale stylesheet response is gated until the following parser script
// requests its probe. The probe reports whether it arrived before the
// stale-response fallback fired, so this checks parser progress without a
// wall-clock threshold in page script.
let server = FixtureServer::spawn().await?;
let browser = Browser::new(AppConfig::default())?;
@@ -845,6 +849,12 @@ async fn runtime_inserted_stylesheet_href_mutation_uses_fresh_fetch_for_parser_b
diagnostic_global(&page, "runtimeInsertedHrefMutationSawLate"),
Some(&JsValueSnapshot::Bool(false))
);
assert!(
page.subresource_network_records().iter().any(|record| {
record.url().path() == "/assets/runtime_inserted_stylesheet_href_mutation_fresh.css"
}),
"the retargeted stylesheet must start the fresh fetch"
);
server.shutdown().await;
Ok(())
+87 -1
View File
@@ -1,7 +1,7 @@
use super::*;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
static NET_UPSTREAM_XHR_404_THEN_200_REQUESTS: AtomicUsize = AtomicUsize::new(0);
@@ -21,6 +21,25 @@ static RUNTIME_OWNED_IN_ORDER_ERROR_AFTER_DCL_GATES: OnceLock<
static RUNTIME_OWNED_IN_ORDER_LOAD_AFTER_DCL_GATES: OnceLock<
Mutex<HashMap<String, Arc<tokio::sync::Notify>>>,
> = OnceLock::new();
static RUNTIME_INSERTED_STYLESHEET_HREF_MUTATION_GATES: OnceLock<
Mutex<HashMap<String, Arc<RuntimeInsertedStylesheetHrefMutationGate>>>,
> = OnceLock::new();
struct RuntimeInsertedStylesheetHrefMutationGate {
release_stale_response: tokio::sync::Notify,
stale_request_started: AtomicBool,
stale_response_timed_out: AtomicBool,
}
impl RuntimeInsertedStylesheetHrefMutationGate {
fn new() -> Self {
Self {
release_stale_response: tokio::sync::Notify::new(),
stale_request_started: AtomicBool::new(false),
stale_response_timed_out: AtomicBool::new(false),
}
}
}
struct ConcurrentSharedStateRequestGuard;
@@ -169,6 +188,34 @@ pub(crate) fn notify_runtime_owned_in_order_load_after_dcl_gate(host_key: &str)
runtime_owned_in_order_load_after_dcl_gate(host_key).notify_one();
}
fn runtime_inserted_stylesheet_href_mutation_gate(
host_key: &str,
) -> Arc<RuntimeInsertedStylesheetHrefMutationGate> {
let gates =
RUNTIME_INSERTED_STYLESHEET_HREF_MUTATION_GATES.get_or_init(|| Mutex::new(HashMap::new()));
let mut gates = gates.lock();
gates
.entry(host_key.to_owned())
.or_insert_with(|| Arc::new(RuntimeInsertedStylesheetHrefMutationGate::new()))
.clone()
}
fn remove_runtime_inserted_stylesheet_href_mutation_gate(host_key: &str) {
let Some(gates) = RUNTIME_INSERTED_STYLESHEET_HREF_MUTATION_GATES.get() else {
return;
};
gates.lock().remove(host_key);
}
pub(crate) fn clear_runtime_inserted_stylesheet_href_mutation_gate(host_key: &str) {
let Some(gates) = RUNTIME_INSERTED_STYLESHEET_HREF_MUTATION_GATES.get() else {
return;
};
if let Some(gate) = gates.lock().remove(host_key) {
gate.release_stale_response.notify_one();
}
}
pub(super) async fn static_page() -> Html<&'static str> {
Html(STATIC_HTML)
}
@@ -5526,6 +5573,45 @@ pub(super) async fn asset_dynamic_blocking_stylesheet_gated_css(
css_response(BLOCKING_STYLESHEET_SLOW_CSS)
}
pub(super) async fn asset_runtime_inserted_stylesheet_href_mutation_stale_css(
headers: HeaderMap,
) -> Response {
let host_key = request_host_key(&headers).unwrap_or_default();
let gate = runtime_inserted_stylesheet_href_mutation_gate(&host_key);
gate.stale_request_started.store(true, Ordering::SeqCst);
let released_by_probe = tokio::time::timeout(
Duration::from_secs(2),
gate.release_stale_response.notified(),
)
.await
.is_ok();
if released_by_probe {
remove_runtime_inserted_stylesheet_href_mutation_gate(&host_key);
} else {
gate.stale_response_timed_out.store(true, Ordering::SeqCst);
}
css_response(BLOCKING_STYLESHEET_SLOW_CSS)
}
pub(super) async fn asset_runtime_inserted_stylesheet_href_mutation_fresh_css() -> Response {
css_response(CHROME_STYLESHEETLIST_1_CSS)
}
pub(super) async fn asset_runtime_inserted_stylesheet_href_mutation_probe_script(
headers: HeaderMap,
) -> Response {
let host_key = request_host_key(&headers).unwrap_or_default();
let gate = runtime_inserted_stylesheet_href_mutation_gate(&host_key);
let parser_not_blocked = !gate.stale_response_timed_out.load(Ordering::SeqCst);
gate.release_stale_response.notify_one();
if gate.stale_request_started.load(Ordering::SeqCst) {
remove_runtime_inserted_stylesheet_href_mutation_gate(&host_key);
}
javascript_string_response(format!(
"window.runtimeInsertedHrefMutationParserNotBlocked = {parser_not_blocked};"
))
}
pub(super) async fn asset_runtime_connected_preload_very_slow_css() -> Response {
sleep(Duration::from_millis(250)).await;
css_response(BLOCKING_STYLESHEET_SLOW_CSS)
+6
View File
@@ -52,6 +52,9 @@ impl FixtureServer {
}
pub async fn shutdown(mut self) {
crate::routes_core::clear_runtime_inserted_stylesheet_href_mutation_gate(
&self.addr.to_string(),
);
if let Some(shutdown_tx) = self.shutdown_tx.take() {
let _ = shutdown_tx.send(());
}
@@ -64,6 +67,9 @@ impl FixtureServer {
impl Drop for FixtureServer {
fn drop(&mut self) {
crate::routes_core::clear_runtime_inserted_stylesheet_href_mutation_gate(
&self.addr.to_string(),
);
if let Some(shutdown_tx) = self.shutdown_tx.take() {
let _ = shutdown_tx.send(());
}
+12
View File
@@ -2393,6 +2393,18 @@ pub(super) fn build_router() -> Router {
"/assets/blocking_stylesheet_slow.css",
get(asset_blocking_stylesheet_slow_css),
)
.route(
"/assets/runtime_inserted_stylesheet_href_mutation_stale.css",
get(asset_runtime_inserted_stylesheet_href_mutation_stale_css),
)
.route(
"/assets/runtime_inserted_stylesheet_href_mutation_fresh.css",
get(asset_runtime_inserted_stylesheet_href_mutation_fresh_css),
)
.route(
"/assets/runtime_inserted_stylesheet_href_mutation_probe.js",
get(asset_runtime_inserted_stylesheet_href_mutation_probe_script),
)
.route(
"/assets/runtime_connected_modulepreload_slow.mjs",
get(asset_runtime_connected_modulepreload_slow_module),