mirror of
https://github.com/lexmount/moli.git
synced 2026-09-28 16:01:39 +00:00
fix(cssom): publish parser style imports before scripts
This commit is contained in:
@@ -18370,7 +18370,7 @@ document.body.setAttribute("data-range", [
|
||||
let outcome = driver
|
||||
.advance_parser_step(
|
||||
&mut page_vm,
|
||||
"<!doctype html><html><head><style>@import url('/style.css');</style><script>window.afterStyle = true;</script></head></html>",
|
||||
"<!doctype html><html><head><style id='blocking-style'>@import url('/style.css');</style><script>window.afterStyle = true;</script></head></html>",
|
||||
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"
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PreparedConnectedStyleLoad> {
|
||||
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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user