2020-01-18 09:38:21 +01:00
|
|
|
|
/*
|
2024-10-04 13:19:50 +02:00
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
|
2026-05-20 14:28:15 +01:00
|
|
|
|
* Copyright (c) 2021-2026, Sam Atkins <sam@ladybird.org>
|
2020-01-18 09:38:21 +01:00
|
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 09:38:21 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-04-18 10:54:06 +02:00
|
|
|
|
#include <LibWeb/Bindings/CSSStyleRule.h>
|
2022-09-24 16:34:04 -06:00
|
|
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
2024-10-08 12:06:53 +01:00
|
|
|
|
#include <LibWeb/CSS/CSSRuleList.h>
|
2021-03-07 15:00:02 +01:00
|
|
|
|
#include <LibWeb/CSS/CSSStyleRule.h>
|
2025-02-05 11:54:17 +00:00
|
|
|
|
#include <LibWeb/CSS/CSSStyleSheet.h>
|
2021-10-15 16:53:38 +01:00
|
|
|
|
#include <LibWeb/CSS/Parser/Parser.h>
|
2024-04-14 23:05:05 +01:00
|
|
|
|
#include <LibWeb/CSS/StyleComputer.h>
|
2025-08-11 16:07:02 +01:00
|
|
|
|
#include <LibWeb/CSS/StylePropertyMap.h>
|
2025-12-04 12:03:01 +00:00
|
|
|
|
#include <LibWeb/Dump.h>
|
2019-06-21 19:19:49 +02:00
|
|
|
|
|
2020-07-26 20:01:35 +02:00
|
|
|
|
namespace Web::CSS {
|
2020-03-07 10:27:02 +01:00
|
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
|
GC_DEFINE_ALLOCATOR(CSSStyleRule);
|
2023-11-19 19:47:52 +01:00
|
|
|
|
|
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
|
|
|
|
GC::Ref<CSSStyleRule> CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules)
|
2022-08-07 15:46:44 +02:00
|
|
|
|
{
|
2024-11-14 05:50:17 +13:00
|
|
|
|
return realm.create<CSSStyleRule>(realm, move(selectors), declaration, nested_rules);
|
2022-08-07 15:46:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
|
|
|
|
CSSStyleRule::CSSStyleRule(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules)
|
2024-10-28 20:16:28 +01:00
|
|
|
|
: CSSGroupingRule(realm, nested_rules, Type::Style)
|
2022-08-07 15:46:44 +02:00
|
|
|
|
, m_selectors(move(selectors))
|
2022-08-07 16:21:26 +02:00
|
|
|
|
, m_declaration(declaration)
|
2019-06-21 19:19:49 +02:00
|
|
|
|
{
|
2024-06-14 16:38:23 +02:00
|
|
|
|
m_declaration->set_parent_rule(*this);
|
2023-01-10 06:28:20 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-07 08:41:28 +02:00
|
|
|
|
void CSSStyleRule::initialize(JS::Realm& realm)
|
2023-01-10 06:28:20 -05:00
|
|
|
|
{
|
2024-03-16 13:13:08 +01:00
|
|
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleRule);
|
2025-04-20 16:22:57 +02:00
|
|
|
|
Base::initialize(realm);
|
2019-06-21 20:54:13 +02:00
|
|
|
|
}
|
2019-06-21 19:19:49 +02:00
|
|
|
|
|
2022-08-07 16:21:26 +02:00
|
|
|
|
void CSSStyleRule::visit_edges(Cell::Visitor& visitor)
|
|
|
|
|
|
{
|
|
|
|
|
|
Base::visit_edges(visitor);
|
2023-02-26 16:09:02 -07:00
|
|
|
|
visitor.visit(m_declaration);
|
2025-08-11 16:07:02 +01:00
|
|
|
|
visitor.visit(m_style_map);
|
2022-08-07 16:21:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom-1/#dom-cssstylerule-style
|
2025-03-18 13:52:01 +00:00
|
|
|
|
GC::Ref<CSSStyleProperties> CSSStyleRule::style()
|
2021-10-01 19:57:45 +02:00
|
|
|
|
{
|
2023-02-26 16:09:02 -07:00
|
|
|
|
return m_declaration;
|
2021-10-01 19:57:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 16:07:02 +01:00
|
|
|
|
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssstylerule-stylemap
|
|
|
|
|
|
GC::Ref<StylePropertyMap> CSSStyleRule::style_map()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_style_map)
|
|
|
|
|
|
m_style_map = StylePropertyMap::create(realm(), m_declaration);
|
|
|
|
|
|
return *m_style_map;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom-1/#serialize-a-css-rule
|
2023-11-20 23:16:39 +13:00
|
|
|
|
String CSSStyleRule::serialized() const
|
2021-10-01 19:57:45 +02:00
|
|
|
|
{
|
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Let s initially be the result of performing serialize a group of selectors on the rule’s associated selectors,
|
|
|
|
|
|
// followed by the string " {", i.e., a single SPACE (U+0020), followed by LEFT CURLY BRACKET (U+007B).
|
2023-08-22 12:45:29 +01:00
|
|
|
|
builder.append(serialize_a_group_of_selectors(selectors()));
|
2021-10-01 19:57:45 +02:00
|
|
|
|
builder.append(" {"sv);
|
|
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 2. Let decls be the result of performing serialize a CSS declaration block on the rule’s associated declarations,
|
|
|
|
|
|
// or null if there are no such declarations.
|
2023-11-21 10:39:54 +13:00
|
|
|
|
auto decls = declaration().length() > 0 ? declaration().serialized() : Optional<String>();
|
2021-10-01 19:57:45 +02:00
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 3. Let rules be the result of performing serialize a CSS rule on each rule in the rule’s cssRules list,
|
|
|
|
|
|
// or null if there are no such rules.
|
|
|
|
|
|
Vector<String> rules;
|
|
|
|
|
|
for (auto& rule : css_rules()) {
|
|
|
|
|
|
rules.append(rule->serialized());
|
|
|
|
|
|
}
|
2021-10-01 19:57:45 +02:00
|
|
|
|
|
|
|
|
|
|
// 4. If decls and rules are both null, append " }" to s (i.e. a single SPACE (U+0020) followed by RIGHT CURLY BRACKET (U+007D)) and return s.
|
2024-10-08 12:06:53 +01:00
|
|
|
|
if (!decls.has_value() && rules.is_empty()) {
|
2021-10-01 19:57:45 +02:00
|
|
|
|
builder.append(" }"sv);
|
2024-10-08 12:06:53 +01:00
|
|
|
|
return builder.to_string_without_validation();
|
2021-10-01 19:57:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. If rules is null:
|
2024-10-08 12:06:53 +01:00
|
|
|
|
if (rules.is_empty()) {
|
|
|
|
|
|
// 1. Append a single SPACE (U+0020) to s
|
2021-10-01 19:57:45 +02:00
|
|
|
|
builder.append(' ');
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 2. Append decls to s
|
2023-10-10 15:00:58 +03:30
|
|
|
|
builder.append(*decls);
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 3. Append " }" to s (i.e. a single SPACE (U+0020) followed by RIGHT CURLY BRACKET (U+007D)).
|
2021-10-01 19:57:45 +02:00
|
|
|
|
builder.append(" }"sv);
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 4. Return s.
|
|
|
|
|
|
return builder.to_string_without_validation();
|
2021-10-01 19:57:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 6. Otherwise:
|
|
|
|
|
|
else {
|
|
|
|
|
|
// 1. If decls is not null, prepend it to rules.
|
|
|
|
|
|
if (decls.has_value())
|
|
|
|
|
|
rules.prepend(decls.value());
|
|
|
|
|
|
|
|
|
|
|
|
// 2. For each rule in rules:
|
|
|
|
|
|
for (auto& rule : rules) {
|
2024-10-14 09:43:13 +00:00
|
|
|
|
// * If rule is the empty string, do nothing.
|
|
|
|
|
|
if (rule.is_empty())
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// * Otherwise:
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// 1. Append a newline followed by two spaces to s.
|
|
|
|
|
|
// 2. Append rule to s.
|
|
|
|
|
|
builder.appendff("\n {}", rule);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Append a newline followed by RIGHT CURLY BRACKET (U+007D) to s.
|
|
|
|
|
|
builder.append("\n}"sv);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Return s.
|
|
|
|
|
|
return builder.to_string_without_validation();
|
|
|
|
|
|
}
|
2021-10-01 19:57:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom-1/#dom-cssstylerule-selectortext
|
2023-12-01 16:55:52 +00:00
|
|
|
|
String CSSStyleRule::selector_text() const
|
2021-09-29 23:43:18 +02:00
|
|
|
|
{
|
2021-10-01 19:57:45 +02:00
|
|
|
|
// The selectorText attribute, on getting, must return the result of serializing the associated group of selectors.
|
2023-12-01 16:55:52 +00:00
|
|
|
|
return serialize_a_group_of_selectors(selectors());
|
2021-09-29 23:43:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-08 12:06:53 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom-1/#dom-cssstylerule-selectortext
|
2021-09-29 23:43:18 +02:00
|
|
|
|
void CSSStyleRule::set_selector_text(StringView selector_text)
|
|
|
|
|
|
{
|
2024-10-17 12:26:37 +01:00
|
|
|
|
clear_caches();
|
|
|
|
|
|
|
2021-10-15 16:53:38 +01:00
|
|
|
|
// 1. Run the parse a group of selectors algorithm on the given value.
|
2025-06-23 22:40:37 +12:00
|
|
|
|
Parser::ParsingParams parsing_params { realm() };
|
|
|
|
|
|
|
|
|
|
|
|
if (m_parent_style_sheet)
|
|
|
|
|
|
parsing_params.declared_namespaces = m_parent_style_sheet->declared_namespaces();
|
|
|
|
|
|
|
2024-11-08 17:50:38 +00:00
|
|
|
|
Optional<SelectorList> parsed_selectors;
|
LibWeb/CSS: Implement the `@scope` rule
`@scope (a) to (b) {}` applies its contained style rules to elements
that have `a` as a parent, and do not have `a b` as a parent. Both the
`a` and `b` selector lists are optional.
Because it's situational whether a `@scope` will apply to a given
element, we store the ancestor scope on the `MatchingRule`, similar to
`@container`, and then determine during matching whether all the parent
`@scope`s match or not.
The rules for how selectors inside `@scope` are adjusted and interpreted
are a bit confusing. Unlike for other at-rules, nested style rules
inside `@scope` do not get a leading `&` added during parsing. To
support this, `adapt_nested_relative_selector_list()` now takes a flag
for whether its parent is a `@scope` or not.
`@scope` can also contain nested declarations without itself being
nested inside a style rule.
When determining their selectors, nested declarations rules adopt the
`@scope`'s scoping root if it has one, or otherwise fall back to the
parent element of the `<style>` element (not implemented here,) or the
`:root`. These are required to have zero specificity, so we wrap the
selector in `:where()`.
2026-05-15 17:11:08 +01:00
|
|
|
|
if (auto nesting_parent = nesting_parent_rule()) {
|
2024-11-08 17:50:38 +00:00
|
|
|
|
// AD-HOC: If we're a nested style rule, then we need to parse the selector as relative and then adapt it with implicit &s.
|
LibWeb/CSS: Implement the `@scope` rule
`@scope (a) to (b) {}` applies its contained style rules to elements
that have `a` as a parent, and do not have `a b` as a parent. Both the
`a` and `b` selector lists are optional.
Because it's situational whether a `@scope` will apply to a given
element, we store the ancestor scope on the `MatchingRule`, similar to
`@container`, and then determine during matching whether all the parent
`@scope`s match or not.
The rules for how selectors inside `@scope` are adjusted and interpreted
are a bit confusing. Unlike for other at-rules, nested style rules
inside `@scope` do not get a leading `&` added during parsing. To
support this, `adapt_nested_relative_selector_list()` now takes a flag
for whether its parent is a `@scope` or not.
`@scope` can also contain nested declarations without itself being
nested inside a style rule.
When determining their selectors, nested declarations rules adopt the
`@scope`'s scoping root if it has one, or otherwise fall back to the
parent element of the `<style>` element (not implemented here,) or the
`:root`. These are required to have zero specificity, so we wrap the
selector in `:where()`.
2026-05-15 17:11:08 +01:00
|
|
|
|
auto nesting_parent_type = [&] {
|
|
|
|
|
|
switch (nesting_parent->type()) {
|
|
|
|
|
|
case Type::Style:
|
|
|
|
|
|
return StyleNestingParent::Style;
|
|
|
|
|
|
case Type::Scope:
|
|
|
|
|
|
return StyleNestingParent::Scope;
|
|
|
|
|
|
default:
|
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
|
}
|
|
|
|
|
|
}();
|
|
|
|
|
|
parsed_selectors = parse_selector_for_nested_style_rule(parsing_params, selector_text, nesting_parent_type);
|
2024-11-08 17:50:38 +00:00
|
|
|
|
} else {
|
2025-06-23 22:40:37 +12:00
|
|
|
|
parsed_selectors = parse_selector(parsing_params, selector_text);
|
2024-11-08 17:50:38 +00:00
|
|
|
|
}
|
2021-09-29 23:43:18 +02:00
|
|
|
|
|
2021-10-15 16:53:38 +01:00
|
|
|
|
// 2. If the algorithm returns a non-null value replace the associated group of selectors with the returned value.
|
2024-04-14 23:05:05 +01:00
|
|
|
|
if (parsed_selectors.has_value()) {
|
2024-11-08 17:50:38 +00:00
|
|
|
|
// NOTE: If we have a parent style rule, we need to update the selectors to add any implicit `&`s
|
|
|
|
|
|
|
2021-10-15 16:53:38 +01:00
|
|
|
|
m_selectors = parsed_selectors.release_value();
|
2024-04-14 23:05:05 +01:00
|
|
|
|
if (auto* sheet = parent_style_sheet()) {
|
2025-01-10 20:00:43 +03:00
|
|
|
|
sheet->invalidate_owners(DOM::StyleInvalidationReason::SetSelectorText);
|
2024-04-14 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-09-29 23:43:18 +02:00
|
|
|
|
|
2021-10-15 16:53:38 +01:00
|
|
|
|
// 3. Otherwise, if the algorithm returns a null value, do nothing.
|
2021-09-29 23:43:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-17 12:26:37 +01:00
|
|
|
|
SelectorList const& CSSStyleRule::absolutized_selectors() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_cached_absolutized_selectors.has_value())
|
|
|
|
|
|
return m_cached_absolutized_selectors.value();
|
|
|
|
|
|
|
2026-05-20 14:28:15 +01:00
|
|
|
|
m_cached_absolutized_selectors = absolutize_selectors_relative_to(selectors(), nesting_parent_rule());
|
2024-10-17 12:26:37 +01:00
|
|
|
|
return m_cached_absolutized_selectors.value();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CSSStyleRule::clear_caches()
|
|
|
|
|
|
{
|
|
|
|
|
|
Base::clear_caches();
|
|
|
|
|
|
m_cached_absolutized_selectors.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-10 16:35:59 +01:00
|
|
|
|
void CSSStyleRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
|
|
|
|
|
|
{
|
|
|
|
|
|
Base::set_parent_style_sheet(parent_style_sheet);
|
|
|
|
|
|
|
|
|
|
|
|
// This is annoying: Style values that request resources need to know their CSSStyleSheet in order to fetch them.
|
|
|
|
|
|
for (auto const& property : m_declaration->properties()) {
|
2025-08-08 10:11:51 +01:00
|
|
|
|
const_cast<StyleValue&>(*property.value).set_style_sheet(parent_style_sheet);
|
2025-04-10 16:35:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 14:28:15 +01:00
|
|
|
|
GC::Ptr<CSSRule const> CSSStyleRule::nesting_parent_rule() const
|
2024-11-08 17:50:38 +00:00
|
|
|
|
{
|
2026-05-20 14:28:15 +01:00
|
|
|
|
for (auto const* parent = parent_rule(); parent; parent = parent->parent_rule()) {
|
LibWeb/CSS: Implement the `@scope` rule
`@scope (a) to (b) {}` applies its contained style rules to elements
that have `a` as a parent, and do not have `a b` as a parent. Both the
`a` and `b` selector lists are optional.
Because it's situational whether a `@scope` will apply to a given
element, we store the ancestor scope on the `MatchingRule`, similar to
`@container`, and then determine during matching whether all the parent
`@scope`s match or not.
The rules for how selectors inside `@scope` are adjusted and interpreted
are a bit confusing. Unlike for other at-rules, nested style rules
inside `@scope` do not get a leading `&` added during parsing. To
support this, `adapt_nested_relative_selector_list()` now takes a flag
for whether its parent is a `@scope` or not.
`@scope` can also contain nested declarations without itself being
nested inside a style rule.
When determining their selectors, nested declarations rules adopt the
`@scope`'s scoping root if it has one, or otherwise fall back to the
parent element of the `<style>` element (not implemented here,) or the
`:root`. These are required to have zero specificity, so we wrap the
selector in `:where()`.
2026-05-15 17:11:08 +01:00
|
|
|
|
if (parent->type() == Type::Style || parent->type() == Type::Scope)
|
2026-05-20 14:28:15 +01:00
|
|
|
|
return parent;
|
2024-11-08 17:50:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 12:03:01 +00:00
|
|
|
|
void CSSStyleRule::dump(StringBuilder& builder, int indent_levels) const
|
|
|
|
|
|
{
|
|
|
|
|
|
Base::dump(builder, indent_levels);
|
|
|
|
|
|
|
|
|
|
|
|
for (auto& selector : selectors()) {
|
|
|
|
|
|
dump_selector(builder, selector, indent_levels + 1);
|
|
|
|
|
|
}
|
2026-05-20 14:01:16 +01:00
|
|
|
|
dump_indent(builder, indent_levels + 1);
|
|
|
|
|
|
builder.appendff("Absolutized selectors:\n");
|
|
|
|
|
|
for (auto& selector : absolutized_selectors()) {
|
|
|
|
|
|
dump_selector(builder, selector, indent_levels + 2);
|
|
|
|
|
|
}
|
2025-12-04 12:03:01 +00:00
|
|
|
|
dump_style_properties(builder, declaration(), indent_levels + 1);
|
|
|
|
|
|
|
|
|
|
|
|
dump_indent(builder, indent_levels + 1);
|
|
|
|
|
|
builder.appendff("Child rules ({}):\n", css_rules().length());
|
|
|
|
|
|
for (auto& child_rule : css_rules())
|
|
|
|
|
|
dump_rule(builder, child_rule, indent_levels + 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-07 10:27:02 +01:00
|
|
|
|
}
|