Просмотр исходного кода

LibWeb/CSS: Implement cascade layers (aka `@layer`)

This is done quite simply for now, there are certainly optimizations
that can and should be made later.

With this we now pass:
- http://wpt.live/css/css-cascade/layer-basic.html
- http://wpt.live/css/css-cascade/layer-important.html
- http://wpt.live/css/css-cascade/layer-statement-copy-crash.html
- http://wpt.live/css/css-cascade/layer-stylesheet-sharing-important.html
- http://wpt.live/css/css-cascade/layer-stylesheet-sharing.html
- http://wpt.live/css/css-cascade/layer-vs-inline-style.html
Sam Atkins 10 месяцев назад
Родитель
Сommit
a50da405e9

+ 86 - 0
Tests/LibWeb/Text/expected/css/layer-rule.txt

@@ -0,0 +1,86 @@
+    PASS: A1 Anonymous layers (first target)
+PASS: A1 Anonymous layers (second target)
+PASS: A2 Anonymous layers (first target)
+PASS: A2 Anonymous layers (second target)
+PASS: A3 Anonymous layers (first target)
+PASS: A3 Anonymous layers (second target)
+PASS: A4 Anonymous layers (first target)
+PASS: A4 Anonymous layers (second target)
+PASS: A5 Anonymous layers (first target)
+PASS: A5 Anonymous layers (second target)
+PASS: A6 Anonymous layers (first target)
+PASS: A6 Anonymous layers (second target)
+PASS: A7 Anonymous layers (first target)
+PASS: A7 Anonymous layers (second target)
+PASS: A8 Anonymous layers (first target)
+PASS: A8 Anonymous layers (second target)
+PASS: A9 Anonymous layers (first target)
+PASS: A9 Anonymous layers (second target)
+PASS: B1 Named layers (first target)
+PASS: B1 Named layers (second target)
+PASS: B2 Named layers (first target)
+PASS: B2 Named layers (second target)
+PASS: B3 Named layers (first target)
+PASS: B3 Named layers (second target)
+PASS: B4 Named layers (first target)
+PASS: B4 Named layers (second target)
+PASS: B5 Named layers (first target)
+PASS: B5 Named layers (second target)
+PASS: B6 Named layers (first target)
+PASS: B6 Named layers (second target)
+PASS: B7 Named layers (first target)
+PASS: B7 Named layers (second target)
+PASS: B8 Named layers (first target)
+PASS: B8 Named layers (second target)
+PASS: B9 Named layers (first target)
+PASS: B9 Named layers (second target)
+PASS: B10 Named layers (first target)
+PASS: B10 Named layers (second target)
+PASS: C1 Named layers shorthand (first target)
+PASS: C1 Named layers shorthand (second target)
+PASS: C2 Named layers shorthand (first target)
+PASS: C2 Named layers shorthand (second target)
+PASS: C3 Named layers shorthand (first target)
+PASS: C3 Named layers shorthand (second target)
+PASS: C4 Named layers shorthand (first target)
+PASS: C4 Named layers shorthand (second target)
+PASS: C5 Named layers shorthand (first target)
+PASS: C5 Named layers shorthand (second target)
+PASS: D1 Mixed named and anonymous layers (first target)
+PASS: D1 Mixed named and anonymous layers (second target)
+PASS: D2 Mixed named and anonymous layers (first target)
+PASS: D2 Mixed named and anonymous layers (second target)
+PASS: D3 Mixed named and anonymous layers (first target)
+PASS: D3 Mixed named and anonymous layers (second target)
+PASS: D4 Mixed named and anonymous layers (first target)
+PASS: D4 Mixed named and anonymous layers (second target)
+PASS: D5 Mixed named and anonymous layers (first target)
+PASS: D5 Mixed named and anonymous layers (second target)
+PASS: E1 Statement syntax (first target)
+PASS: E1 Statement syntax (second target)
+PASS: E2 Statement syntax (first target)
+PASS: E2 Statement syntax (second target)
+PASS: E3 Statement syntax (first target)
+PASS: E3 Statement syntax (second target)
+PASS: E4 Statement syntax (first target)
+PASS: E4 Statement syntax (second target)
+PASS: E5 Statement syntax (first target)
+PASS: E5 Statement syntax (second target)
+PASS: I.A1 Unlayered !important style (first target)
+PASS: I.A1 Unlayered !important style (second target)
+PASS: I.B1 Same specificity, layered !important first (first target)
+PASS: I.B1 Same specificity, layered !important first (second target)
+PASS: I.C1 Same specificity, layered !important second (first target)
+PASS: I.C1 Same specificity, layered !important second (second target)
+PASS: I.D1 Same specificity, all !important (first target)
+PASS: I.D1 Same specificity, all !important (second target)
+PASS: I.D2 Same specificity, all !important (first target)
+PASS: I.D2 Same specificity, all !important (second target)
+PASS: I.D3 Same specificity, all !important (first target)
+PASS: I.D3 Same specificity, all !important (second target)
+PASS: I.D4 Same specificity, all !important (first target)
+PASS: I.D4 Same specificity, all !important (second target)
+PASS: I.E1 Different specificity, all !important (first target)
+PASS: I.E1 Different specificity, all !important (second target)
+PASS: I.E2 Different specificity, all !important (first target)
+PASS: I.E2 Different specificity, all !important (second target)

