diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2021-10-28 14:09:56 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-10-28 16:05:48 +0200 |
commit | 52a1be5eae81bd2eea767cfce569e089ec1bb3df (patch) | |
tree | af489900cd9ec6d448d44eebdbfa8daa8abe43b7 /Userland | |
parent | 6f1debaab0884956f3479b62ed1957f9a488c238 (diff) | |
download | serenity-52a1be5eae81bd2eea767cfce569e089ec1bb3df.zip |
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.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 67 |
1 files changed, 54 insertions, 13 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index e7565bfa0a..09c3189d4a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -1349,9 +1349,16 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration() return consume_a_declaration(m_token_stream); } +// https://www.w3.org/TR/css-syntax-3/#consume-declaration template<typename T> 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(); auto start_position = tokens.position(); auto& token = tokens.next_token(); @@ -1361,20 +1368,30 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tok 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; declaration.m_name = ((Token)token).ident(); + // 1. While the next input token is a <whitespace-token>, consume the next input token. 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)) { log_parse_error(); tokens.rewind_to_position(start_position); 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(); + // 4. As long as the next input token is anything other than an <EOF-token>, consume a + // component value and append it to the declarationâs value. for (;;) { if (tokens.peek_token().is(Token::Type::EndOfFile)) { break; @@ -1382,24 +1399,47 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tok declaration.m_values.append(consume_a_component_value(tokens)); } + // 5. If the last two non-<whitespace-token>s in the declarationâs 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 declarationâs value and set the declarationâs + // important flag to true. if (declaration.m_values.size() >= 2) { - auto second_last = declaration.m_values.at(declaration.m_values.size() - 2); - auto last = declaration.m_values.at(declaration.m_values.size() - 1); - - if (second_last.m_type == StyleComponentValueRule::ComponentType::Token && last.m_type == StyleComponentValueRule::ComponentType::Token) { - auto last_token = last.m_token; - auto second_last_token = second_last.m_token; - - if (second_last_token.is(Token::Type::Delim) && second_last_token.m_value.to_string().equals_ignoring_case("!")) { - if (last_token.is(Token::Type::Ident) && last_token.m_value.to_string().equals_ignoring_case("important")) { - declaration.m_values.remove(declaration.m_values.size() - 2); - declaration.m_values.remove(declaration.m_values.size() - 1); - declaration.m_important = true; + // Walk backwards from the end until we find "important" + 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; + } + + // Walk backwards from important until we find "!" + if (important_index.has_value()) { + Optional<size_t> bang_index; + for (size_t i = important_index.value() - 1; i > 0; i--) { + auto value = declaration.m_values[i]; + if (value.is(Token::Type::Delim) && value.token().delim() == "!"sv) { + bang_index = i; + break; } + 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 declarationâs value is a <whitespace-token>, remove that token. while (!declaration.m_values.is_empty()) { auto maybe_whitespace = declaration.m_values.last(); 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(); } + // 7. Return the declaration. return declaration; } |