瀏覽代碼

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.
Sam Atkins 3 年之前
父節點
當前提交
52a1be5eae
共有 1 個文件被更改,包括 54 次插入13 次删除
  1. 54 13
      Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

+ 54 - 13
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;
 }