Skip to content

Commit

Permalink
WIP: LibWeb/CSS: Support namespaced attributes in attr()
Browse files Browse the repository at this point in the history
Parse a qualified name, and use the namespace when looking up the
attribute. Also bring the spec comments up to date, as the syntax
description has changed a bit.

WIP: Waiting for LadybirdBrowser#3138
before the test will pass.
  • Loading branch information
AtkinsSJ committed Jan 4, 2025
1 parent d879771 commit c4c7e5b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 8 deletions.
46 changes: 38 additions & 8 deletions Libraries/LibWeb/CSS/Parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9649,42 +9649,72 @@ bool Parser::expand_unresolved_values(DOM::Element& element, FlyString const& pr
bool Parser::substitute_attr_function(DOM::Element& element, FlyString const& property_name, Function const& attr_function, Vector<ComponentValue>& dest)
{
// First, parse the arguments to attr():
// attr() = attr( <q-name> <attr-type>? , <declaration-value>?)
// <attr-type> = string | url | ident | color | number | percentage | length | angle | time | frequency | flex | <dimension-unit>
// attr() = attr( <attr-name> <attr-type>? , <declaration-value>?)
// https://drafts.csswg.org/css-values-5/#funcdef-attr

TokenStream attr_contents { attr_function.value };
attr_contents.discard_whitespace();
if (!attr_contents.has_next_token())
return false;

// - Attribute name
// FIXME: Support optional attribute namespace
if (!attr_contents.next_token().is(Token::Type::Ident))
// <attr-name> = [ <ident-token>? '|' ]? <ident-token>
Optional<FlyString> attribute_namespace;
FlyString attribute_name;

auto& start_of_name = attr_contents.consume_a_token();
if (start_of_name.is_delim('|')) {
// Namespace is blank
if (!attr_contents.next_token().is(Token::Type::Ident))
return false;
attribute_name = attr_contents.consume_a_token().token().ident();
} else if (start_of_name.is(Token::Type::Ident)) {
// We're assuming whitespace is not allowed inside the qualified name, which is the case inside selectors. But the spec doesn't say.
if (attr_contents.next_token().is_delim('|')) {
// Namespace | Name
attribute_namespace = start_of_name.token().ident();
attr_contents.discard_a_token(); // '|'

if (!attr_contents.next_token().is(Token::Type::Ident))
return false;
attribute_name = attr_contents.consume_a_token().token().ident();
} else {
// Just name
attribute_name = start_of_name.token().ident();
}
} else {
return false;
auto attribute_name = attr_contents.consume_a_token().token().ident();
}
attr_contents.discard_whitespace();

// - Attribute type (optional)
// <attr-type> = type( <syntax> ) | string | <attr-unit>
// The <attr-unit> production matches any identifier that is an ASCII case-insensitive match for the name of a CSS dimension unit, such as px, or the <delim-token> %.
// FIXME: Implement the type() function
auto attribute_type = "string"_fly_string;
if (attr_contents.next_token().is(Token::Type::Ident)) {
attribute_type = attr_contents.consume_a_token().token().ident();
attr_contents.discard_whitespace();
}

// - Comma, then fallback values (optional)
// FIXME: Properly parse a <declaration-value>. Currently we just use whatever is left in attr_contents, but some tokens are invalid there.
bool has_fallback_values = false;
if (attr_contents.has_next_token()) {
if (!attr_contents.next_token().is(Token::Type::Comma))
return false;
(void)attr_contents.consume_a_token(); // Comma
attr_contents.discard_a_token(); // Comma
has_fallback_values = true;
}

// Then, run the substitution algorithm:
// FIXME: The substitution algorithm has changed, update this to the current spec.
// https://drafts.csswg.org/css-values-5/#resolve-an-attr-function

// 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
// https://drafts.csswg.org/css-values-5/#attr-types
if (element.has_attribute(attribute_name)) {
auto attribute_value = element.get_attribute_value(attribute_name);
if (element.has_attribute_ns(attribute_namespace, attribute_name)) {
auto attribute_value = element.get_attribute_value(attribute_name, attribute_namespace);
if (attribute_type.equals_ignoring_ascii_case("angle"_fly_string)) {
// Parse a component value from the attribute’s value.
auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
Expand Down
22 changes: 22 additions & 0 deletions Tests/LibWeb/Ref/input/css-attr-namespaced.xht
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:foo="http://www.example.com">
<head>
<link rel="match" href="../expected/text-div.html"/>
<style>
@namespace example "http://www.example.com";
#target::before {
content: attr(example|bar);
}
#target::after {
content: attr(|baz);
}
</style>
</head>
<body>
<div id="target"
bar="FAIL"
foo:bar="Well, " baz=" friends!"
foo:baz="FAIL"
>hello</div>
</body>
</html>

0 comments on commit c4c7e5b

Please sign in to comment.