Parcourir la source

LibWeb: Rough implementation of CSS namespace rule

This provides a rough implementation of the CSS @namespace rule.
Currently we just support default namespaces, namespace prefixes
are still to come.
Jonah il y a 2 ans
Parent
commit
60e35f2a97

+ 13 - 0
Tests/LibWeb/Layout/expected/css-namespace-rule-matches.txt

@@ -0,0 +1,13 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+  BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
+    BlockContainer <body> at (8,20) content-size 784x21.828125 children: not-inline
+      BlockContainer <p> at (8,20) content-size 784x21.828125 children: inline
+        line 0 width: 183.875, height: 21.828125, bottom: 21.828125, baseline: 16.890625
+          frag 0 from TextNode start: 0, length: 15, rect: [18,20 163.875x21.828125]
+            "Should be green"
+        TextNode <#text>
+        InlineNode <a>
+          TextNode <#text>
+        TextNode <#text>
+      BlockContainer <(anonymous)> at (8,61.828125) content-size 784x0 children: inline
+        TextNode <#text>

+ 13 - 0
Tests/LibWeb/Layout/expected/css-namespace-rule-no-match.txt

@@ -0,0 +1,13 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+  BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
+    BlockContainer <body> at (8,20) content-size 784x21.828125 children: not-inline
+      BlockContainer <p> at (8,20) content-size 784x21.828125 children: inline
+        line 0 width: 151.328125, height: 21.828125, bottom: 21.828125, baseline: 16.890625
+          frag 0 from TextNode start: 0, length: 13, rect: [13,20 141.328125x21.828125]
+            "Should be red"
+        TextNode <#text>
+        InlineNode <a>
+          TextNode <#text>
+        TextNode <#text>
+      BlockContainer <(anonymous)> at (8,61.828125) content-size 784x0 children: inline
+        TextNode <#text>

+ 19 - 0
Tests/LibWeb/Layout/input/css-namespace-rule-matches.html

@@ -0,0 +1,19 @@
+<style>
+    * {
+        font: 20px SerenitySans;
+    }
+    a {
+        padding: 5px;
+        color: red;
+    }
+</style>
+<style>
+    @namespace url('http://www.w3.org/1999/xhtml');
+    a {
+        padding: 10px;
+        color: green;
+    }
+</style>
+<p>
+    <a href="#">Should be green</a>
+</p>

+ 19 - 0
Tests/LibWeb/Layout/input/css-namespace-rule-no-match.html

@@ -0,0 +1,19 @@
+<style>
+    * {
+        font: 20px SerenitySans;
+    }
+    a {
+        padding: 5px;
+        color: red;
+    }
+</style>
+<style>
+    @namespace url('http://www.w3.org/2000/svg');
+    a {
+        padding: 10px;
+        color: green;
+    }
+</style>
+<p>
+    <a href="#">Should be red</a>
+</p>

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -29,6 +29,7 @@ set(SOURCES
     CSS/CSSFontFaceRule.cpp
     CSS/CSSMediaRule.cpp
     CSS/CSSNumericType.cpp
+    CSS/CSSNamespaceRule.cpp
     CSS/CSSRule.cpp
     CSS/CSSRuleList.cpp
     CSS/CSSStyleDeclaration.cpp

+ 59 - 0
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.cpp

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023, Jonah Shafran <jonahshafran@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Realm.h>
+#include <LibWeb/Bindings/CSSNamespaceRulePrototype.h>
+#include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/CSS/CSSNamespaceRule.h>
+#include <LibWeb/WebIDL/ExceptionOr.h>
+
+namespace Web::CSS {
+
+CSSNamespaceRule::CSSNamespaceRule(JS::Realm& realm, Optional<StringView> prefix, StringView namespace_uri)
+    : CSSRule(realm)
+    , m_namespace_uri(namespace_uri)
+    , m_prefix(prefix.has_value() ? prefix.value() : ""sv)
+{
+}
+
+WebIDL::ExceptionOr<JS::NonnullGCPtr<CSSNamespaceRule>> CSSNamespaceRule::create(JS::Realm& realm, Optional<AK::StringView> prefix, AK::StringView namespace_uri)
+{
+    return MUST_OR_THROW_OOM(realm.heap().allocate<CSSNamespaceRule>(realm, realm, prefix, namespace_uri));
+}
+
+JS::ThrowCompletionOr<void> CSSNamespaceRule::initialize(JS::Realm& realm)
+{
+    MUST_OR_THROW_OOM(Base::initialize(realm));
+    set_prototype(&Bindings::ensure_web_prototype<Bindings::CSSNamespaceRulePrototype>(realm, "CSSNamespaceRule"));
+
+    return {};
+}
+
+// https://www.w3.org/TR/cssom/#serialize-a-css-rule
+DeprecatedString CSSNamespaceRule::serialized() const
+{
+    StringBuilder builder;
+    // The literal string "@namespace", followed by a single SPACE (U+0020),
+    builder.append("@namespace "sv);
+
+    // followed by the serialization as an identifier of the prefix attribute (if any),
+    if (!m_prefix.is_empty() && !m_prefix.is_null()) {
+        builder.append(m_prefix);
+        // followed by a single SPACE (U+0020) if there is a prefix,
+        builder.append(" "sv);
+    }
+
+    //  followed by the serialization as URL of the namespaceURI attribute,
+    builder.append(m_namespace_uri);
+
+    // followed the character ";" (U+003B).
+    builder.append(";"sv);
+
+    return builder.to_deprecated_string();
+}
+
+}

+ 37 - 0
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.h

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2023, Jonah Shafran <jonahshafran@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/CSS/CSSRule.h>
+
+namespace Web::CSS {
+
+class CSSNamespaceRule final : public CSSRule {
+    WEB_PLATFORM_OBJECT(CSSNamespaceRule, CSSRule);
+
+public:
+    static WebIDL::ExceptionOr<JS::NonnullGCPtr<CSSNamespaceRule>> create(JS::Realm&, Optional<StringView> prefix, StringView namespace_uri);
+
+    virtual ~CSSNamespaceRule() = default;
+
+    void set_namespace_uri(DeprecatedString value) { m_namespace_uri = move(value); }
+    DeprecatedString namespace_uri() const { return m_namespace_uri; }
+    void set_prefix(DeprecatedString value) { m_prefix = move(value); }
+    DeprecatedString prefix() const { return m_prefix; }
+    virtual Type type() const override { return Type::Namespace; }
+
+private:
+    CSSNamespaceRule(JS::Realm&, Optional<StringView> prefix, StringView namespace_uri);
+
+    virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
+
+    virtual DeprecatedString serialized() const override;
+    DeprecatedString m_namespace_uri;
+    DeprecatedString m_prefix;
+};
+
+}

+ 8 - 0
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.idl

@@ -0,0 +1,8 @@
+#import <CSS/CSSRule.idl>
+
+// https://www.w3.org/TR/cssom/#the-cssnamespacerule-interface
+[Exposed=Window]
+interface CSSNamespaceRule : CSSRule {
+  readonly attribute CSSOMString namespaceURI;
+  readonly attribute CSSOMString prefix;
+};

+ 1 - 0
Userland/Libraries/LibWeb/CSS/CSSRule.h

@@ -29,6 +29,7 @@ public:
         FontFace = 5,
         Keyframes = 7,
         Keyframe = 8,
+        Namespace = 10,
         Supports = 12,
     };
 

+ 4 - 0
Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp

@@ -144,6 +144,7 @@ void CSSRuleList::for_each_effective_style_rule(Function<void(CSSStyleRule const
             break;
         case CSSRule::Type::Keyframe:
         case CSSRule::Type::Keyframes:
+        case CSSRule::Type::Namespace:
             break;
         }
     }
@@ -174,6 +175,8 @@ void CSSRuleList::for_each_effective_keyframes_at_rule(Function<void(CSSKeyframe
         case CSSRule::Type::Keyframes:
             callback(static_cast<CSSKeyframesRule const&>(*rule));
             break;
+        case CSSRule::Type::Namespace:
+            break;
         }
     }
 }
@@ -212,6 +215,7 @@ bool CSSRuleList::evaluate_media_queries(HTML::Window const& window)
         }
         case CSSRule::Type::Keyframe:
         case CSSRule::Type::Keyframes:
+        case CSSRule::Type::Namespace:
             break;
         }
     }

+ 14 - 0
Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp

@@ -6,6 +6,7 @@
 
 #include <LibWeb/Bindings/CSSStyleSheetPrototype.h>
 #include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/CSS/CSSNamespaceRule.h>
 #include <LibWeb/CSS/CSSStyleSheet.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/CSS/StyleComputer.h>
@@ -137,4 +138,17 @@ void CSSStyleSheet::set_style_sheet_list(Badge<StyleSheetList>, StyleSheetList*
     m_style_sheet_list = list;
 }
 
+Optional<StringView> CSSStyleSheet::namespace_filter() const
+{
+    for (JS::NonnullGCPtr<CSSRule> rule : *m_rules) {
+        if (rule->type() == CSSRule::Type::Namespace) {
+            auto& namespace_rule = verify_cast<CSSNamespaceRule>(*rule);
+            if (!namespace_rule.namespace_uri().is_empty() && namespace_rule.prefix().is_empty())
+                return namespace_rule.namespace_uri().view();
+        }
+    }
+
+    return {};
+}
+
 }

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

@@ -48,6 +48,8 @@ public:
 
     void set_style_sheet_list(Badge<StyleSheetList>, StyleSheetList*);
 
+    Optional<StringView> namespace_filter() const;
+
 private:
     CSSStyleSheet(JS::Realm&, CSSRuleList&, MediaList&, Optional<AK::URL> location);
 

+ 32 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -17,6 +17,7 @@
 #include <LibWeb/CSS/CSSKeyframeRule.h>
 #include <LibWeb/CSS/CSSKeyframesRule.h>
 #include <LibWeb/CSS/CSSMediaRule.h>
+#include <LibWeb/CSS/CSSNamespaceRule.h>
 #include <LibWeb/CSS/CSSStyleDeclaration.h>
 #include <LibWeb/CSS/CSSStyleRule.h>
 #include <LibWeb/CSS/CSSStyleSheet.h>
@@ -3241,6 +3242,37 @@ CSSRule* Parser::convert_to_rule(NonnullRefPtr<Rule> rule)
 
             return CSSKeyframesRule::create(m_context.realm(), name, move(keyframes)).release_value_but_fixme_should_propagate_errors();
         }
+        if (rule->at_rule_name().equals_ignoring_ascii_case("namespace"sv)) {
+            // https://drafts.csswg.org/css-namespaces/#syntax
+            auto token_stream = TokenStream { rule->prelude() };
+            token_stream.skip_whitespace();
+
+            auto token = token_stream.next_token();
+            Optional<StringView> prefix = {};
+            if (token.is(Token::Type::Ident)) {
+                prefix = token.token().ident();
+                token_stream.skip_whitespace();
+                token = token_stream.next_token();
+            }
+
+            DeprecatedString namespace_uri;
+            if (token.is(Token::Type::String)) {
+                namespace_uri = token.token().string();
+            } else if (auto url = parse_url_function(token, AllowedDataUrlType::None); url.has_value()) {
+                namespace_uri = url.value().to_deprecated_string();
+            } else {
+                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @namespace rule invalid; discarding.");
+                return {};
+            }
+
+            token_stream.skip_whitespace();
+            if (token_stream.has_next_token()) {
+                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @namespace rule invalid; discarding.");
+                return {};
+            }
+
+            return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri).release_value_but_fixme_should_propagate_errors();
+        }
 
         // FIXME: More at rules!
         dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", rule->at_rule_name());

+ 25 - 3
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -220,19 +220,40 @@ StyleComputer::RuleCache const& StyleComputer::rule_cache_for_cascade_origin(Cas
     }
 }
 
+Vector<MatchingRule> StyleComputer::filter_namespace_rules(DOM::Element const& element, Vector<MatchingRule> const& rules) const
+{
+    Vector<MatchingRule> filtered_rules;
+
+    for (auto const& rule : rules) {
+        auto namespace_uri = rule.sheet->namespace_filter();
+        if (namespace_uri.has_value()) {
+            if (namespace_uri.value() == element.namespace_uri())
+                filtered_rules.append(rule);
+        } else {
+            filtered_rules.append(rule);
+        }
+    }
+
+    // FIXME: Filter out non-default namespace using prefixes
+
+    return filtered_rules;
+}
+
 Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement> pseudo_element) const
 {
     auto const& rule_cache = rule_cache_for_cascade_origin(cascade_origin);
 
     Vector<MatchingRule> rules_to_run;
     auto add_rules_to_run = [&](Vector<MatchingRule> const& rules) {
+        Vector<MatchingRule> namespace_filtered_rules = filter_namespace_rules(element, rules);
+
         if (pseudo_element.has_value()) {
-            for (auto& rule : rules) {
+            for (auto& rule : namespace_filtered_rules) {
                 if (rule.contains_pseudo_element)
                     rules_to_run.append(rule);
             }
         } else {
-            rules_to_run.extend(rules);
+            rules_to_run.extend(namespace_filtered_rules);
         }
     };
 
@@ -2556,10 +2577,11 @@ NonnullOwnPtr<StyleComputer::RuleCache> StyleComputer::make_rule_cache_for_casca
             for (CSS::Selector const& selector : rule.selectors()) {
                 MatchingRule matching_rule {
                     &rule,
+                    sheet,
                     style_sheet_index,
                     rule_index,
                     selector_index,
-                    selector.specificity(),
+                    selector.specificity()
                 };
 
                 for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) {

+ 3 - 0
Userland/Libraries/LibWeb/CSS/StyleComputer.h

@@ -24,6 +24,7 @@ namespace Web::CSS {
 
 struct MatchingRule {
     JS::GCPtr<CSSStyleRule const> rule;
+    JS::GCPtr<CSSStyleSheet const> sheet;
     size_t style_sheet_index { 0 };
     size_t rule_index { 0 };
     size_t selector_index { 0 };
@@ -173,6 +174,8 @@ private:
     void build_rule_cache();
     void build_rule_cache_if_needed() const;
 
+    Vector<MatchingRule> filter_namespace_rules(DOM::Element const&, Vector<MatchingRule> const&) const;
+
     JS::NonnullGCPtr<DOM::Document> m_document;
 
     struct AnimationKeyFrameSet {

+ 13 - 0
Userland/Libraries/LibWeb/Dump.cpp

@@ -679,6 +679,9 @@ ErrorOr<void> dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int in
     case CSS::CSSRule::Type::Keyframe:
     case CSS::CSSRule::Type::Keyframes:
         break;
+    case CSS::CSSRule::Type::Namespace:
+        TRY(dump_namespace_rule(builder, verify_cast<CSS::CSSNamespaceRule const>(rule), indent_levels));
+        break;
     }
     return {};
 }
@@ -835,4 +838,14 @@ void dump_tree(StringBuilder& builder, Painting::Paintable const& paintable, boo
     }
 }
 
+ErrorOr<void> dump_namespace_rule(StringBuilder& builder, CSS::CSSNamespaceRule const& namespace_, int indent_levels)
+{
+    indent(builder, indent_levels);
+    TRY(builder.try_appendff("  Namespace: {}\n", namespace_.namespace_uri()));
+    if (!namespace_.prefix().is_null() && !namespace_.prefix().is_empty())
+        TRY(builder.try_appendff("  Prefix: {}\n", namespace_.prefix()));
+
+    return {};
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/Dump.h

@@ -8,6 +8,7 @@
 #pragma once
 
 #include <AK/Forward.h>
+#include <LibWeb/CSS/CSSNamespaceRule.h>
 #include <LibWeb/Forward.h>
 
 namespace Web {
@@ -27,6 +28,7 @@ void dump_import_rule(StringBuilder&, CSS::CSSImportRule const&, int indent_leve
 ErrorOr<void> dump_media_rule(StringBuilder&, CSS::CSSMediaRule const&, int indent_levels = 0);
 ErrorOr<void> dump_style_rule(StringBuilder&, CSS::CSSStyleRule const&, int indent_levels = 0);
 ErrorOr<void> dump_supports_rule(StringBuilder&, CSS::CSSSupportsRule const&, int indent_levels = 0);
+ErrorOr<void> dump_namespace_rule(StringBuilder&, CSS::CSSNamespaceRule const&, int indent_levels = 0);
 void dump_selector(StringBuilder&, CSS::Selector const&);
 void dump_selector(CSS::Selector const&);
 

+ 1 - 0
Userland/Libraries/LibWeb/idl_files.cmake

@@ -11,6 +11,7 @@ libweb_js_bindings(CSS/CSSKeyframeRule)
 libweb_js_bindings(CSS/CSSKeyframesRule)
 libweb_js_bindings(CSS/CSSMediaRule)
 libweb_js_bindings(CSS/CSS NAMESPACE)
+libweb_js_bindings(CSS/CSSNamespaceRule)
 libweb_js_bindings(CSS/CSSRule)
 libweb_js_bindings(CSS/CSSRuleList)
 libweb_js_bindings(CSS/CSSStyleDeclaration)