1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 16:37:35 +00:00

LibWeb: Allow whitespace when parsing "!important" in CSS

This accounts for cases like:
```css
.foo {
    color: blue ! important ;
}
```

That's rare now that minifying is so popular, but does appear on some
websites.

I've added spec comments to `consume_a_declaration()` while I was at it.
This commit is contained in:
Sam Atkins 2021-10-28 14:09:56 +01:00 committed by Andreas Kling
parent 6f1debaab0
commit 52a1be5eae

View file

@ -1349,9 +1349,16 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration()
return consume_a_declaration(m_token_stream); return consume_a_declaration(m_token_stream);
} }
// https://www.w3.org/TR/css-syntax-3/#consume-declaration
template<typename T> template<typename T>
Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tokens) Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tokens)
{ {
// Note: This algorithm assumes that the next input token has already been checked to
// be an <ident-token>.
// To consume a declaration:
// Consume the next input token.
tokens.skip_whitespace(); tokens.skip_whitespace();
auto start_position = tokens.position(); auto start_position = tokens.position();
auto& token = tokens.next_token(); auto& token = tokens.next_token();
@ -1361,20 +1368,30 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tok
return {}; return {};
} }
// Create a new declaration with its name set to the value of the current input token
// and its value initially set to the empty list.
StyleDeclarationRule declaration; StyleDeclarationRule declaration;
declaration.m_name = ((Token)token).ident(); declaration.m_name = ((Token)token).ident();
// 1. While the next input token is a <whitespace-token>, consume the next input token.
tokens.skip_whitespace(); tokens.skip_whitespace();
auto& maybe_colon = tokens.next_token(); // 2. If the next input token is anything other than a <colon-token>, this is a parse error.
// Return nothing.
auto& maybe_colon = tokens.peek_token();
if (!maybe_colon.is(Token::Type::Colon)) { if (!maybe_colon.is(Token::Type::Colon)) {
log_parse_error(); log_parse_error();
tokens.rewind_to_position(start_position); tokens.rewind_to_position(start_position);
return {}; return {};
} }
// Otherwise, consume the next input token.
tokens.next_token();
// 3. While the next input token is a <whitespace-token>, consume the next input token.
tokens.skip_whitespace(); tokens.skip_whitespace();
// 4. As long as the next input token is anything other than an <EOF-token>, consume a
// component value and append it to the declarations value.
for (;;) { for (;;) {
if (tokens.peek_token().is(Token::Type::EndOfFile)) { if (tokens.peek_token().is(Token::Type::EndOfFile)) {
break; break;
@ -1382,24 +1399,47 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tok
declaration.m_values.append(consume_a_component_value(tokens)); declaration.m_values.append(consume_a_component_value(tokens));
} }
// 5. If the last two non-<whitespace-token>s in the declarations value are a <delim-token>
// with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive
// match for "important", remove them from the declarations value and set the declarations
// important flag to true.
if (declaration.m_values.size() >= 2) { if (declaration.m_values.size() >= 2) {
auto second_last = declaration.m_values.at(declaration.m_values.size() - 2); // Walk backwards from the end until we find "important"
auto last = declaration.m_values.at(declaration.m_values.size() - 1); Optional<size_t> important_index;
for (size_t i = declaration.m_values.size() - 1; i > 0; i--) {
auto value = declaration.m_values[i];
if (value.is(Token::Type::Ident) && value.token().ident().equals_ignoring_case("important")) {
important_index = i;
break;
}
if (value.is(Token::Type::Whitespace))
continue;
break;
}
if (second_last.m_type == StyleComponentValueRule::ComponentType::Token && last.m_type == StyleComponentValueRule::ComponentType::Token) { // Walk backwards from important until we find "!"
auto last_token = last.m_token; if (important_index.has_value()) {
auto second_last_token = second_last.m_token; Optional<size_t> bang_index;
for (size_t i = important_index.value() - 1; i > 0; i--) {
if (second_last_token.is(Token::Type::Delim) && second_last_token.m_value.to_string().equals_ignoring_case("!")) { auto value = declaration.m_values[i];
if (last_token.is(Token::Type::Ident) && last_token.m_value.to_string().equals_ignoring_case("important")) { if (value.is(Token::Type::Delim) && value.token().delim() == "!"sv) {
declaration.m_values.remove(declaration.m_values.size() - 2); bang_index = i;
declaration.m_values.remove(declaration.m_values.size() - 1); break;
declaration.m_important = true;
} }
if (value.is(Token::Type::Whitespace))
continue;
break;
}
if (bang_index.has_value()) {
declaration.m_values.remove(important_index.value());
declaration.m_values.remove(bang_index.value());
declaration.m_important = true;
} }
} }
} }
// 6. While the last token in the declarations value is a <whitespace-token>, remove that token.
while (!declaration.m_values.is_empty()) { while (!declaration.m_values.is_empty()) {
auto maybe_whitespace = declaration.m_values.last(); auto maybe_whitespace = declaration.m_values.last();
if (!(maybe_whitespace.is(Token::Type::Whitespace))) { if (!(maybe_whitespace.is(Token::Type::Whitespace))) {
@ -1408,6 +1448,7 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tok
declaration.m_values.take_last(); declaration.m_values.take_last();
} }
// 7. Return the declaration.
return declaration; return declaration;
} }