script: Make stylesheets loaded via <link> elements block the rendering (#39536)

Stylesheets loaded via the `<link>` element should block the rendering
of the page according to the HTML specification [1]. This change makes
it so that they do this and, in addition, we do not take reftest
screenshots until all no element is blocking the rendering.

This change does not add support for the `blocking` attribute of
`<link>`, but that can be added in a follow change. In addition to
fixing a few tests, this change likely makes other tests no longer
intermittent. We will need to watch CI runs after this lands in order to
verify that though.

Testing: This change fixes at least two WPT tests.
Fixes: #26424.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-09-27 15:32:26 +02:00 committed by GitHub
parent 6aa82309c3
commit 55d094a871
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 46 additions and 7 deletions

View file

@ -350,6 +350,9 @@ pub(crate) struct Document {
pending_parsing_blocking_script: DomRefCell<Option<PendingScript>>, pending_parsing_blocking_script: DomRefCell<Option<PendingScript>>,
/// Number of stylesheets that block executing the next parser-inserted script /// Number of stylesheets that block executing the next parser-inserted script
script_blocking_stylesheets_count: Cell<u32>, script_blocking_stylesheets_count: Cell<u32>,
/// Number of elements that block the rendering of the page.
/// <https://html.spec.whatwg.org/multipage/#implicitly-potentially-render-blocking>
render_blocking_element_count: Cell<u32>,
/// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing> /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing>
deferred_scripts: PendingInOrderScriptVec, deferred_scripts: PendingInOrderScriptVec,
/// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible> /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible>
@ -1728,6 +1731,21 @@ impl Document {
count_cell.set(count_cell.get() - 1); count_cell.set(count_cell.get() - 1);
} }
pub(crate) fn render_blocking_element_count(&self) -> u32 {
self.render_blocking_element_count.get()
}
pub(crate) fn increment_render_blocking_element_count(&self) {
let count_cell = &self.render_blocking_element_count;
count_cell.set(count_cell.get() + 1);
}
pub(crate) fn decrement_render_blocking_element_count(&self) {
let count_cell = &self.render_blocking_element_count;
assert!(count_cell.get() > 0);
count_cell.set(count_cell.get() - 1);
}
pub(crate) fn invalidate_stylesheets(&self) { pub(crate) fn invalidate_stylesheets(&self) {
self.stylesheets.borrow_mut().force_dirty(OriginSet::all()); self.stylesheets.borrow_mut().force_dirty(OriginSet::all());
@ -2705,6 +2723,10 @@ impl Document {
// //
// Returns the set of reflow phases run as a [`ReflowPhasesRun`]. // Returns the set of reflow phases run as a [`ReflowPhasesRun`].
pub(crate) fn update_the_rendering(&self) -> ReflowPhasesRun { pub(crate) fn update_the_rendering(&self) -> ReflowPhasesRun {
if self.render_blocking_element_count() > 0 {
return Default::default();
}
if self.has_pending_animated_image_update.get() { if self.has_pending_animated_image_update.get() {
self.image_animation_manager self.image_animation_manager
.borrow() .borrow()
@ -3389,7 +3411,8 @@ impl Document {
has_focus: Cell::new(has_focus), has_focus: Cell::new(has_focus),
current_script: Default::default(), current_script: Default::default(),
pending_parsing_blocking_script: Default::default(), pending_parsing_blocking_script: Default::default(),
script_blocking_stylesheets_count: Cell::new(0u32), script_blocking_stylesheets_count: Default::default(),
render_blocking_element_count: Default::default(),
deferred_scripts: Default::default(), deferred_scripts: Default::default(),
asap_in_order_scripts_list: Default::default(), asap_in_order_scripts_list: Default::default(),
asap_scripts_set: Default::default(), asap_scripts_set: Default::default(),

View file

@ -2347,6 +2347,10 @@ impl Window {
return; return;
} }
if document.render_blocking_element_count() > 0 {
return;
}
// Checks if the html element has reftest-wait attribute present. // Checks if the html element has reftest-wait attribute present.
// See http://testthewebforward.org/docs/reftests.html // See http://testthewebforward.org/docs/reftests.html
// and https://web-platform-tests.org/writing-tests/crashtest.html // and https://web-platform-tests.org/writing-tests/crashtest.html

View file

@ -277,6 +277,15 @@ impl FetchResponseListener for StylesheetContext {
document.decrement_script_blocking_stylesheet_count(); document.decrement_script_blocking_stylesheet_count();
} }
// From <https://html.spec.whatwg.org/multipage/#link-type-stylesheet>:
// > A link element of this type is implicitly potentially render-blocking if the element
// > was created by its node document's parser.
if matches!(self.source, StylesheetContextSource::LinkElement { .. }) &&
owner.parser_inserted()
{
document.decrement_render_blocking_element_count();
}
document.finish_load(LoadType::Stylesheet(self.url.clone()), CanGc::note()); document.finish_load(LoadType::Stylesheet(self.url.clone()), CanGc::note());
if let Some(any_failed) = owner.load_finished(successful) { if let Some(any_failed) = owner.load_finished(successful) {
@ -377,6 +386,15 @@ impl ElementStylesheetLoader<'_> {
document.increment_script_blocking_stylesheet_count(); document.increment_script_blocking_stylesheet_count();
} }
// From <https://html.spec.whatwg.org/multipage/#link-type-stylesheet>:
// > A link element of this type is implicitly potentially render-blocking if the element
// > was created by its node document's parser.
if matches!(context.source, StylesheetContextSource::LinkElement { .. }) &&
owner.parser_inserted()
{
document.increment_render_blocking_element_count();
}
// https://html.spec.whatwg.org/multipage/#default-fetch-and-process-the-linked-resource // https://html.spec.whatwg.org/multipage/#default-fetch-and-process-the-linked-resource
let global = self.element.global(); let global = self.element.global();
let request = create_a_potential_cors_request( let request = create_a_potential_cors_request(

View file

@ -1,3 +0,0 @@
[parser-inserted-stylesheet-link.html]
[Rendering is blocked before render-blocking resources are loaded]
expected: FAIL

View file

@ -1,3 +0,0 @@
[remove-attr-stylesheet-link-keeps-blocking.html]
[Rendering is blocked before render-blocking resources are loaded]
expected: FAIL