+ 590 - 0
Tests/LibWeb/Text/input/css/layer-rule.html

@@ -0,0 +1,590 @@
+<!DOCTYPE html>
+<!-- Adapted from https://wpt.live/css/css-cascade/layer-basic.html
+ and https://wpt.live/css/css-cascade/layer-important.html -->
+<target class="first"></target>
+<target class="second"></target>
+<script src="../include.js"></script>
+<script>
+    test(() => {
+        // In all test cases, the rule specified as "color: green" should win.
+        var testCases = [
+            {
+                title: 'A1 Anonymous layers',
+                style: `
+                    @layer { }
+                    target { color: green; }
+                `,
+            },
+            {
+                title: 'A2 Anonymous layers',
+                style: `
+                    target { color: green; }
+                    @layer {
+                        target { color: red; }
+                    }
+                `,
+            },
+            {
+                title: 'A3 Anonymous layers',
+                style: `
+                    @layer {
+                        target { color: red; }
+                    }
+                    target { color: green; }
+                `,
+            },
+            {
+                title: 'A4 Anonymous layers',
+                style: `
+                    @layer {
+                        target { color: red; }
+                    }
+                    @layer {
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'A5 Anonymous layers',
+                style: `
+                    @layer {
+                        target { color: green; }
+                        @layer {
+                            target { color: red; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'A6 Anonymous layers',
+                style: `
+                    @layer {
+                        @layer {
+                            target { color: red; }
+                        }
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'A7 Anonymous layers',
+                style: `
+                    @layer {
+                        @layer {
+                            target { color: red; }
+                        }
+                        target { color: red; }
+                    }
+                    @layer {
+                        @layer {
+                            target { color: red; }
+                        }
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'A8 Anonymous layers',
+                style: `
+                    @layer {
+                        @layer {
+                            @layer {
+                                target { color: red; }
+                            }
+                        }
+                        target { color: red; }
+                    }
+                    @layer {
+                        @layer {
+                            target { color: red; }
+                        }
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'A9 Anonymous layers',
+                style: `
+                    @layer {
+                        @layer {
+                            target { color: red; }
+                        }
+                        target { color: red; }
+                    }
+                    @layer {
+                        @layer {
+                            @layer {
+                                target { color: red; }
+                            }
+                        }
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'B1 Named layers',
+                style: `
+                    @layer A {
+                    }
+                    target { color: green; }
+                `,
+            },
+            {
+                title: 'B2 Named layers',
+                style: `
+                    @layer A {
+                        target { color: red; }
+                    }
+                    target { color: green; }
+                `,
+            },
+            {
+                title: 'B3 Named layers',
+                style: `
+                    @layer A {
+                        target { color: red; }
+                    }
+                    @layer A {
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'B4 Named layers',
+                style: `
+                    @layer A {
+                        target { color: red; }
+                    }
+                    @layer B {
+                        target { color: green; }
+                    }
+                    @layer A {
+                        target { color: red; }
+                    }
+                `,
+            },
+            {
+                title: 'B5 Named layers',
+                style: `
+                    @layer A {
+                        target { color: green; }
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'B6 Named layers',
+                style: `
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer A {
+                        @layer A {
+                            target { color: green; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'B7 Named layers',
+                style: `
+                    @layer A {
+                        target { color: red; }
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer B {
+                        target { color: green; }
+                    }
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'B8 Named layers',
+                style: `
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer B {
+                        @layer A {
+                            target { color: green; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'B9 Named layers',
+                style: `
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer B {
+                        @layer A {
+                            target.first { color: green; }
+                        }
+                    }
+                   @layer A {
+                        @layer A {
+                            target.first { color: red; }
+                            target.second { color: green; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'B10 Named layers',
+                style: `
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer B {
+                        @layer A {
+                            target.first { color: green; }
+                        }
+                    }
+                   @layer A {
+                        @layer B {
+                            target.first { color: red; }
+                            target.second { color: green; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'C1 Named layers shorthand',
+                style: `
+                    @layer A.A {
+                        target { color: red; }
+                    }
+                    @layer B.A {
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'C2 Named layers shorthand',
+                style: `
+                    @layer A.A {
+                        target { color: red; }
+                    }
+                    @layer B.A {
+                        target.first { color: green; }
+                    }
+                    @layer A.A {
+                        target.first { color: red; }
+                        target.second { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'C3 Named layers shorthand',
+                style: `
+                    @layer A.A {
+                        target { color: red; }
+                    }
+                    @layer B.A {
+                        target.first { color: green; }
+                    }
+                    @layer A.B {
+                        target.first { color: red; }
+                        target.second { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'C4 Named layers shorthand',
+                style: `
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer B.A {
+                        target { color: green; }
+                    }
+                    @layer A.A
+                        target { color: red; }
+                    }
+                `,
+            },
+            {
+                title: 'C5 Named layers shorthand',
+                style: `
+                    @layer A {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer B.A {
+                        target { color: green; }
+                    }
+                    @layer A.B {
+                        target { color: red; }
+                    }
+                `,
+            },
+            {
+                title: 'D1 Mixed named and anonymous layers',
+                style: `
+                    @layer A {
+                        target { color: red; }
+                    }
+                    @layer {
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'D2 Mixed named and anonymous layers',
+                style: `
+                    @layer A {
+                        @layer {
+                            target { color: red; }
+                        }
+                    }
+                    @layer A {
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'D3 Mixed named and anonymous layers',
+                style: `
+                    @layer A {
+                        @layer {
+                            target { color: red; }
+                        }
+                    }
+                    @layer A {
+                        @layer {
+                            target { color: green; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'D4 Mixed named and anonymous layers',
+                style: `
+                    @layer A {
+                        @layer {
+                            target { color: red; }
+                        }
+                    }
+                    @layer {
+                        target { color: green; }
+                    }
+                    @layer A {
+                        @layer {
+                            target { color: red; }
+                        }
+                    }
+                `,
+            },
+            {
+                title: 'D5 Mixed named and anonymous layers',
+                style: `
+                    @layer {
+                        @layer A {
+                            target { color: red; }
+                        }
+                    }
+                    @layer {
+                        target { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'E1 Statement syntax',
+                style: `
+                    @layer A, B, C;
+                    @layer A {
+                        target.first { color: red; }
+                        target.second { color: red; }
+                    }
+                    @layer B {
+                        target.first { color: red; }
+                    }
+                    @layer C {
+                        target.first { color: green; }
+                        target.second { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'E2 Statement syntax',
+                style: `
+                    @layer A, C, B;
+                    @layer A {
+                        target.first { color: red; }
+                        target.second { color: red; }
+                    }
+                    @layer B {
+                        target.first { color: green; }
+                    }
+                    @layer C {
+                        target.first { color: red; }
+                        target.second { color: green; }
+                    }
+                `,
+            },
+            {
+                title: 'E3 Statement syntax',
+                style: `
+                    @layer C, B, A;
+                    @layer A {
+                        target.first { color: green; }
+                        target.second { color: green; }
+                    }
+                    @layer B {
+                        target.first { color: red; }
+                    }
+                    @layer C {
+                        target.first { color: red; }
+                        target.second { color: red; }
+                    }
+                `,
+            },
+            {
+                title: 'E4 Statement syntax',
+                style: `
+                    @layer B, A.B, A.A;
+                    @layer A {
+                        @layer A {
+                            target.first { color: green; }
+                        }
+                        @layer B {
+                            target.first { color: red; }
+                            target.second { color: green; }
+                        }
+                    }
+                    @layer B {
+                        target { color: red; }
+                    }
+                `,
+            },
+            {
+                title: 'E5 Statement syntax',
+                style: `
+                    @layer A.B, B, A.A;
+                    @layer A {
+                        @layer A {
+                            target.first { color: red; }
+                        }
+                        @layer B {
+                            target.first { color: red; }
+                            target.second { color: red; }
+                        }
+                    }
+                    @layer B {
+                        target { color: green; }
+                    }
+                `,
+            },
+
+            {
+                title: 'I.A1 Unlayered !important style',
+                style: `
+                    target { color: green !important; }
+                    target { color: red; }
+                `
+            },
+            {
+                title: 'I.B1 Same specificity, layered !important first',
+                style: `
+                    @layer { target { color: green !important; } }
+                    target { color: red; }
+                `,
+            },
+            {
+                title: 'I.C1 Same specificity, layered !important second',
+                style: `
+                    target { color: red; }
+                    @layer { target { color: green !important; } }
+                `,
+            },
+            {
+                title: 'I.D1 Same specificity, all !important',
+                style: `
+                    @layer { target { color: green !important; } }
+                    @layer { target { color: red !important; } }
+                    target { color: red !important; }
+                `,
+            },
+            {
+                title: 'I.D2 Same specificity, all !important',
+                style: `
+                    @layer { target { color: green !important; } }
+                    target { color: red !important; }
+                    @layer { target { color: red !important; } }
+                `,
+            },
+            {
+                title: 'I.D3 Same specificity, all !important',
+                style: `
+                    target { color: red !important; }
+                    @layer { target { color: green !important; } }
+                    @layer { target { color: red !important; } }
+                `,
+            },
+            {
+                title: 'I.D4 Same specificity, all !important',
+                style: `
+                    @layer A, B;
+                    @layer B { target { color: red !important; } }
+                    @layer A { target { color: green !important; } }
+                    target { color: red !important; }
+                `,
+            },
+            {
+                title: 'I.E1 Different specificity, all !important',
+                style: `
+                    @layer { target { color: green !important; } }
+                    @layer { target { color: red !important; } }
+                    target.first { color: red !important; }
+                `,
+            },
+            {
+                title: 'I.E2 Different specificity, all !important',
+                style: `
+                    @layer { target { color: green !important; } }
+                    @layer { target.first { color: red !important; } }
+                    target { color: red !important; }
+                `,
+            },
+        ];
+
+        for (let testCase of testCases) {
+            const styleElement = document.createElement('style');
+            styleElement.textContent = testCase['style'];
+            document.head.append(styleElement);
+
+            var targets = document.querySelectorAll('target');
+            for (let target of targets) {
+                let actual = window.getComputedStyle(target).color;
+                if (actual === 'rgb(0, 128, 0)') {
+                    println(`PASS: ${testCase['title']} (${target.classList[0]} target)`);
+                } else {
+                    println(`FAIL: ${testCase['title']} (${target.classList[0]} target) - Expected 'rgb(0, 128, 0)', got '${actual}'`);
+                }
+            }
+
+            styleElement.remove();
+        }
+
+    });
+</script>

+ 5 - 0
Userland/Libraries/LibWeb/CSS/CSSStyleRule.cpp

@@ -46,6 +46,11 @@ CSSStyleDeclaration* CSSStyleRule::style()
     return m_declaration;
     return m_declaration;
 }
 }
 
 
+FlyString CSSStyleRule::qualified_layer_name() const
+{
+    return parent_layer_internal_qualified_name();
+}
+
 // https://www.w3.org/TR/cssom/#serialize-a-css-rule
 // https://www.w3.org/TR/cssom/#serialize-a-css-rule
 String CSSStyleRule::serialized() const
 String CSSStyleRule::serialized() const
 {
 {

+ 2 - 0
Userland/Libraries/LibWeb/CSS/CSSStyleRule.h

@@ -33,6 +33,8 @@ public:
 
 
     CSSStyleDeclaration* style();
     CSSStyleDeclaration* style();
 
 
+    FlyString qualified_layer_name() const;
+
 private:
 private:
     CSSStyleRule(JS::Realm&, Vector<NonnullRefPtr<Selector>>&&, PropertyOwningCSSStyleDeclaration&);
     CSSStyleRule(JS::Realm&, Vector<NonnullRefPtr<Selector>>&&, PropertyOwningCSSStyleDeclaration&);
 
 

+ 106 - 10
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -29,6 +29,8 @@
 #include <LibWeb/CSS/CSSAnimation.h>
 #include <LibWeb/CSS/CSSAnimation.h>
 #include <LibWeb/CSS/CSSFontFaceRule.h>
 #include <LibWeb/CSS/CSSFontFaceRule.h>
 #include <LibWeb/CSS/CSSImportRule.h>
 #include <LibWeb/CSS/CSSImportRule.h>
+#include <LibWeb/CSS/CSSLayerBlockRule.h>
+#include <LibWeb/CSS/CSSLayerStatementRule.h>
 #include <LibWeb/CSS/CSSStyleRule.h>
 #include <LibWeb/CSS/CSSStyleRule.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/CSS/SelectorEngine.h>
 #include <LibWeb/CSS/SelectorEngine.h>
@@ -98,6 +100,7 @@ StyleComputer::StyleComputer(DOM::Document& document)
     , m_default_font_metrics(16, Platform::FontPlugin::the().default_font().pixel_metrics())
     , m_default_font_metrics(16, Platform::FontPlugin::the().default_font().pixel_metrics())
     , m_root_element_font_metrics(m_default_font_metrics)
     , m_root_element_font_metrics(m_default_font_metrics)
 {
 {
+    m_qualified_layer_names_in_order.append({});
 }
 }
 
 
 StyleComputer::~StyleComputer() = default;
 StyleComputer::~StyleComputer() = default;
@@ -322,6 +325,13 @@ StyleComputer::RuleCache const& StyleComputer::rule_cache_for_cascade_origin(Cas
     return true;
     return true;
 }
 }
 
 
+[[nodiscard]] static bool filter_layer(FlyString const& qualified_layer_name, MatchingRule const& rule)
+{
+    if (rule.rule && rule.rule->qualified_layer_name() != qualified_layer_name)
+        return false;
+    return true;
+}
+
 bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector) const
 bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector) const
 {
 {
     for (u32 hash : selector.ancestor_hashes()) {
     for (u32 hash : selector.ancestor_hashes()) {
@@ -333,7 +343,7 @@ bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector)
     return false;
     return false;
 }
 }
 
 
-Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement::Type> pseudo_element) const
+Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, FlyString const& qualified_layer_name) const
 {
 {
     auto const& root_node = element.root();
     auto const& root_node = element.root();
     auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr;
     auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr;
@@ -351,12 +361,12 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
         rules_to_run.grow_capacity(rules_to_run.size() + rules.size());
         rules_to_run.grow_capacity(rules_to_run.size() + rules.size());
         if (pseudo_element.has_value()) {
         if (pseudo_element.has_value()) {
             for (auto const& rule : rules) {
             for (auto const& rule : rules) {
-                if (rule.contains_pseudo_element && filter_namespace_rule(element, rule))
+                if (rule.contains_pseudo_element && filter_namespace_rule(element, rule) && filter_layer(qualified_layer_name, rule))
                     rules_to_run.unchecked_append(rule);
                     rules_to_run.unchecked_append(rule);
             }
             }
         } else {
         } else {
             for (auto const& rule : rules) {
             for (auto const& rule : rules) {
-                if (!rule.contains_pseudo_element && filter_namespace_rule(element, rule))
+                if (!rule.contains_pseudo_element && filter_namespace_rule(element, rule) && filter_layer(qualified_layer_name, rule))
                     rules_to_run.unchecked_append(rule);
                     rules_to_run.unchecked_append(rule);
             }
             }
         }
         }
@@ -1737,6 +1747,7 @@ static void apply_dimension_attribute(StyleProperties& style, DOM::Element const
 }
 }
 
 
 // https://www.w3.org/TR/css-cascade/#cascading
 // https://www.w3.org/TR/css-cascade/#cascading
+// https://drafts.csswg.org/css-cascade-5/#layering
 void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
 void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
 {
 {
     // First, we collect all the CSS rules whose selectors match `element`:
     // First, we collect all the CSS rules whose selectors match `element`:
@@ -1745,8 +1756,16 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
     sort_matching_rules(matching_rule_set.user_agent_rules);
     sort_matching_rules(matching_rule_set.user_agent_rules);
     matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element);
     matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element);
     sort_matching_rules(matching_rule_set.user_rules);
     sort_matching_rules(matching_rule_set.user_rules);
-    matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element);
-    sort_matching_rules(matching_rule_set.author_rules);
+    // @layer-ed author rules
+    for (auto const& layer_name : m_qualified_layer_names_in_order) {
+        auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, layer_name);
+        sort_matching_rules(layer_rules);
+        matching_rule_set.author_rules.append({ layer_name, layer_rules });
+    }
+    // Un-@layer-ed author rules
+    auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element);
+    sort_matching_rules(unlayered_author_rules);
+    matching_rule_set.author_rules.append({ {}, unlayered_author_rules });
 
 
     if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
     if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
         VERIFY(pseudo_element.has_value());
         VERIFY(pseudo_element.has_value());
@@ -1758,7 +1777,10 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
     }
     }
 
 
     // Then we resolve all the CSS custom properties ("variables") for this element:
     // Then we resolve all the CSS custom properties ("variables") for this element:
-    cascade_custom_properties(element, pseudo_element, matching_rule_set.author_rules);
+    // FIXME: Also resolve !important custom properties, in a second cascade.
+    for (auto& layer : matching_rule_set.author_rules) {
+        cascade_custom_properties(element, pseudo_element, layer.rules);
+    }
 
 
     // Then we apply the declarations from the matched rules in cascade order:
     // Then we apply the declarations from the matched rules in cascade order:
 
 
@@ -1789,8 +1811,10 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
         }
         }
     }
     }
 
 
-    // Normal author declarations
-    cascade_declarations(style, element, pseudo_element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::No);
+    // Normal author declarations, ordered by @layer, with un-@layer-ed rules last
+    for (auto const& layer : matching_rule_set.author_rules) {
+        cascade_declarations(style, element, pseudo_element, layer.rules, CascadeOrigin::Author, Important::No);
+    }
 
 
     // Animation declarations [css-animations-2]
     // Animation declarations [css-animations-2]
     auto animation_name = [&]() -> Optional<String> {
     auto animation_name = [&]() -> Optional<String> {
@@ -1854,8 +1878,10 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
         }
         }
     }
     }
 
 
-    // Important author declarations
-    cascade_declarations(style, element, pseudo_element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::Yes);
+    // Important author declarations, with un-@layer-ed rules first, followed by each @layer in reverse order.
+    for (auto const& layer : matching_rule_set.author_rules.in_reverse()) {
+        cascade_declarations(style, element, pseudo_element, layer.rules, CascadeOrigin::Author, Important::Yes);
+    }
 
 
     // Important user declarations
     // Important user declarations
     cascade_declarations(style, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::Yes);
     cascade_declarations(style, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::Yes);
@@ -2742,12 +2768,82 @@ NonnullOwnPtr<StyleComputer::RuleCache> StyleComputer::make_rule_cache_for_casca
     return rule_cache;
     return rule_cache;
 }
 }
 
 
+struct LayerNode {
+    OrderedHashMap<FlyString, LayerNode> children {};
+};
+
+static void flatten_layer_names_tree(Vector<FlyString>& layer_names, StringView const& parent_qualified_name, FlyString const& name, LayerNode const& node)
+{
+    FlyString qualified_name = parent_qualified_name.is_empty() ? name : MUST(String::formatted("{}.{}", parent_qualified_name, name));
+
+    for (auto const& item : node.children)
+        flatten_layer_names_tree(layer_names, qualified_name, item.key, item.value);
+
+    layer_names.append(qualified_name);
+}
+
+void StyleComputer::build_qualified_layer_names_cache()
+{
+    LayerNode root;
+
+    auto insert_layer_name = [&](FlyString const& internal_qualified_name) {
+        auto* node = &root;
+        internal_qualified_name.bytes_as_string_view()
+            .for_each_split_view('.', SplitBehavior::Nothing, [&](StringView part) {
+                auto local_name = MUST(FlyString::from_utf8(part));
+                node = &node->children.ensure(local_name);
+            });
+    };
+
+    // Walk all style sheets, identifying when we first see a @layer name, and add its qualified name to the list.
+    // TODO: Separate the light and shadow-dom layers.
+    for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet, JS::GCPtr<DOM::ShadowRoot>) {
+        // NOTE: Postorder so that a @layer block is iterated after its children,
+        // because we want those children to occur before it in the list.
+        sheet.for_each_effective_rule(TraversalOrder::Postorder, [&](auto& rule) {
+            switch (rule.type()) {
+            case CSSRule::Type::Import:
+                // TODO: Handle `layer(foo)` in import rules once we implement that.
+                break;
+            case CSSRule::Type::LayerBlock: {
+                auto& layer_block = static_cast<CSSLayerBlockRule const&>(rule);
+                insert_layer_name(layer_block.internal_qualified_name({}));
+                break;
+            }
+            case CSSRule::Type::LayerStatement: {
+                auto& layer_statement = static_cast<CSSLayerStatementRule const&>(rule);
+                auto qualified_names = layer_statement.internal_qualified_name_list({});
+                for (auto& name : qualified_names)
+                    insert_layer_name(name);
+                break;
+            }
+
+                // Ignore everything else
+            case CSSRule::Type::Style:
+            case CSSRule::Type::Media:
+            case CSSRule::Type::FontFace:
+            case CSSRule::Type::Keyframes:
+            case CSSRule::Type::Keyframe:
+            case CSSRule::Type::Namespace:
+            case CSSRule::Type::Supports:
+                break;
+            }
+        });
+    });
+
+    // Now, produce a flat list of qualified names to use later
+    m_qualified_layer_names_in_order.clear();
+    flatten_layer_names_tree(m_qualified_layer_names_in_order, ""sv, {}, root);
+}
+
 void StyleComputer::build_rule_cache()
 void StyleComputer::build_rule_cache()
 {
 {
     if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) {
     if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) {
         m_user_style_sheet = JS::make_handle(parse_css_stylesheet(CSS::Parser::ParsingContext(document()), user_style_source.value()));
         m_user_style_sheet = JS::make_handle(parse_css_stylesheet(CSS::Parser::ParsingContext(document()), user_style_source.value()));
     }
     }
 
 
+    build_qualified_layer_names_cache();
+
     m_author_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::Author);
     m_author_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::Author);
     m_user_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::User);
     m_user_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::User);
     m_user_agent_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent);
     m_user_agent_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent);

+ 10 - 2
Userland/Libraries/LibWeb/CSS/StyleComputer.h

@@ -133,7 +133,7 @@ public:
     NonnullRefPtr<StyleProperties> compute_style(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type> = {}) const;
     NonnullRefPtr<StyleProperties> compute_style(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type> = {}) const;
     RefPtr<StyleProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>) const;
     RefPtr<StyleProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>) const;
 
 
-    Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement::Type>) const;
+    Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement::Type>, FlyString const& qualified_layer_name = {}) const;
 
 
     void invalidate_rule_cache();
     void invalidate_rule_cache();
 
 
@@ -188,10 +188,18 @@ private:
 
 
     [[nodiscard]] Length::FontMetrics calculate_root_element_font_metrics(StyleProperties const&) const;
     [[nodiscard]] Length::FontMetrics calculate_root_element_font_metrics(StyleProperties const&) const;
 
 
+    Vector<FlyString> m_qualified_layer_names_in_order;
+    void build_qualified_layer_names_cache();
+
+    struct LayerMatchingRules {
+        FlyString qualified_layer_name;
+        Vector<MatchingRule> rules;
+    };
+
     struct MatchingRuleSet {
     struct MatchingRuleSet {
         Vector<MatchingRule> user_agent_rules;
         Vector<MatchingRule> user_agent_rules;
         Vector<MatchingRule> user_rules;
         Vector<MatchingRule> user_rules;
-        Vector<MatchingRule> author_rules;
+        Vector<LayerMatchingRules> author_rules;
     };
     };
 
 
     void cascade_declarations(StyleProperties&, DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, Vector<MatchingRule> const&, CascadeOrigin, Important) const;
     void cascade_declarations(StyleProperties&, DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, Vector<MatchingRule> const&, CascadeOrigin, Important) const;