mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 05:55:13 +00:00
LibWeb: Implement more of the "script-blocking style sheet" mechanism
The basic idea is that style sheets can block script execution under some circumstances. With this commit, we now handle the simplest cases where a parser-inserted link element gets to download its style sheet before script execution continues. This improves performance on Speedometer 3 where JavaScript APIs that depend on layout results (like Element.scrollIntoView()) would get called too early (before the relevant CSS was downloaded), and so we'd perform premature layout work. This work then had to be redone after downloading the CSS anyway, wasting time. Note that our Text/input/link-re-enable-crash.html test had to be tweaked after these changes, since it relied on the old, incorrect, behavior where scripts would run before downloading CSS.
This commit is contained in:
parent
8e37cd2f71
commit
0c0650e60a
Notes:
github-actions[bot]
2025-04-20 12:55:15 +00:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/0c0650e60ac Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4407
9 changed files with 101 additions and 18 deletions
|
@ -583,6 +583,7 @@ void Document::visit_edges(Cell::Visitor& visitor)
|
|||
}
|
||||
|
||||
visitor.visit(m_adopted_style_sheets);
|
||||
visitor.visit(m_script_blocking_style_sheet_set);
|
||||
|
||||
for (auto& shadow_root : m_shadow_roots)
|
||||
visitor.visit(shadow_root);
|
||||
|
@ -3242,11 +3243,18 @@ String Document::dump_dom_tree_as_json() const
|
|||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#has-no-style-sheet-that-is-blocking-scripts
|
||||
bool Document::has_no_style_sheet_that_is_blocking_scripts() const
|
||||
{
|
||||
// A Document has no style sheet that is blocking scripts if it does not have a style sheet that is blocking scripts.
|
||||
return !has_a_style_sheet_that_is_blocking_scripts();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#has-a-style-sheet-that-is-blocking-scripts
|
||||
bool Document::has_a_style_sheet_that_is_blocking_scripts() const
|
||||
{
|
||||
// FIXME: 1. If document's script-blocking style sheet set is not empty, then return true.
|
||||
if (m_script_blocking_style_sheet_counter > 0)
|
||||
// 1. If document's script-blocking style sheet set is not empty, then return true.
|
||||
if (!m_script_blocking_style_sheet_set.is_empty())
|
||||
return true;
|
||||
|
||||
// 2. If document's node navigable is null, then return false.
|
||||
|
@ -3256,8 +3264,8 @@ bool Document::has_a_style_sheet_that_is_blocking_scripts() const
|
|||
// 3. Let containerDocument be document's node navigable's container document.
|
||||
auto container_document = navigable()->container_document();
|
||||
|
||||
// FIXME: 4. If containerDocument is non-null and containerDocument's script-blocking style sheet set is not empty, then return true.
|
||||
if (container_document && container_document->m_script_blocking_style_sheet_counter > 0)
|
||||
// 4. If containerDocument is non-null and containerDocument's script-blocking style sheet set is not empty, then return true.
|
||||
if (container_document && !container_document->m_script_blocking_style_sheet_set.is_empty())
|
||||
return true;
|
||||
|
||||
// 5. Return false
|
||||
|
|
|
@ -499,7 +499,8 @@ public:
|
|||
|
||||
String dump_dom_tree_as_json() const;
|
||||
|
||||
bool has_a_style_sheet_that_is_blocking_scripts() const;
|
||||
[[nodiscard]] bool has_a_style_sheet_that_is_blocking_scripts() const;
|
||||
[[nodiscard]] bool has_no_style_sheet_that_is_blocking_scripts() const;
|
||||
|
||||
bool is_fully_active() const;
|
||||
bool is_active() const;
|
||||
|
@ -897,6 +898,9 @@ public:
|
|||
|
||||
ElementByIdMap& element_by_id() const;
|
||||
|
||||
auto& script_blocking_style_sheet_set() { return m_script_blocking_style_sheet_set; }
|
||||
auto const& script_blocking_style_sheet_set() const { return m_script_blocking_style_sheet_set; }
|
||||
|
||||
protected:
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
@ -1030,8 +1034,8 @@ private:
|
|||
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter
|
||||
u32 m_throw_on_dynamic_markup_insertion_counter { 0 };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#script-blocking-style-sheet-counter
|
||||
u32 m_script_blocking_style_sheet_counter { 0 };
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#script-blocking-style-sheet-set
|
||||
HashTable<GC::Ref<DOM::Element>> m_script_blocking_style_sheet_set;
|
||||
|
||||
GC::Ptr<HTML::History> m_history;
|
||||
|
||||
|
|
|
@ -471,6 +471,8 @@ public:
|
|||
void release_pointer_capture(WebIDL::Long pointer_id);
|
||||
bool has_pointer_capture(WebIDL::Long pointer_id);
|
||||
|
||||
virtual bool contributes_a_script_blocking_style_sheet() const { return false; }
|
||||
|
||||
protected:
|
||||
Element(Document&, DOM::QualifiedName);
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
|
|
@ -87,6 +87,12 @@ void StyleElementUtils::update_a_style_block(DOM::Element& style_element)
|
|||
{},
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
// 7. If element contributes a script-blocking style sheet, append element to its node document's script-blocking style sheet set.
|
||||
if (style_element.contributes_a_script_blocking_style_sheet())
|
||||
style_element.document().script_blocking_style_sheet_set().set(style_element);
|
||||
|
||||
// FIXME: 8. If element's media attribute's value matches the environment and element is potentially render-blocking, then block rendering on element.
|
||||
}
|
||||
|
||||
void StyleElementUtils::visit_edges(JS::Cell::Visitor& visitor)
|
||||
|
|
|
@ -509,9 +509,14 @@ void HTMLLinkElement::process_stylesheet_resource(bool success, Fetch::Infrastru
|
|||
dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error));
|
||||
}
|
||||
|
||||
// FIXME: 6. If el contributes a script-blocking style sheet, then:
|
||||
// FIXME: 1. Assert: el's node document's script-blocking style sheet counter is greater than 0.
|
||||
// FIXME: 2. Decrement el's node document's script-blocking style sheet counter by 1.
|
||||
// 6. If el contributes a script-blocking style sheet, then:
|
||||
if (contributes_a_script_blocking_style_sheet()) {
|
||||
// 1. Assert: el's node document's script-blocking style sheet set contains el.
|
||||
VERIFY(document().script_blocking_style_sheet_set().contains(*this));
|
||||
|
||||
// 2. Remove el from its node document's script-blocking style sheet set.
|
||||
document().script_blocking_style_sheet_set().remove(*this);
|
||||
}
|
||||
|
||||
// 7. Unblock rendering on el.
|
||||
unblock_rendering();
|
||||
|
@ -541,7 +546,10 @@ bool HTMLLinkElement::stylesheet_linked_resource_fetch_setup_steps(Fetch::Infras
|
|||
// 1. If el's disabled attribute is set, then return false.
|
||||
if (has_attribute(AttributeNames::disabled))
|
||||
return false;
|
||||
// FIXME: 2. If el contributes a script-blocking style sheet, increment el's node document's script-blocking style sheet counter by 1.
|
||||
|
||||
// 2. If el contributes a script-blocking style sheet, append el to its node document's script-blocking style sheet set.
|
||||
if (contributes_a_script_blocking_style_sheet())
|
||||
document().script_blocking_style_sheet_set().set(*this);
|
||||
|
||||
// 3. If el's media attribute's value matches the environment and el is potentially render-blocking, then block rendering on el.
|
||||
// FIXME: Check media attribute value.
|
||||
|
@ -674,4 +682,34 @@ void HTMLLinkElement::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(m_sizes);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#contributes-a-script-blocking-style-sheet
|
||||
bool HTMLLinkElement::contributes_a_script_blocking_style_sheet() const
|
||||
{
|
||||
// An element el in the context of a Document of an HTML parser or XML parser
|
||||
// contributes a script-blocking style sheet if all of the following are true:
|
||||
|
||||
// el was created by that Document's parser.
|
||||
if (m_parser_document != &document())
|
||||
return false;
|
||||
|
||||
// FIXME: el is either a style element or a link element that was an external resource link that contributes to the styling processing model when the el was created by the parser.
|
||||
|
||||
// FIXME: el's media attribute's value matches the environment.
|
||||
|
||||
// FIXME: el's style sheet was enabled when the element was created by the parser.
|
||||
if (has_attribute(AttributeNames::disabled))
|
||||
return false;
|
||||
|
||||
// FIXME: The last time the event loop reached step 1, el's root was that Document.
|
||||
|
||||
// The user agent hasn't given up on loading that particular style sheet yet.
|
||||
// A user agent may give up on loading a style sheet at any time.
|
||||
if (m_fetch_controller && m_fetch_controller->state() == Fetch::Infrastructure::FetchController::State::Terminated)
|
||||
return false;
|
||||
if (m_fetch_controller && m_fetch_controller->state() == Fetch::Infrastructure::FetchController::State::Aborted)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ public:
|
|||
|
||||
GC::Ptr<CSS::CSSStyleSheet> sheet() const;
|
||||
|
||||
virtual bool contributes_a_script_blocking_style_sheet() const final;
|
||||
|
||||
private:
|
||||
HTMLLinkElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
|
|
@ -103,4 +103,27 @@ CSS::CSSStyleSheet const* HTMLStyleElement::sheet() const
|
|||
return m_style_element_utils.sheet();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#contributes-a-script-blocking-style-sheet
|
||||
bool HTMLStyleElement::contributes_a_script_blocking_style_sheet() const
|
||||
{
|
||||
// An element el in the context of a Document of an HTML parser or XML parser
|
||||
// contributes a script-blocking style sheet if all of the following are true:
|
||||
|
||||
// FIXME: el was created by that Document's parser.
|
||||
|
||||
// el is either a style element or a link element that was an external resource link that contributes to the styling processing model when the el was created by the parser.
|
||||
// NOTE: This is a style element, so all good!
|
||||
|
||||
// FIXME: el's media attribute's value matches the environment.
|
||||
|
||||
// FIXME: el's style sheet was enabled when the element was created by the parser.
|
||||
|
||||
// FIXME: The last time the event loop reached step 1, el's root was that Document.
|
||||
|
||||
// FIXME: The user agent hasn't given up on loading that particular style sheet yet.
|
||||
// A user agent may give up on loading a style sheet at any time.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ public:
|
|||
CSS::CSSStyleSheet* sheet();
|
||||
CSS::CSSStyleSheet const* sheet() const;
|
||||
|
||||
virtual bool contributes_a_script_blocking_style_sheet() const final;
|
||||
|
||||
private:
|
||||
HTMLStyleElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="include.js"></script>
|
||||
<link rel="stylesheet" href="valid.css">
|
||||
<script>
|
||||
var scriptOnloadWasInvoked = false;
|
||||
</script>
|
||||
<link id="link" rel="stylesheet" href="valid.css" onload="link.disabled = true; link.disabled = false;">
|
||||
<script>
|
||||
test(() => {
|
||||
const link = document.querySelector("link");
|
||||
link.onload = () => {
|
||||
link.disabled = true;
|
||||
link.disabled = false;
|
||||
println("PASS (didn't crash)");
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Add table
Reference in a new issue