mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-25 20:22:18 +03:00
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.
This commit is contained in:
parent
3f7d97f098
commit
60e35f2a97
Notes:
sideshowbarker
2024-07-18 05:01:22 +09:00
Author: https://github.com/Epigenetic Commit: https://github.com/SerenityOS/serenity/commit/60e35f2a97 Pull-request: https://github.com/SerenityOS/serenity/pull/20257 Reviewed-by: https://github.com/AtkinsSJ ✅
13
Tests/LibWeb/Layout/expected/css-namespace-rule-matches.txt
Normal file
13
Tests/LibWeb/Layout/expected/css-namespace-rule-matches.txt
Normal file
@ -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
Tests/LibWeb/Layout/expected/css-namespace-rule-no-match.txt
Normal file
13
Tests/LibWeb/Layout/expected/css-namespace-rule-no-match.txt
Normal file
@ -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
Tests/LibWeb/Layout/input/css-namespace-rule-matches.html
Normal file
19
Tests/LibWeb/Layout/input/css-namespace-rule-matches.html
Normal file
@ -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
Tests/LibWeb/Layout/input/css-namespace-rule-no-match.html
Normal file
19
Tests/LibWeb/Layout/input/css-namespace-rule-no-match.html
Normal file
@ -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>
|
@ -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
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.cpp
Normal file
59
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.cpp
Normal file
@ -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
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.h
Normal file
37
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.h
Normal file
@ -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
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.idl
Normal file
8
Userland/Libraries/LibWeb/CSS/CSSNamespaceRule.idl
Normal file
@ -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;
|
||||
};
|
@ -29,6 +29,7 @@ public:
|
||||
FontFace = 5,
|
||||
Keyframes = 7,
|
||||
Keyframe = 8,
|
||||
Namespace = 10,
|
||||
Supports = 12,
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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&);
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user