LibWeb: Throw error in insertRule when rule violates constraints

This commit is contained in:
Callum Law 2025-06-21 03:16:34 +12:00 committed by Sam Atkins
commit ef7ba02842
Notes: github-actions[bot] 2025-06-23 11:53:57 +00:00
3 changed files with 125 additions and 1 deletions

View file

@ -90,7 +90,48 @@ WebIDL::ExceptionOr<unsigned> CSSRuleList::insert_a_css_rule(Variant<StringView,
if (!new_rule)
return WebIDL::SyntaxError::create(realm(), "Unable to parse CSS rule."_string);
// FIXME: 6. If new rule cannot be inserted into list at the zero-index position index due to constraints specified by CSS, then throw a HierarchyRequestError exception. [CSS21]
auto has_rule_of_type_other_than_specified_before_index = [&](Vector<CSSRule::Type> types, size_t index) {
for (size_t i = 0; i < index; i++) {
if (!any_of(types, [&](auto type) { return m_rules[i]->type() == type; }))
return true;
}
return false;
};
auto has_rule_of_type_at_or_after_index = [&](CSSRule::Type type, size_t index) {
for (size_t i = index; i < m_rules.size(); i++) {
if (m_rules[i]->type() == type)
return true;
}
return false;
};
// 6. If new rule cannot be inserted into list at the zero-indexed position index due to constraints specified by CSS, then throw a HierarchyRequestError exception. [CSS21]
// "Any @import rules must precede all other valid at-rules and style rules in a style sheet
// (ignoring @charset and @layer statement rules) and must not have any other valid at-rules
// or style rules between it and previous @import rules, or else the @import rule is invalid."
// https://drafts.csswg.org/css-cascade-5/#at-import
//
// "Any @namespace rules must follow all @charset and @import rules and precede all other
// non-ignored at-rules and style rules in a style sheet.
auto rule_is_disallowed = false;
switch (new_rule->type()) {
case CSSRule::Type::LayerStatement:
break;
case CSSRule::Type::Import:
rule_is_disallowed = has_rule_of_type_other_than_specified_before_index({ CSSRule::Type::Import, CSSRule::Type::LayerStatement }, index);
break;
case CSSRule::Type::Namespace:
rule_is_disallowed = has_rule_of_type_at_or_after_index(CSSRule::Type::Import, index) || has_rule_of_type_other_than_specified_before_index({ CSSRule::Type::Import, CSSRule::Type::Namespace, CSSRule::Type::LayerStatement }, index);
break;
default:
rule_is_disallowed = has_rule_of_type_at_or_after_index(CSSRule::Type::Import, index) || has_rule_of_type_at_or_after_index(CSSRule::Type::Namespace, index);
break;
}
if (rule_is_disallowed)
return WebIDL::HierarchyRequestError::create(realm(), "Cannot insert rule at specified index."_string);
// 7. If new rule is an @namespace at-rule, and list contains anything other than @import at-rules, and @namespace at-rules, throw an InvalidStateError exception.
if (new_rule->type() == CSSRule::Type::Namespace && any_of(m_rules, [](auto existing_rule) { return existing_rule->type() != CSSRule::Type::Import && existing_rule->type() != CSSRule::Type::Namespace; }))

View file

@ -0,0 +1,5 @@
Inserting import rules after style rule throws: HierarchyRequestError
Inserting namespace rules after style rule throws: HierarchyRequestError
Inserting namespace rules before import rule throws: HierarchyRequestError
Inserting style rules before import rule throws: HierarchyRequestError
Inserting style rules before namespace rule throws: HierarchyRequestError

View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<style>
div {
}
</style>
<style>
div {
}
</style>
<style>
@import url("foo.css");
</style>
<style>
@namespace abc url("https://www.w3.org/1999/xhtml");
</style>
<style>
@media (all) {
* {
}
}
</style>
<script src="../include.js"></script>
<script>
test(() => {
try {
document.styleSheets[0].insertRule('@import url("foo.css");', 1);
println(
"Fail! insertRule should not allow inserting import rules after style rules."
);
} catch (e) {
println(`Inserting import rules after style rule throws: ${e.name}`);
}
try {
document.styleSheets[1].insertRule(
'@namespace abc url("https://www.w3.org/1999/xhtml");',
1
);
println(
"Fail! insertRule should not allow inserting namespace rules after style rules."
);
} catch (e) {
println(`Inserting namespace rules after style rule throws: ${e.name}`);
}
try {
document.styleSheets[2].insertRule(
'@namespace abc url("https://www.w3.org/1999/xhtml");',
0
);
println(
"Fail! insertRule should not allow inserting namespace rules before import rule."
);
} catch (e) {
println(`Inserting namespace rules before import rule throws: ${e.name}`);
}
try {
document.styleSheets[2].insertRule("div { color: red; }", 0);
println(
"Fail! insertRule should not allow inserting style rules before import rule."
);
} catch (e) {
println(`Inserting style rules before import rule throws: ${e.name}`);
}
try {
document.styleSheets[3].insertRule("div { color: red }", 0);
println(
"Fail! insertRule should not allow inserting style rules before namespace rule."
);
} catch (e) {
println(`Inserting style rules before namespace rule throws: ${e.name}`);
}
});
</script>
</html>