diff --git a/moli-renderer-v8/src/runtime/phase_one/mod.rs b/moli-renderer-v8/src/runtime/phase_one/mod.rs index a133ca13f8..88eea8855f 100644 --- a/moli-renderer-v8/src/runtime/phase_one/mod.rs +++ b/moli-renderer-v8/src/runtime/phase_one/mod.rs @@ -18370,7 +18370,7 @@ document.body.setAttribute("data-range", [ let outcome = driver .advance_parser_step( &mut page_vm, - "", + "", None, ) .await @@ -18380,6 +18380,20 @@ document.body.setAttribute("data-range", [ matches!(outcome, ParserStepAdvanceOutcome::BlockedOnStylesheet(_)), "parser-created style import should gate parser-blocking script on live PageVm" ); + let style = page_vm + .vm() + .document_runtime + .dom_host() + .element_handle_by_id("blocking-style") + .expect("parser-created style owner"); + assert_eq!( + page_vm + .vm() + .document_runtime + .pending_style_import_binding_for_test(style), + Some((1, true)), + "the parser-discovered import must bind its live stylesheet root before the script gate is released" + ); })); } diff --git a/moli-renderer-v8/src/script_vm/main_document_owner.rs b/moli-renderer-v8/src/script_vm/main_document_owner.rs index 66e69ceb6a..247923f5e1 100644 --- a/moli-renderer-v8/src/script_vm/main_document_owner.rs +++ b/moli-renderer-v8/src/script_vm/main_document_owner.rs @@ -63,6 +63,22 @@ impl ScriptVm { .document_runtime .note_discovered_document_owned_blocking_stylesheet_inputs(inputs.iter()); self.settle_stylesheet_link_clients(completed_stylesheet_clients); + if inputs.iter().any(|input| { + matches!( + input.signature(), + crate::stylesheet_blocking::DocumentBlockingStylesheetSignature::ParserCreatedStyleImport { .. } + ) + }) { + // The import graph owns the parser gate, while the connected + // style operation owns its CSSOM installation. Bind both views to + // the same live root before an already-fast graph can release the + // parser-blocking script. + self.sync_live_document_style_sources(); + let prepared = self + .document_runtime + .prepare_parser_discovered_style_import_loads(inputs); + self.commit_and_apply_connected_style_loads(prepared); + } inputs.len() } diff --git a/moli-renderer-v8/src/stylesheet_runtime/connected.rs b/moli-renderer-v8/src/stylesheet_runtime/connected.rs index e4a58dea69..e1ec05afba 100644 --- a/moli-renderer-v8/src/stylesheet_runtime/connected.rs +++ b/moli-renderer-v8/src/stylesheet_runtime/connected.rs @@ -9,9 +9,9 @@ use crate::module_runtime::{ use crate::planning::{ScriptFetchMetadata, module_script_credentials_mode}; use crate::service_worker_runtime::ServiceWorkerRequestDestination; use crate::stylesheet_blocking::{ - StylesheetFetchOptions, connected_preload_like_link_url, - document_owned_blocking_stylesheet_candidate_for_node, link_rel_includes_token, - preload_like_link_loads_stylesheet, stylesheet_link_disposition, + DocumentOwnedBlockingStylesheetDiscoveryInput, StylesheetFetchOptions, + connected_preload_like_link_url, document_owned_blocking_stylesheet_candidate_for_node, + link_rel_includes_token, preload_like_link_loads_stylesheet, stylesheet_link_disposition, stylesheet_preload_link_request, }; use crate::types::{AsyncSubresourceFetchResponseFilter, SubresourceResourceType}; @@ -238,6 +238,24 @@ impl DocumentRuntime { prepared } + pub(crate) fn prepare_parser_discovered_style_import_loads( + &mut self, + inputs: &[DocumentOwnedBlockingStylesheetDiscoveryInput], + ) -> Vec { + inputs + .iter() + .filter(|input| { + matches!( + input.signature(), + DocumentBlockingStylesheetSignature::ParserCreatedStyleImport { .. } + ) + }) + .flat_map(|input| { + self.prepare_connected_style_loads(DomHandle::new(input.node_id().index()), true) + }) + .collect() + } + #[cfg(test)] pub(crate) fn queue_initial_connected_style_loads(&mut self) { let prepared = self.prepare_initial_connected_style_loads(); @@ -580,6 +598,21 @@ impl DocumentRuntime { self.connected_style_load_is_queued(handle) } + #[cfg(test)] + pub(crate) fn pending_style_import_binding_for_test( + &self, + handle: DomHandle, + ) -> Option<(usize, bool)> { + let operation = self + .stylesheet_lifecycle + .owner_states + .pending_operation(handle)?; + let ConnectedLoadParameters::StyleImports { roots, .. } = &operation.parameters else { + return None; + }; + Some((roots.len(), operation.blocking_operation.is_some())) + } + pub(super) fn push_ready_connected_style_load(&mut self, ready: ReadyConnectedStyleLoad) { let producer = self .stylesheet_lifecycle @@ -671,11 +704,17 @@ impl DocumentRuntime { if !node.flags().parser_created() || !node.is_html_element_named("style") { return false; } - document_owned_blocking_stylesheet_candidate_for_node( + let Some(candidate) = document_owned_blocking_stylesheet_candidate_for_node( &self.dom_host, NodeId::new(handle.index()), - ) - .is_some() + ) else { + return false; + }; + let signature = DocumentBlockingStylesheetSignature::from_candidate(&candidate); + self.stylesheet_lifecycle + .fetches + .blocking_operation(NodeId::new(handle.index()), &signature) + .is_none() } pub(crate) fn has_pending_style_loads(&self) -> bool { diff --git a/moli-stylesheet-blocking/src/discovery.rs b/moli-stylesheet-blocking/src/discovery.rs index d58be82217..a4490e6105 100644 --- a/moli-stylesheet-blocking/src/discovery.rs +++ b/moli-stylesheet-blocking/src/discovery.rs @@ -691,7 +691,7 @@ fn parser_created_style_import_urls( if !element.is_html_element("style") || !element.parser_blocking_eligible { return None; } - if element.disabled || !media_blocks_scripts(element.media.as_deref()) { + if !media_blocks_scripts(element.media.as_deref()) { return None; } let css_text = document.text_content(native_node_id)?; @@ -777,6 +777,37 @@ mod tests { ); } + #[test] + fn style_disabled_content_attribute_does_not_suppress_parser_import_blocking() { + let document_url = url::Url::parse("https://example.com/path/page.html").unwrap(); + let mut host = DomHost::from_dom(NativeDom::new_html(document_url)); + let style = host.create_parser_element_without_attributes( + "style".to_owned(), + "http://www.w3.org/1999/xhtml".to_owned(), + None, + ); + let text = host.create_text_node("@import url('theme.css');"); + assert!(host.set_attribute(style, "disabled", "")); + assert!(host.append_child(style, text)); + assert!(host.append_child(host.document_handle(), style)); + + let candidate = document_owned_blocking_stylesheet_candidate_for_node( + &host, + moli_dom::NodeId::new(style.index()), + ) + .expect("the unsupported style content attribute must not disable import blocking"); + let super::DocumentOwnedBlockingStylesheetCandidate::ParserCreatedStyleImport { + urls, .. + } = candidate + else { + panic!("expected a parser-created style import candidate"); + }; + assert_eq!( + urls, + [url::Url::parse("https://example.com/path/theme.css").unwrap()] + ); + } + #[test] fn hidden_nonce_remains_part_of_stylesheet_request_identity() { let document_url = url::Url::parse("https://example.com/page.html").unwrap();