diff --git a/moli-dom/src/native/element/control_state.rs b/moli-dom/src/native/element/control_state.rs index 1503936dd..c9351df2a 100644 --- a/moli-dom/src/native/element/control_state.rs +++ b/moli-dom/src/native/element/control_state.rs @@ -148,6 +148,7 @@ pub struct ElementControlState { indeterminate: bool, script: Option>, link_created_by_parser: bool, + style_parser_children_pending: bool, link_explicitly_enabled: bool, selection_start: Option, selection_end: Option, @@ -654,6 +655,18 @@ impl ElementControlState { true } + pub fn style_parser_children_pending(&self) -> bool { + self.style_parser_children_pending + } + + pub fn note_parser_created_style(&mut self) { + self.style_parser_children_pending = true; + } + + pub fn finish_parsing_style_children(&mut self) -> bool { + std::mem::take(&mut self.style_parser_children_pending) + } + pub fn finish_parsing_link_children(&mut self) -> bool { if !self.link_created_by_parser { return false; diff --git a/moli-dom/src/native/element/mod.rs b/moli-dom/src/native/element/mod.rs index 314ddf0bc..47a9b3e17 100644 --- a/moli-dom/src/native/element/mod.rs +++ b/moli-dom/src/native/element/mod.rs @@ -155,6 +155,15 @@ impl Element { { rare_data.control_state_mut().note_parser_created_link(); } + if creation_source == ElementCreationSource::Parser + && local_name == "style" + && matches!( + namespace.as_str(), + "http://www.w3.org/1999/xhtml" | "http://www.w3.org/2000/svg" + ) + { + rare_data.control_state_mut().note_parser_created_style(); + } Self { local_name: LocalName::from(local_name), namespace: Namespace::from(namespace), @@ -866,6 +875,18 @@ impl Element { self.control_state_mut().finish_parsing_link_children() } + /// A parser-owned style block is processed only after its children finish. + pub fn style_parser_children_pending(&self) -> bool { + self.control_state().style_parser_children_pending() + } + + pub fn finish_parsing_style_children(&mut self) -> bool { + if !self.is_inline_style_element() { + return false; + } + self.control_state_mut().finish_parsing_style_children() + } + pub fn parser_associated_form_owner(&self) -> Option { self.rare_data.parser_associated_form_owner() } diff --git a/moli-dom/src/native/host/mutation/state.rs b/moli-dom/src/native/host/mutation/state.rs index 64b03d35e..bde4714ec 100644 --- a/moli-dom/src/native/host/mutation/state.rs +++ b/moli-dom/src/native/host/mutation/state.rs @@ -1054,6 +1054,31 @@ impl DomHost { did_change } + pub fn finish_parsing_style_children(&mut self, handle: DomHandle) -> bool { + let did_change = self + .node_mut(handle) + .and_then(|node| node.data_mut().as_element_mut()) + .is_some_and(|element| element.finish_parsing_style_children()); + if did_change { + self.record_mutation(MutationScope::LocalState); + } + did_change + } + + pub fn finish_parsing_style_children_effects( + &mut self, + handle: DomHandle, + ) -> DomMutationEffects { + let mut effects = DomMutationEffects::default(); + if self.finish_parsing_style_children(handle) { + effects.mark_stylesheet_owner_contents_change( + handle, + self.dom.stylesheet_candidate_tree_scope_for_node(handle), + ); + } + effects + } + pub fn set_cryptographic_nonce(&mut self, handle: DomHandle, nonce: Option) -> bool { let did_change = { let Some(element) = self diff --git a/moli-parser/src/live_target.rs b/moli-parser/src/live_target.rs index d6527b1cb..d12ffed73 100644 --- a/moli-parser/src/live_target.rs +++ b/moli-parser/src/live_target.rs @@ -521,6 +521,9 @@ pub enum ParserDomMutation { parent: NativeNodeId, child: NativeNodeId, }, + FinishParsingStyleChildren { + node: NativeNodeId, + }, } impl ParserDomMutation { @@ -533,6 +536,9 @@ impl ParserDomMutation { reference_child, } => host.insert_before_effects(parent, child, reference_child), Self::RemoveChild { parent, child } => host.remove_child_effects(parent, child), + Self::FinishParsingStyleChildren { node } => { + host.finish_parsing_style_children_effects(node) + } } } @@ -554,6 +560,7 @@ impl ParserDomMutation { Self::RemoveChild { parent, child } => { host.remove_child_without_mutation_effects(parent, child) } + Self::FinishParsingStyleChildren { node } => host.finish_parsing_style_children(node), } } } @@ -2647,7 +2654,18 @@ impl ParserStreamHtmlTreeSinkTarget { .iter() .rposition(|candidate| candidate.node_id == node_id) { - self.open_parser_elements.remove(index); + let element = self.open_parser_elements.remove(index); + if element.name.local.as_ref() == "style" + && matches!( + element.name.ns.as_ref(), + "http://www.w3.org/1999/xhtml" | "http://www.w3.org/2000/svg" + ) + { + self.record_parser_dom_mutation(ParserDomMutation::FinishParsingStyleChildren { + node: node_id, + }) + .consume(); + } } if self.read_is_html_element_named(node_id, "link") { let _ = self.capture_parser_blocking_stylesheet(node_id); @@ -3582,10 +3600,10 @@ impl ParserStreamHtmlTreeSinkTarget { if self.read_is_html_element_named(node_id, "template") { self.open_template_element_depth = self.open_template_element_depth.saturating_sub(1); } + self.note_parser_element_popped(node_id); if self.read_is_html_element_named(node_id, "style") { self.note_blocking_stylesheet_pause_if_needed(node_id); } - self.note_parser_element_popped(node_id); } pub(super) fn restore_parser_stream_dom_host(&mut self, dom_host: DomHost) { diff --git a/moli-parser/src/stream.rs b/moli-parser/src/stream.rs index e661c6f36..2109d96f5 100644 --- a/moli-parser/src/stream.rs +++ b/moli-parser/src/stream.rs @@ -2493,6 +2493,47 @@ mod tests { ); } + #[test] + fn parser_style_children_finish_at_close_and_eof() { + for (opening, closing) in [ + (""), + (""), + (""), + ); + let root = document.document_element_node_id().unwrap(); + assert!( + !document + .node(root) + .and_then(Node::as_element) + .unwrap() + .style_parser_children_pending() + ); + } + } + #[test] fn xml_parser_records_unclosed_and_mismatched_elements() { for (index, source) in [ diff --git a/moli-renderer-v8/src/document_runtime/dom_facade.rs b/moli-renderer-v8/src/document_runtime/dom_facade.rs index a51e0bc23..eac7841de 100644 --- a/moli-renderer-v8/src/document_runtime/dom_facade.rs +++ b/moli-renderer-v8/src/document_runtime/dom_facade.rs @@ -93,9 +93,22 @@ impl DocumentRuntime { scope: &mut v8::PinScope<'_, '_>, host_ptr: *mut JsContextHost, effects: DomMutationEffects, + ) { + self.apply_parser_stream_mutation_effects_to_live_dom_host_with_options( + scope, host_ptr, effects, RuntimeMutationOptions::parser_tree_sink(), + ); + } + + pub(super) fn apply_parser_stream_mutation_effects_to_live_dom_host_with_options( + &mut self, + scope: &mut v8::PinScope<'_, '_>, + host_ptr: *mut JsContextHost, + effects: DomMutationEffects, + options: RuntimeMutationOptions, ) { self.assert_active_parser_document_incarnation(); - apply_parser_mutation_effects(scope, host_ptr, self, &effects); + let mut owner = RuntimeParserMutationEffects { runtime: self, options }; + apply_parser_mutation_effects(scope, host_ptr, &mut owner, &effects); } pub(crate) fn parser_runtime_dom_node_exists(&mut self, node_id: DomHandle) -> bool { @@ -818,20 +831,25 @@ impl DocumentRuntime { } } -impl ParserMutationEffectsOwner for DocumentRuntime { +struct RuntimeParserMutationEffects<'a> { + runtime: &'a mut DocumentRuntime, + options: RuntimeMutationOptions, +} + +impl ParserMutationEffectsOwner for RuntimeParserMutationEffects<'_> { type Prepared = RuntimeMutationApplyResult; fn prepare_parser_mutation_effects(&mut self, effects: &DomMutationEffects) -> Self::Prepared { prepare_runtime_mutation_effects( - self.dom_host(), - self.document.url(), + self.runtime.dom_host(), + self.runtime.document.url(), effects, - RuntimeMutationOptions::parser_tree_sink(), + self.options, ) } fn ensure_parser_reaction_queue(&mut self, host_ptr: *mut JsContextHost) { - self.ensure_parser_custom_element_reaction_queue(host_ptr); + self.runtime.ensure_parser_custom_element_reaction_queue(host_ptr); } fn finish_parser_mutation_effects( @@ -840,7 +858,7 @@ impl ParserMutationEffectsOwner for DocumentRuntime { host_ptr: *mut JsContextHost, prepared: Self::Prepared, ) { - let _ = finish_runtime_mutation_effects(self, scope, host_ptr, prepared); + let _ = finish_runtime_mutation_effects(self.runtime, scope, host_ptr, prepared); } } diff --git a/moli-renderer-v8/src/document_runtime/mutation_commands/tree/parser.rs b/moli-renderer-v8/src/document_runtime/mutation_commands/tree/parser.rs index a874174b2..103abfe8d 100644 --- a/moli-renderer-v8/src/document_runtime/mutation_commands/tree/parser.rs +++ b/moli-renderer-v8/src/document_runtime/mutation_commands/tree/parser.rs @@ -54,6 +54,17 @@ impl DocumentRuntime { source_profile: TreeMutationSourceProfile, ) { match mutation { + ParserDomMutation::FinishParsingStyleChildren { node } => { + let effects = self + .dom_host_mut() + .finish_parsing_style_children_effects(node); + self.apply_parser_stream_mutation_effects_to_live_dom_host_with_options( + scope, + host_ptr, + effects, + mutation_options, + ); + } ParserDomMutation::AppendChild { parent, child } => self .apply_parser_append_child_to_live_dom_host( scope, diff --git a/moli-renderer-v8/src/document_runtime/security_policy.rs b/moli-renderer-v8/src/document_runtime/security_policy.rs index fbe82d764..ebafb3f00 100644 --- a/moli-renderer-v8/src/document_runtime/security_policy.rs +++ b/moli-renderer-v8/src/document_runtime/security_policy.rs @@ -389,6 +389,9 @@ impl DocumentRuntime { let Some(element) = self.dom_host.node(owner).and_then(Node::as_element) else { return; }; + if element.style_parser_children_pending() { + return; + } let source = self.dom_host.text_content(owner).unwrap_or_default(); let nonce = element.cryptographic_nonce().map(str::to_owned); let is_declarative_css_module = diff --git a/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs b/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs index ea5a2dec4..ff82a6f53 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs @@ -1272,7 +1272,7 @@ impl JsContextHost { let element = dom_host.node(owner).and_then(Node::as_element)?; if !dom_host.is_connected(owner) || dom_host.get_attribute(owner, "disabled").is_some() - || !crate::style_engine::stylesheet_owner_type_is_supported(element) + || !crate::style_engine::stylesheet_owner_can_have_sheet(element) { return None; } diff --git a/moli-renderer-v8/src/native_bridge/document/css_state/font_faces.rs b/moli-renderer-v8/src/native_bridge/document/css_state/font_faces.rs index 6f738c904..e9009eefe 100644 --- a/moli-renderer-v8/src/native_bridge/document/css_state/font_faces.rs +++ b/moli-renderer-v8/src/native_bridge/document/css_state/font_faces.rs @@ -41,7 +41,7 @@ pub(super) fn owner_font_face_projection( let dom_host = host.dom_host(); let element = dom_host.node(owner)?.as_element()?; if !dom_host.is_connected(owner) - || !crate::style_engine::stylesheet_owner_type_is_supported(element) + || !crate::style_engine::stylesheet_owner_can_have_sheet(element) { return None; } diff --git a/moli-renderer-v8/src/native_bridge/document/css_state/style_sheets.rs b/moli-renderer-v8/src/native_bridge/document/css_state/style_sheets.rs index cb91bb87b..2b3313a47 100644 --- a/moli-renderer-v8/src/native_bridge/document/css_state/style_sheets.rs +++ b/moli-renderer-v8/src/native_bridge/document/css_state/style_sheets.rs @@ -27,7 +27,7 @@ pub(super) fn sync_document_style_sheets<'s>( let Some(element) = node.as_element() else { continue; }; - if !crate::style_engine::stylesheet_owner_type_is_supported(element) { + if !crate::style_engine::stylesheet_owner_can_have_sheet(element) { continue; } let wrapper = if document_is_connected { diff --git a/moli-renderer-v8/src/native_bridge/element/shadow_dom/root_accessors.rs b/moli-renderer-v8/src/native_bridge/element/shadow_dom/root_accessors.rs index 841663eff..98d70ca25 100644 --- a/moli-renderer-v8/src/native_bridge/element/shadow_dom/root_accessors.rs +++ b/moli-renderer-v8/src/native_bridge/element/shadow_dom/root_accessors.rs @@ -99,7 +99,7 @@ fn sync_shadow_root_style_sheets<'s>( continue; }; if !runtime.dom_host().is_connected(sheet_handle) - || !crate::style_engine::stylesheet_owner_type_is_supported(element) + || !crate::style_engine::stylesheet_owner_can_have_sheet(element) { continue; } @@ -132,7 +132,7 @@ fn sync_detached_shadow_root_style_sheets<'s>( else { continue; }; - if !crate::style_engine::stylesheet_owner_type_is_supported(element) { + if !crate::style_engine::stylesheet_owner_can_have_sheet(element) { continue; } if element.local_name().eq_ignore_ascii_case("link") diff --git a/moli-renderer-v8/src/native_bridge/element/styles/declaration/style_world.rs b/moli-renderer-v8/src/native_bridge/element/styles/declaration/style_world.rs index 0311354a3..f064da22b 100644 --- a/moli-renderer-v8/src/native_bridge/element/styles/declaration/style_world.rs +++ b/moli-renderer-v8/src/native_bridge/element/styles/declaration/style_world.rs @@ -7,7 +7,7 @@ use crate::{ FullStyleWorldSnapshot, IncrementalStyleWorldUpdate, PreparedStyleWorldUpdate, StyleSourceId, StyleTreeScopeVersions, StyleViewport, StyleWorldEnvironment, StyleWorldUpdatePlan, StyloStyleEnvironment, StyloStylesheetSource, - link_rel_qualifies_as_stylesheet, stylesheet_owner_type_is_supported, + link_rel_qualifies_as_stylesheet, stylesheet_owner_can_have_sheet, }, stylesheet_blocking::link_rel_includes_token, }; @@ -344,7 +344,7 @@ pub(super) fn active_stylesheet_handles( element.is_html_element("link") && link_stylesheet_is_enabled(runtime, *handle); (style || link) && (include_detached || stylesheet_is_active_in_scope(runtime, root, *handle)) - && stylesheet_owner_type_is_supported(element) + && stylesheet_owner_can_have_sheet(element) }) .collect::>(); let preferred_title = handles diff --git a/moli-renderer-v8/src/native_bridge/element/stylesheets/sheet.rs b/moli-renderer-v8/src/native_bridge/element/stylesheets/sheet.rs index c95f9ed3f..0fc4fa80d 100644 --- a/moli-renderer-v8/src/native_bridge/element/stylesheets/sheet.rs +++ b/moli-renderer-v8/src/native_bridge/element/stylesheets/sheet.rs @@ -133,7 +133,7 @@ pub(crate) fn style_sheet_for_element<'s>( let is_css_link = element.is_html_element("link") && link_rel_qualifies_as_stylesheet(element.attribute("rel"), element.attribute("title")); if (!is_style && !is_css_link) - || !crate::style_engine::stylesheet_owner_type_is_supported(element) + || !crate::style_engine::stylesheet_owner_can_have_sheet(element) || (is_css_link && runtime .dom_host() diff --git a/moli-renderer-v8/src/runtime/page_css.rs b/moli-renderer-v8/src/runtime/page_css.rs index c003e19d3..98c54e489 100644 --- a/moli-renderer-v8/src/runtime/page_css.rs +++ b/moli-renderer-v8/src/runtime/page_css.rs @@ -119,7 +119,7 @@ impl PageVm { let dom_host = self.vm().document_runtime.dom_host(); let node = dom_host.node(handle)?; let element = node.as_element()?; - if !crate::style_engine::stylesheet_owner_type_is_supported(element) { + if !crate::style_engine::stylesheet_owner_can_have_sheet(element) { return None; } diff --git a/moli-renderer-v8/src/runtime/phase_one/mod.rs b/moli-renderer-v8/src/runtime/phase_one/mod.rs index b93084b56..9cf31303a 100644 --- a/moli-renderer-v8/src/runtime/phase_one/mod.rs +++ b/moli-renderer-v8/src/runtime/phase_one/mod.rs @@ -6080,6 +6080,59 @@ globalThis.__outerDocumentWriteScriptContinued = true; })); } + #[test] + fn parser_style_csp_checks_complete_contents_once() { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("runtime"); + runtime.block_on(tokio::task::LocalSet::new().run_until(async { + for (markup, policy, report_only, expected_violations, expected_sheet) in [ + ("", "style-src 'none'", false, 1, false), + ("", "style-src 'none'", true, 1, true), + ("", "style-src 'sha256-rB6kiow2O3eFUeTNyyLeK3wV0+l7vNB90J1aqllKvjg='", false, 0, true), + ("", "style-src 'none'", false, 1, false), + ("\")", "style-src 'none'", false, 1, false), + ("", "style-src 'none'", false, 2, false), + ] { + let env = default_test_page_vm_env_config_with(|env| { + let policies = if report_only { + &mut env.document_policy_container.response_content_security_report_only_policies + } else { + &mut env.document_policy_container.response_content_security_policies + }; + *policies = vec![policy.to_owned()]; + }); + let html = Box::leak(format!("{markup}").into_boxed_str()); + let mut page_vm = parse_phase_one_html_into_page_vm_for_test_with_env_and_finish(html, env, true).await; + let result = page_vm.evaluate_expression("Boolean(document.getElementById('sheet').sheet)").expect("stylesheet probe"); + assert_eq!(result.get("value").and_then(serde_json::Value::as_bool), Some(expected_sheet), "{markup}, {policy}, report-only={report_only}"); + + let local_executor = page_vm.local_executor.clone(); + let page_vm_ptr: *mut PageVm = &mut page_vm; + super::access::run_named_owner_local_task( + local_executor, + "parser style CSP task channel closed", + async move { + let page_vm = unsafe { &mut *page_vm_ptr }; + page_vm.page_task_queue.accept_ready_parse_time_wakes(); + page_vm.vm_mut().drain_pre_domcontentloaded_content_security_policy_violation_tasks_for_test(); + while let Some(task) = page_vm.page_task_queue.parse_time_pop_front() { + let work = PostParsePageOwnedWork::lifecycle_work( + crate::page_task_queue::PostParseLifecycleWork::from_parse_time_page_task(task), + ); + execute_page_owned_work_turn_on_local_task(page_vm, work).await?; + } + Ok(()) + }, + ).await.expect("parser style tasks"); + let result = page_vm.evaluate_expression("__parserStyleViolations").expect("violation event count"); + assert_eq!(result.get("value").and_then(serde_json::Value::as_u64), Some(expected_violations), "{markup}, {policy}, report-only={report_only}"); + } + })); + } + #[test] fn buffered_script_preload_cache_reuses_ready_late_parser_blocking_preload() { let runtime = tokio::runtime::Builder::new_current_thread() diff --git a/moli-renderer-v8/src/style_engine/mod.rs b/moli-renderer-v8/src/style_engine/mod.rs index c292b109e..d5bcfab23 100644 --- a/moli-renderer-v8/src/style_engine/mod.rs +++ b/moli-renderer-v8/src/style_engine/mod.rs @@ -131,9 +131,7 @@ use source_id::StyleSourceKind; use source_id::{StyleInvalidationSourceTarget, StyleScopeId}; pub(crate) use source_lifecycle::OwnedStyleSourceDocumentContext; use source_lifecycle::StyleSourceDocumentContext; -pub(crate) use source_owner::{ - link_rel_qualifies_as_stylesheet, stylesheet_owner_type_is_supported, -}; +pub(crate) use source_owner::{link_rel_qualifies_as_stylesheet, stylesheet_owner_can_have_sheet}; pub(crate) use stylesheet_resources::{StylesheetResourceGeneration, StylesheetResourceSnapshot}; #[cfg(test)] use stylesheet_resources::{ diff --git a/moli-renderer-v8/src/style_engine/source_owner.rs b/moli-renderer-v8/src/style_engine/source_owner.rs index 67e4ef006..6a84d613c 100644 --- a/moli-renderer-v8/src/style_engine/source_owner.rs +++ b/moli-renderer-v8/src/style_engine/source_owner.rs @@ -18,7 +18,10 @@ pub(crate) fn link_rel_qualifies_as_stylesheet(rel: Option<&str>, title: Option< && (!includes_token("alternate") || title.is_some_and(|title| !title.is_empty())) } -pub(crate) fn stylesheet_owner_type_is_supported(element: &Element) -> bool { +pub(crate) fn stylesheet_owner_can_have_sheet(element: &Element) -> bool { + if element.style_parser_children_pending() { + return false; + } let type_attribute = element.attribute("type"); if element.is_html_element("style") || (element.namespace() == "http://www.w3.org/2000/svg" && element.local_name() == "style") @@ -75,7 +78,7 @@ fn style_element_is_stylesheet_source_enabled( }; element.is_inline_style_element() && host.get_attribute(handle, "disabled").is_none() - && stylesheet_owner_type_is_supported(element) + && stylesheet_owner_can_have_sheet(element) && stylesheet_source_media_matches(media_text, emulated_media, viewport) } diff --git a/moli-renderer-v8/src/stylesheet_runtime/connected.rs b/moli-renderer-v8/src/stylesheet_runtime/connected.rs index 2721507c3..5ed5388fe 100644 --- a/moli-renderer-v8/src/stylesheet_runtime/connected.rs +++ b/moli-renderer-v8/src/stylesheet_runtime/connected.rs @@ -1444,6 +1444,14 @@ impl DocumentRuntime { let mut canceled_load_event_bindings = Vec::new(); let mut prepared = Vec::new(); for (owner, should_queue) in transitions { + if self + .dom_host + .node(owner) + .and_then(Node::as_element) + .is_some_and(|element| element.style_parser_children_pending()) + { + continue; + } canceled_load_event_bindings.extend(self.invalidate_style_related_state(owner)); if !should_queue { continue; @@ -1875,10 +1883,13 @@ fn connected_modulepreload_has_non_matching_media( fn connected_style_owner_kind( element: &crate::dom::native::Element, ) -> Option { + if element.style_parser_children_pending() { + return None; + } if super::is_inline_style_element(element) { return if is_declarative_css_module_style_element(element) { Some(ConnectedStyleOwnerKind::DeclarativeCssModule) - } else if crate::style_engine::stylesheet_owner_type_is_supported(element) { + } else if crate::style_engine::stylesheet_owner_can_have_sheet(element) { Some(ConnectedStyleOwnerKind::ClassicStyle) } else { None diff --git a/moli-stylesheet-blocking/src/discovery.rs b/moli-stylesheet-blocking/src/discovery.rs index 5a32e4af5..496eacb22 100644 --- a/moli-stylesheet-blocking/src/discovery.rs +++ b/moli-stylesheet-blocking/src/discovery.rs @@ -101,6 +101,7 @@ pub struct StylesheetElementRead { is_html_element: bool, local_name: String, parser_blocking_eligible: bool, + style_parser_children_pending: bool, rel: Option, href: Option, as_attr: Option, @@ -121,6 +122,7 @@ impl StylesheetElementRead { Some(Self { is_html_element: element.namespace() == "http://www.w3.org/1999/xhtml", local_name: element.local_name().to_owned(), + style_parser_children_pending: element.style_parser_children_pending(), parser_blocking_eligible: if element.is_html_element("link") { element.link_created_by_parser() } else { @@ -154,6 +156,7 @@ impl StylesheetElementRead { is_html_element: true, local_name: "link".to_owned(), parser_blocking_eligible: true, + style_parser_children_pending: false, rel: Some("stylesheet".to_owned()), href: Some(href.to_owned()), as_attr: None, @@ -690,7 +693,10 @@ fn parser_created_style_import_urls( ) -> Option> { let native_node_id = NativeNodeId::new(node_id.index()); let element = document.stylesheet_element(native_node_id)?; - if !element.is_html_element("style") || !element.parser_blocking_eligible { + if !element.is_html_element("style") + || !element.parser_blocking_eligible + || element.style_parser_children_pending + { return None; } if !media_blocks_scripts(element.media.as_deref()) { @@ -793,6 +799,16 @@ mod tests { assert!(host.append_child(style, text)); assert!(host.append_child(host.document_handle(), style)); + assert!( + document_owned_blocking_stylesheet_candidate_for_node( + &host, + moli_dom::NodeId::new(style.index()), + ) + .is_none(), + "an open parser style must not start imports" + ); + assert!(host.finish_parsing_style_children(style)); + let candidate = document_owned_blocking_stylesheet_candidate_for_node( &host, moli_dom::NodeId::new(style.index()),