mirror of
https://github.com/lexmount/moli.git
synced 2026-09-24 00:01:27 +00:00
fix(style): process parser styles after their children finish
This commit is contained in:
@@ -148,6 +148,7 @@ pub struct ElementControlState {
|
||||
indeterminate: bool,
|
||||
script: Option<Box<ScriptElementState>>,
|
||||
link_created_by_parser: bool,
|
||||
style_parser_children_pending: bool,
|
||||
link_explicitly_enabled: bool,
|
||||
selection_start: Option<u32>,
|
||||
selection_end: Option<u32>,
|
||||
@@ -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;
|
||||
|
||||
@@ -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<NativeNodeId> {
|
||||
self.rare_data.parser_associated_form_owner()
|
||||
}
|
||||
|
||||
@@ -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<String>) -> bool {
|
||||
let did_change = {
|
||||
let Some(element) = self
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -2493,6 +2493,47 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_style_children_finish_at_close_and_eof() {
|
||||
for (opening, closing) in [
|
||||
("<html><head><style id='sheet'>", "</style>"),
|
||||
("<html><body><svg><style id='sheet'>", "</style></svg>"),
|
||||
("<html><head><style id='sheet'>", ""),
|
||||
] {
|
||||
let mut stream = DocumentStream::new_scripting_enabled_parser_stream_for_testing(
|
||||
Url::parse("https://example.test/page").unwrap(),
|
||||
);
|
||||
stream.pump_parser_step(&format!("{opening}p {{ col"));
|
||||
let document = stream.snapshot_parser_stream_document().into_document();
|
||||
let style =
|
||||
document.elements_by_tag_name(document.document_node_id(), "style", false)[0];
|
||||
assert!(
|
||||
document
|
||||
.node(style)
|
||||
.and_then(Node::as_element)
|
||||
.unwrap()
|
||||
.style_parser_children_pending()
|
||||
);
|
||||
stream.pump_parser_step(&format!("or: blue; }}{closing}"));
|
||||
let document = if closing.is_empty() {
|
||||
stream.finish()
|
||||
} else {
|
||||
stream.snapshot_parser_stream_document().into_document()
|
||||
};
|
||||
assert!(
|
||||
!document
|
||||
.node(style)
|
||||
.and_then(Node::as_element)
|
||||
.unwrap()
|
||||
.style_parser_children_pending()
|
||||
);
|
||||
assert_eq!(
|
||||
document.text_content(style).as_deref(),
|
||||
Some("p { color: blue; }")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_stream_reports_split_connected_meta_csp_once_and_ignores_template_contents() {
|
||||
let stream = DocumentStream::new_scripting_enabled_parser_stream_for_testing(
|
||||
|
||||
@@ -667,6 +667,13 @@ impl<'host> XmlTreeSinkBase for XmlDocumentSink<'host> {
|
||||
self.target.borrow().document_handle()
|
||||
}
|
||||
|
||||
fn pop(&self, node: &Self::Handle) {
|
||||
self.target
|
||||
.borrow_mut()
|
||||
.dom_host
|
||||
.finish_parsing_style_children(node.node_id);
|
||||
}
|
||||
|
||||
fn elem_name<'a>(&'a self, target: &'a Self::Handle) -> Self::ElemName<'a> {
|
||||
target
|
||||
.element_name
|
||||
@@ -774,6 +781,24 @@ mod tests {
|
||||
use moli_dom::native::{DomHost, NativeDom, NativeNodeId, Node, NodeType};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn xml_style_children_finish_at_close() {
|
||||
for namespace in ["http://www.w3.org/1999/xhtml", "http://www.w3.org/2000/svg"] {
|
||||
let document = XmlParser.parse(
|
||||
Url::parse("https://example.test/style.xml").unwrap(),
|
||||
format!("<style xmlns='{namespace}'>p {{ color: blue; }}</style>"),
|
||||
);
|
||||
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 [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
let preferred_title = handles
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 id='sheet'>p {color:blue;}</style>", "style-src 'none'", false, 1, false),
|
||||
("<style id='sheet'>p {color:blue;}</style>", "style-src 'none'", true, 1, true),
|
||||
("<style id='sheet'>p {color:blue;}</style>", "style-src 'sha256-rB6kiow2O3eFUeTNyyLeK3wV0+l7vNB90J1aqllKvjg='", false, 0, true),
|
||||
("<style id='sheet'></style>", "style-src 'none'", false, 1, false),
|
||||
("<style id='sheet'>p {color:blue;}", "style-src 'none'", false, 1, false),
|
||||
("<script>document.write(\"<style id='sheet'>p {color:blue;}</style>\")</script>", "style-src 'none'", false, 1, false),
|
||||
("<script>const s = document.createElement('style'); s.id = 'sheet'; document.head.append(s); s.textContent = 'p {color:blue;}';</script>", "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!("<!doctype html><html><head><script>globalThis.__parserStyleViolations = 0; document.addEventListener('securitypolicyviolation', () => __parserStyleViolations++);</script>{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()
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ConnectedStyleOwnerKind> {
|
||||
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
|
||||
|
||||
@@ -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<String>,
|
||||
href: Option<String>,
|
||||
as_attr: Option<String>,
|
||||
@@ -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<Vec<Url>> {
|
||||
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()),
|
||||
|
||||
Reference in New Issue
Block a user