LibWeb: Invalidate owner sheet on add/remove in CSSStyleProperties

Fixes at least 2 WPT subtests.
This commit is contained in:
Andreas Kling 2025-04-24 14:25:50 +02:00 committed by Andreas Kling
commit cf34a7bb32
Notes: github-actions[bot] 2025-04-24 16:28:02 +00:00
6 changed files with 87 additions and 7 deletions

View file

@ -270,9 +270,13 @@ WebIDL::ExceptionOr<void> CSSStyleProperties::set_property(StringView property_n
} }
// 10. If updated is true, update style attribute for the CSS declaration block. // 10. If updated is true, update style attribute for the CSS declaration block.
if (updated) if (updated) {
update_style_attribute(); update_style_attribute();
// Non-standard: Invalidate style for the owners of our containing sheet, if any.
invalidate_owners(DOM::StyleInvalidationReason::CSSStylePropertiesSetProperty);
}
return {}; return {};
} }
@ -1049,9 +1053,13 @@ WebIDL::ExceptionOr<String> CSSStyleProperties::remove_property(StringView prope
} }
// 7. If removed is true, Update style attribute for the CSS declaration block. // 7. If removed is true, Update style attribute for the CSS declaration block.
if (removed) if (removed) {
update_style_attribute(); update_style_attribute();
// Non-standard: Invalidate style for the owners of our containing sheet, if any.
invalidate_owners(DOM::StyleInvalidationReason::CSSStylePropertiesRemoveProperty);
}
// 8. Return value. // 8. Return value.
return value; return value;
} }
@ -1164,15 +1172,20 @@ WebIDL::ExceptionOr<void> CSSStyleProperties::set_css_text(StringView css_text)
update_style_attribute(); update_style_attribute();
// Non-standard: Invalidate style for the owners of our containing sheet, if any. // Non-standard: Invalidate style for the owners of our containing sheet, if any.
if (auto rule = parent_rule()) { invalidate_owners(DOM::StyleInvalidationReason::CSSStylePropertiesTextChange);
if (auto sheet = rule->parent_style_sheet()) {
sheet->invalidate_owners(DOM::StyleInvalidationReason::CSSStylePropertiesTextChange);
}
}
return {}; return {};
} }
void CSSStyleProperties::invalidate_owners(DOM::StyleInvalidationReason reason)
{
if (auto rule = parent_rule()) {
if (auto sheet = rule->parent_style_sheet()) {
sheet->invalidate_owners(reason);
}
}
}
// https://drafts.csswg.org/cssom/#set-a-css-declaration // https://drafts.csswg.org/cssom/#set-a-css-declaration
bool CSSStyleProperties::set_a_css_declaration(PropertyID property_id, NonnullRefPtr<CSSStyleValue const> value, Important important) bool CSSStyleProperties::set_a_css_declaration(PropertyID property_id, NonnullRefPtr<CSSStyleValue const> value, Important important)
{ {

View file

@ -71,6 +71,8 @@ private:
void empty_the_declarations(); void empty_the_declarations();
void set_the_declarations(Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties); void set_the_declarations(Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties);
void invalidate_owners(DOM::StyleInvalidationReason);
Vector<StyleProperty> m_properties; Vector<StyleProperty> m_properties;
HashMap<FlyString, StyleProperty> m_custom_properties; HashMap<FlyString, StyleProperty> m_custom_properties;
}; };

View file

@ -248,6 +248,11 @@ WebIDL::ExceptionOr<void> CSSStyleSheet::replace_sync(StringView text)
rules_without_import.append(rule); rules_without_import.append(rule);
} }
// NOTE: The spec doesn't say where to set the parent style sheet, so we'll do it here.
for (auto& rule : rules_without_import) {
rule->set_parent_style_sheet(this);
}
// 4. Set sheets CSS rules to rules. // 4. Set sheets CSS rules to rules.
m_rules->set_rules({}, rules_without_import); m_rules->set_rules({}, rules_without_import);

View file

@ -52,6 +52,8 @@ enum class ShouldComputeRole {
X(AdoptedStyleSheetsList) \ X(AdoptedStyleSheetsList) \
X(CSSFontLoaded) \ X(CSSFontLoaded) \
X(CSSImportRule) \ X(CSSImportRule) \
X(CSSStylePropertiesRemoveProperty) \
X(CSSStylePropertiesSetProperty) \
X(CSSStylePropertiesTextChange) \ X(CSSStylePropertiesTextChange) \
X(CustomElementStateChange) \ X(CustomElementStateChange) \
X(DidLoseFocus) \ X(DidLoseFocus) \

View file

@ -0,0 +1,9 @@
Harness status: OK
Found 3 tests
2 Pass
1 Fail
Fail mutating constructed CSSStyleSheet applied to root invalidates styles
Pass mutating constructed CSSStyleSheet applied to shadowdom invalidates styles
Pass mutating dependent constructed CSSStyleSheet applied to shadowdom invalidates styles

View file

@ -0,0 +1,49 @@
<!doctype html>
<meta charset="utf-8">
<title>CSSStyleSheet rule mutation invalidation</title>
<link rel="author" href="mailto:wpt@keithcirkel.co.uk" title="Keith Cirkel">
<link rel="help" href="https://drafts.csswg.org/cssom/#extensions-to-the-document-or-shadow-root-interface">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<span id="span1">Should be green.</span>
<span id="span2">Should be green.</span>
<script>
promise_test(async function(t) {
const sheet = new CSSStyleSheet();
sheet.replaceSync('span {color:var(--color, red);}');
document.adoptedStyleSheets = [sheet];
t.add_cleanup(() => {
document.adoptedStyleSheets = [];
})
assert_equals(getComputedStyle(span1).color, "rgb(255, 0, 0)", "Sheet should apply");
sheet.rules[0].style.setProperty('--color', 'green');
assert_equals(getComputedStyle(span1).color, "rgb(0, 128, 0)", "Sheet should invalidate style");
document.adoptedStyleSheets = [];
assert_equals(getComputedStyle(span1).color, "rgb(0, 0, 0)", "Removing sheet should apply");
}, "mutating constructed CSSStyleSheet applied to root invalidates styles");
promise_test(async function() {
span1.attachShadow({mode:'open'})
span1.shadowRoot.append(document.createElement('slot'))
const sheet = new CSSStyleSheet();
sheet.replaceSync(':host {color:var(--color, red);}');
span1.shadowRoot.adoptedStyleSheets = [sheet];
assert_equals(getComputedStyle(span1).color, "rgb(255, 0, 0)", "Sheet should apply");
sheet.rules[0].style.setProperty('--color', 'green');
assert_equals(getComputedStyle(span1).color, "rgb(0, 128, 0)", "Sheet should invalidate style");
}, "mutating constructed CSSStyleSheet applied to shadowdom invalidates styles");
promise_test(async function() {
span2.attachShadow({mode:'open'})
span2.shadowRoot.append(document.createElement('slot'))
const sheet1 = new CSSStyleSheet();
const sheet2 = new CSSStyleSheet();
sheet1.replaceSync(':host {color:var(--color, hotpink);}');
sheet2.replaceSync(':host {--color: blue}');
const style2 = sheet2.rules[0].style;
span2.shadowRoot.adoptedStyleSheets = [sheet1, sheet2];
assert_equals(getComputedStyle(span2).color, "rgb(0, 0, 255)", "Sheet should apply");
style2.setProperty('--color', 'green');
assert_equals(getComputedStyle(span2).color, "rgb(0, 128, 0)", "Sheet should invalidate style");
}, "mutating dependent constructed CSSStyleSheet applied to shadowdom invalidates styles");
</script>