mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb: Invalidate stylesheet owners when disabled state changes
Toggling CSSStyleSheet::disabled previously cleared the cached media match bits and reloaded fonts, but never informed the owning documents or shadow roots that style resolution was now stale. Worse, the IDL binding for the disabled attribute dispatches through a non-virtual setter on StyleSheet, so any override on CSSStyleSheet was bypassed entirely. Make set_disabled() virtual so the CSSStyleSheet override actually runs, snapshot the pre-mutation shadow-root stylesheet effects before flipping the flag, and hand them to invalidate_owners() so a disable that strips the last host-reaching rule still tears down host-side style correctly.
This commit is contained in:
committed by
Andreas Kling
parent
a0dc0c61f4
commit
cfa75e6eb4
Notes:
github-actions[bot]
2026-04-23 14:49:35 +00:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/cfa75e6eb43 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/9049
@@ -384,6 +384,7 @@ void CSSStyleSheet::set_disabled(bool disabled)
|
||||
if (this->disabled() == disabled)
|
||||
return;
|
||||
|
||||
auto previous_sheet_effects = determine_shadow_root_stylesheet_effects(*this);
|
||||
auto document = owning_document();
|
||||
// When a stylesheet is disabled we stop evaluating its media queries, so both the cached top-level match bit
|
||||
// and the MediaList's internal state can go stale across viewport changes. Clear the cache for both
|
||||
@@ -401,6 +402,8 @@ void CSSStyleSheet::set_disabled(bool disabled)
|
||||
} else if (document) {
|
||||
document->font_computer().unload_fonts_from_sheet(*this);
|
||||
}
|
||||
|
||||
invalidate_owners(DOM::StyleInvalidationReason::StyleSheetDisabledStateChange, &previous_sheet_effects);
|
||||
}
|
||||
|
||||
void CSSStyleSheet::for_each_owning_style_scope(Function<void(StyleScope&)> const& callback) const
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
void remove_owning_document_or_shadow_root(DOM::Node& document_or_shadow_root);
|
||||
void invalidate_owners(DOM::StyleInvalidationReason, ShadowRootStylesheetEffects const* previous_sheet_effects = nullptr);
|
||||
GC::Ptr<DOM::Document> owning_document() const;
|
||||
void set_disabled(bool);
|
||||
virtual void set_disabled(bool) override;
|
||||
void for_each_owning_style_scope(Function<void(StyleScope&)> const&) const;
|
||||
|
||||
Optional<FlyString> default_namespace() const;
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
void set_origin_clean(bool origin_clean) { m_origin_clean = origin_clean; }
|
||||
|
||||
bool disabled() const { return m_disabled; }
|
||||
void set_disabled(bool disabled) { m_disabled = disabled; }
|
||||
virtual void set_disabled(bool disabled) { m_disabled = disabled; }
|
||||
|
||||
CSSStyleSheet* parent_style_sheet() { return m_parent_style_sheet; }
|
||||
void set_parent_css_style_sheet(CSSStyleSheet*);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
before disable: rgb(255, 0, 0)
|
||||
after disable: rgb(0, 0, 0)
|
||||
after re-enable: rgb(255, 0, 0)
|
||||
@@ -2,8 +2,8 @@ Harness status: OK
|
||||
|
||||
Found 32 tests
|
||||
|
||||
27 Pass
|
||||
5 Fail
|
||||
29 Pass
|
||||
3 Fail
|
||||
Pass document.adoptedStyleSheets should initially have length 0.
|
||||
Pass new CSSStyleSheet produces empty CSSStyleSheet
|
||||
Pass title can be set in the CSSStyleSheet constructor
|
||||
@@ -21,8 +21,8 @@ Pass Adding non-constructed stylesheet to AdoptedStyleSheets is not allowed when
|
||||
Pass Adding non-constructed stylesheet to AdoptedStyleSheets is not allowed when the owner document of the stylesheet and the AdoptedStyleSheets are in different document trees
|
||||
Pass CSSStyleSheet.replaceSync replaces stylesheet text synchronously
|
||||
Pass CSSStyleSheet.replaceSync correctly updates the style of its adopters synchronously
|
||||
Fail Adopted sheets are ordered after non-adopted sheets in the shadow root
|
||||
Fail Adopted sheets are ordered after non-adopted sheets in the document
|
||||
Pass Adopted sheets are ordered after non-adopted sheets in the shadow root
|
||||
Pass Adopted sheets are ordered after non-adopted sheets in the document
|
||||
Pass Inserting an @import rule through insertRule on a constructed stylesheet throws an exception
|
||||
Fail CSSStyleSheet.replaceSync should not trigger any loads from @import rules
|
||||
Pass CSSStyleSheet.replace allows, but ignores, import rule inside
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<div id="target" class="foo">target</div>
|
||||
<div>bystander 1</div>
|
||||
<div>bystander 2</div>
|
||||
<div>bystander 3</div>
|
||||
<script>
|
||||
function settleAndReset(triggerElement) {
|
||||
getComputedStyle(triggerElement).color;
|
||||
getComputedStyle(triggerElement).color;
|
||||
internals.resetStyleInvalidationCounters();
|
||||
}
|
||||
|
||||
function addBystanders(parent, count) {
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const bystander = document.createElement("div");
|
||||
bystander.textContent = `bystander ${i + 4}`;
|
||||
parent.appendChild(bystander);
|
||||
}
|
||||
}
|
||||
|
||||
test(() => {
|
||||
const target = document.getElementById("target");
|
||||
addBystanders(document.body, 25);
|
||||
|
||||
const sheet = new CSSStyleSheet();
|
||||
sheet.replaceSync(".foo { color: rgb(255, 0, 0); }");
|
||||
document.adoptedStyleSheets = [sheet];
|
||||
|
||||
println(`before disable: ${getComputedStyle(target).color}`);
|
||||
|
||||
settleAndReset(target);
|
||||
sheet.disabled = true;
|
||||
println(`after disable: ${getComputedStyle(target).color}`);
|
||||
|
||||
settleAndReset(target);
|
||||
sheet.disabled = false;
|
||||
println(`after re-enable: ${getComputedStyle(target).color}`);
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user