mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 09:27:35 +00: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
18 changed files with 266 additions and 3 deletions
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue