mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 14:47:44 +00:00
LibWeb: Add basic support for the attr() CSS function
CSS Values and Units Module Level 5 defines attr as: `attr(<q-name> <attr-type>?, <declaration-value>?)` This implementation does not contain support for the type argument, effectively supporting `attr(<q-name>, <declaration-value>?)`
This commit is contained in:
parent
ba6ba67fa0
commit
6437f5da36
4 changed files with 56 additions and 19 deletions
|
@ -4294,9 +4294,9 @@ RefPtr<StyleValue> Parser::parse_as_css_value(PropertyID property_id)
|
||||||
|
|
||||||
Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value(PropertyID property_id, TokenStream<StyleComponentValueRule>& tokens)
|
Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value(PropertyID property_id, TokenStream<StyleComponentValueRule>& tokens)
|
||||||
{
|
{
|
||||||
auto block_contains_var = [](StyleBlockRule const& block, auto&& recurse) -> bool {
|
auto block_contains_var_or_attr = [](StyleBlockRule const& block, auto&& recurse) -> bool {
|
||||||
for (auto const& token : block.values()) {
|
for (auto const& token : block.values()) {
|
||||||
if (token.is_function() && token.function().name().equals_ignoring_case("var"sv))
|
if (token.is_function() && (token.function().name().equals_ignoring_case("var"sv) || token.function().name().equals_ignoring_case("attr"sv)))
|
||||||
return true;
|
return true;
|
||||||
if (token.is_block() && recurse(token.block(), recurse))
|
if (token.is_block() && recurse(token.block(), recurse))
|
||||||
return true;
|
return true;
|
||||||
|
@ -4306,7 +4306,7 @@ Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value
|
||||||
|
|
||||||
m_context.set_current_property_id(property_id);
|
m_context.set_current_property_id(property_id);
|
||||||
Vector<StyleComponentValueRule> component_values;
|
Vector<StyleComponentValueRule> component_values;
|
||||||
bool contains_var = false;
|
bool contains_var_or_attr = false;
|
||||||
|
|
||||||
while (tokens.has_next_token()) {
|
while (tokens.has_next_token()) {
|
||||||
auto& token = tokens.next_token();
|
auto& token = tokens.next_token();
|
||||||
|
@ -4324,18 +4324,18 @@ Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value
|
||||||
return ParsingResult::IncludesIgnoredVendorPrefix;
|
return ParsingResult::IncludesIgnoredVendorPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contains_var) {
|
if (!contains_var_or_attr) {
|
||||||
if (token.is_function() && token.function().name().equals_ignoring_case("var"sv))
|
if (token.is_function() && (token.function().name().equals_ignoring_case("var"sv) || token.function().name().equals_ignoring_case("attr"sv)))
|
||||||
contains_var = true;
|
contains_var_or_attr = true;
|
||||||
else if (token.is_block() && block_contains_var(token.block(), block_contains_var))
|
else if (token.is_block() && block_contains_var_or_attr(token.block(), block_contains_var_or_attr))
|
||||||
contains_var = true;
|
contains_var_or_attr = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
component_values.append(token);
|
component_values.append(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property_id == PropertyID::Custom || contains_var)
|
if (property_id == PropertyID::Custom || contains_var_or_attr)
|
||||||
return { UnresolvedStyleValue::create(move(component_values), contains_var) };
|
return { UnresolvedStyleValue::create(move(component_values), contains_var_or_attr) };
|
||||||
|
|
||||||
if (component_values.is_empty())
|
if (component_values.is_empty())
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
|
|
|
@ -151,6 +151,14 @@ public:
|
||||||
Position const& start_position() const { return m_start_position; }
|
Position const& start_position() const { return m_start_position; }
|
||||||
Position const& end_position() const { return m_end_position; }
|
Position const& end_position() const { return m_end_position; }
|
||||||
|
|
||||||
|
static Token of_string(FlyString str)
|
||||||
|
{
|
||||||
|
Token token;
|
||||||
|
token.m_type = Type::String;
|
||||||
|
token.m_value = move(str);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Type m_type { Type::Invalid };
|
Type m_type { Type::Invalid };
|
||||||
|
|
||||||
|
|
|
@ -495,7 +495,7 @@ static RefPtr<StyleValue> get_custom_property(DOM::Element const& element, FlySt
|
||||||
bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index) const
|
bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index) const
|
||||||
{
|
{
|
||||||
// FIXME: Do this better!
|
// FIXME: Do this better!
|
||||||
// We build a copy of the tree of StyleComponentValueRules, with all var()s replaced with their contents.
|
// We build a copy of the tree of StyleComponentValueRules, with all var()s and attr()s replaced with their contents.
|
||||||
// This is a very naive solution, and we could do better if the CSS Parser could accept tokens one at a time.
|
// This is a very naive solution, and we could do better if the CSS Parser could accept tokens one at a time.
|
||||||
|
|
||||||
// Arbitrary large value chosen to avoid the billion-laughs attack.
|
// Arbitrary large value chosen to avoid the billion-laughs attack.
|
||||||
|
@ -553,6 +553,35 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
|
||||||
return false;
|
return false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
} else if (value.function().name().equals_ignoring_case("attr"sv)) {
|
||||||
|
// https://drafts.csswg.org/css-values-5/#attr-substitution
|
||||||
|
auto const& attr_contents = value.function().values();
|
||||||
|
if (attr_contents.is_empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto const& attr_name_token = attr_contents.first();
|
||||||
|
if (!attr_name_token.is(Token::Type::Ident))
|
||||||
|
return false;
|
||||||
|
auto attr_name = attr_name_token.token().ident();
|
||||||
|
|
||||||
|
auto attr_value = element.get_attribute(attr_name);
|
||||||
|
// 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
|
||||||
|
if (!attr_value.is_null()) {
|
||||||
|
// FIXME: attr() should also accept an optional type argument, not just strings.
|
||||||
|
dest.empend(Token::of_string(attr_value));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Otherwise, if the attr() function has a fallback value as its last argument, replace the attr() function by the fallback value.
|
||||||
|
// If there are any var() or attr() references in the fallback, substitute them as well.
|
||||||
|
if (attr_contents.size() > 2 && attr_contents[1].is(Token::Type::Comma)) {
|
||||||
|
if (!expand_unresolved_values(element, property_name, dependencies, attr_contents, dest, 2))
|
||||||
|
return false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Otherwise, the property containing the attr() function is invalid at computed-value time.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const& source_function = value.function();
|
auto const& source_function = value.function();
|
||||||
|
@ -580,9 +609,9 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
|
||||||
|
|
||||||
RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const
|
RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const
|
||||||
{
|
{
|
||||||
// Unresolved always contains a var(), unless it is a custom property's value, in which case we shouldn't be trying
|
// Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
|
||||||
// to produce a different StyleValue from it.
|
// to produce a different StyleValue from it.
|
||||||
VERIFY(unresolved.contains_var());
|
VERIFY(unresolved.contains_var_or_attr());
|
||||||
|
|
||||||
Vector<StyleComponentValueRule> expanded_values;
|
Vector<StyleComponentValueRule> expanded_values;
|
||||||
HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
|
HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
|
||||||
|
|
|
@ -1625,27 +1625,27 @@ private:
|
||||||
|
|
||||||
class UnresolvedStyleValue final : public StyleValue {
|
class UnresolvedStyleValue final : public StyleValue {
|
||||||
public:
|
public:
|
||||||
static NonnullRefPtr<UnresolvedStyleValue> create(Vector<StyleComponentValueRule>&& values, bool contains_var)
|
static NonnullRefPtr<UnresolvedStyleValue> create(Vector<StyleComponentValueRule>&& values, bool contains_var_or_attr)
|
||||||
{
|
{
|
||||||
return adopt_ref(*new UnresolvedStyleValue(move(values), contains_var));
|
return adopt_ref(*new UnresolvedStyleValue(move(values), contains_var_or_attr));
|
||||||
}
|
}
|
||||||
virtual ~UnresolvedStyleValue() override = default;
|
virtual ~UnresolvedStyleValue() override = default;
|
||||||
|
|
||||||
virtual String to_string() const override;
|
virtual String to_string() const override;
|
||||||
|
|
||||||
Vector<StyleComponentValueRule> const& values() const { return m_values; }
|
Vector<StyleComponentValueRule> const& values() const { return m_values; }
|
||||||
bool contains_var() const { return m_contains_var; }
|
bool contains_var_or_attr() const { return m_contains_var_or_attr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UnresolvedStyleValue(Vector<StyleComponentValueRule>&& values, bool contains_var)
|
UnresolvedStyleValue(Vector<StyleComponentValueRule>&& values, bool contains_var_or_attr)
|
||||||
: StyleValue(Type::Unresolved)
|
: StyleValue(Type::Unresolved)
|
||||||
, m_values(move(values))
|
, m_values(move(values))
|
||||||
, m_contains_var(contains_var)
|
, m_contains_var_or_attr(contains_var_or_attr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<StyleComponentValueRule> m_values;
|
Vector<StyleComponentValueRule> m_values;
|
||||||
bool m_contains_var { false };
|
bool m_contains_var_or_attr { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnsetStyleValue final : public StyleValue {
|
class UnsetStyleValue final : public StyleValue {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue