+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,61.828125) content-size 784x0 children: inline
+ TextNode <#text>
diff --git a/Tests/LibWeb/Layout/expected/css-namespace-rule-no-match.txt b/Tests/LibWeb/Layout/expected/css-namespace-rule-no-match.txt
new file mode 100644
index 00000000000..850327f8762
--- /dev/null
+++ b/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 at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer at (8,20) content-size 784x21.828125 children: not-inline
+ BlockContainer 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
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,61.828125) content-size 784x0 children: inline
+ TextNode <#text>
diff --git a/Tests/LibWeb/Layout/input/css-namespace-rule-matches.html b/Tests/LibWeb/Layout/input/css-namespace-rule-matches.html
new file mode 100644
index 00000000000..06441d75ff7
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/css-namespace-rule-matches.html
@@ -0,0 +1,19 @@
+
+
+
+ Should be green
+
diff --git a/Tests/LibWeb/Layout/input/css-namespace-rule-no-match.html b/Tests/LibWeb/Layout/input/css-namespace-rule-no-match.html
new file mode 100644
index 00000000000..5c49093a117
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/css-namespace-rule-no-match.html
@@ -0,0 +1,19 @@
+
+
+
+ Should be red
+
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index 77faf067bd1..3c8d65e836b 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/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
diff --git a/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.cpp b/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.cpp
new file mode 100644
index 00000000000..b3f8e3f81a8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023, Jonah Shafran
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::CSS {
+
+CSSNamespaceRule::CSSNamespaceRule(JS::Realm& realm, Optional prefix, StringView namespace_uri)
+ : CSSRule(realm)
+ , m_namespace_uri(namespace_uri)
+ , m_prefix(prefix.has_value() ? prefix.value() : ""sv)
+{
+}
+
+WebIDL::ExceptionOr> CSSNamespaceRule::create(JS::Realm& realm, Optional prefix, AK::StringView namespace_uri)
+{
+ return MUST_OR_THROW_OOM(realm.heap().allocate(realm, realm, prefix, namespace_uri));
+}
+
+JS::ThrowCompletionOr CSSNamespaceRule::initialize(JS::Realm& realm)
+{
+ MUST_OR_THROW_OOM(Base::initialize(realm));
+ set_prototype(&Bindings::ensure_web_prototype(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();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.h b/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.h
new file mode 100644
index 00000000000..4d77a2517e6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2023, Jonah Shafran
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+
+namespace Web::CSS {
+
+class CSSNamespaceRule final : public CSSRule {
+ WEB_PLATFORM_OBJECT(CSSNamespaceRule, CSSRule);
+
+public:
+ static WebIDL::ExceptionOr> create(JS::Realm&, Optional 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 prefix, StringView namespace_uri);
+
+ virtual JS::ThrowCompletionOr initialize(JS::Realm&) override;
+
+ virtual DeprecatedString serialized() const override;
+ DeprecatedString m_namespace_uri;
+ DeprecatedString m_prefix;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.idl b/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.idl
new file mode 100644
index 00000000000..d2ffeaad1c4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.idl
@@ -0,0 +1,8 @@
+#import
+
+// https://www.w3.org/TR/cssom/#the-cssnamespacerule-interface
+[Exposed=Window]
+interface CSSNamespaceRule : CSSRule {
+ readonly attribute CSSOMString namespaceURI;
+ readonly attribute CSSOMString prefix;
+};
diff --git a/Userland/Libraries/LibWeb/CSS/CSSRule.h b/Userland/Libraries/LibWeb/CSS/CSSRule.h
index db9ae0bcb7c..b9049ab37d8 100644
--- a/Userland/Libraries/LibWeb/CSS/CSSRule.h
+++ b/Userland/Libraries/LibWeb/CSS/CSSRule.h
@@ -29,6 +29,7 @@ public:
FontFace = 5,
Keyframes = 7,
Keyframe = 8,
+ Namespace = 10,
Supports = 12,
};
diff --git a/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp b/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp
index 60b9894a840..93a8dbdfdf8 100644
--- a/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp
+++ b/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp
@@ -144,6 +144,7 @@ void CSSRuleList::for_each_effective_style_rule(Function(*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;
}
}
diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp
index 7c09a573aa2..d3a29d41f90 100644
--- a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp
+++ b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp
@@ -6,6 +6,7 @@
#include
#include
+#include
#include
#include
#include
@@ -137,4 +138,17 @@ void CSSStyleSheet::set_style_sheet_list(Badge, StyleSheetList*
m_style_sheet_list = list;
}
+Optional CSSStyleSheet::namespace_filter() const
+{
+ for (JS::NonnullGCPtr rule : *m_rules) {
+ if (rule->type() == CSSRule::Type::Namespace) {
+ auto& namespace_rule = verify_cast(*rule);
+ if (!namespace_rule.namespace_uri().is_empty() && namespace_rule.prefix().is_empty())
+ return namespace_rule.namespace_uri().view();
+ }
+ }
+
+ return {};
+}
+
}
diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h
index f153b84da9b..c6d031f71e2 100644
--- a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h
+++ b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h
@@ -48,6 +48,8 @@ public:
void set_style_sheet_list(Badge, StyleSheetList*);
+ Optional namespace_filter() const;
+
private:
CSSStyleSheet(JS::Realm&, CSSRuleList&, MediaList&, Optional location);
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index 154af0e13ce..75eaa55598a 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -3241,6 +3242,37 @@ CSSRule* Parser::convert_to_rule(NonnullRefPtr 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 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());
diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
index 719df8e9b02..e951d292f4b 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
@@ -220,19 +220,40 @@ StyleComputer::RuleCache const& StyleComputer::rule_cache_for_cascade_origin(Cas
}
}
+Vector StyleComputer::filter_namespace_rules(DOM::Element const& element, Vector const& rules) const
+{
+ Vector 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 StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional pseudo_element) const
{
auto const& rule_cache = rule_cache_for_cascade_origin(cascade_origin);
Vector rules_to_run;
auto add_rules_to_run = [&](Vector const& rules) {
+ Vector 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::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) {
diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h
index 9b53e9e563d..9079e2d5c87 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h
@@ -24,6 +24,7 @@ namespace Web::CSS {
struct MatchingRule {
JS::GCPtr rule;
+ JS::GCPtr 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 filter_namespace_rules(DOM::Element const&, Vector const&) const;
+
JS::NonnullGCPtr m_document;
struct AnimationKeyFrameSet {
diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp
index 14ba654afac..0111ff993ae 100644
--- a/Userland/Libraries/LibWeb/Dump.cpp
+++ b/Userland/Libraries/LibWeb/Dump.cpp
@@ -679,6 +679,9 @@ ErrorOr 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(rule), indent_levels));
+ break;
}
return {};
}
@@ -835,4 +838,14 @@ void dump_tree(StringBuilder& builder, Painting::Paintable const& paintable, boo
}
}
+ErrorOr 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 {};
+}
+
}
diff --git a/Userland/Libraries/LibWeb/Dump.h b/Userland/Libraries/LibWeb/Dump.h
index 7a2387a1b7e..58f3035f313 100644
--- a/Userland/Libraries/LibWeb/Dump.h
+++ b/Userland/Libraries/LibWeb/Dump.h
@@ -8,6 +8,7 @@
#pragma once
#include
+#include
#include
namespace Web {
@@ -27,6 +28,7 @@ void dump_import_rule(StringBuilder&, CSS::CSSImportRule const&, int indent_leve
ErrorOr dump_media_rule(StringBuilder&, CSS::CSSMediaRule const&, int indent_levels = 0);
ErrorOr dump_style_rule(StringBuilder&, CSS::CSSStyleRule const&, int indent_levels = 0);
ErrorOr dump_supports_rule(StringBuilder&, CSS::CSSSupportsRule const&, int indent_levels = 0);
+ErrorOr dump_namespace_rule(StringBuilder&, CSS::CSSNamespaceRule const&, int indent_levels = 0);
void dump_selector(StringBuilder&, CSS::Selector const&);
void dump_selector(CSS::Selector const&);
diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake
index dad7ea04ba1..0c37ffbe405 100644
--- a/Userland/Libraries/LibWeb/idl_files.cmake
+++ b/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)