mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
LibWeb: Skip declaration parsing in block contents when not applicable
When parsing block contents, the CSS parser speculatively tries to parse each item as a declaration first. If that fails, it restores the token position and tries again as a qualified rule. This means every qualified rule inside an at-rule block (e.g. @layer, @media) gets parsed twice: once as a failed declaration (which consumes all tokens via consume_the_remnants_of_a_bad_declaration), and then again successfully as a rule. Add a lookahead that checks for the `ident whitespace* ':'` pattern before attempting declaration parsing. Since declarations must start with this pattern per spec, we can skip the attempt entirely when it doesn't match and go straight to qualified rule parsing. This is a massive win on large Tailwind CSS stylesheets (like the one used by chatgpt.com) where thousands of rules inside @layer blocks were being double-parsed. On a 1.2MB Tailwind v4 stylesheet, parse time goes from ~2000ms to ~95ms (21x speedup).
This commit is contained in:
committed by
Jelle Raaijmakers
parent
e17d797bdb
commit
a5db4c874e
Notes:
github-actions[bot]
2026-03-23 08:29:27 +00:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/a5db4c874e1 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8572 Reviewed-by: https://github.com/gmta ✅
@@ -729,43 +729,50 @@ Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<T
|
||||
|
||||
// anything else
|
||||
{
|
||||
// Mark input.
|
||||
input.mark();
|
||||
|
||||
// Consume a declaration from input, with nested set to true.
|
||||
// If a declaration was returned, append it to decls, and discard a mark from input.
|
||||
if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) {
|
||||
declarations.append(declaration.release_value());
|
||||
input.discard_a_mark();
|
||||
// OPTIMIZATION: Look ahead to determine if this can be a declaration (ident whitespace* ':').
|
||||
// If not, skip straight to qualified rule parsing, avoiding the expensive
|
||||
// mark/restore cycle and consume_the_remnants_of_a_bad_declaration.
|
||||
bool could_be_declaration = false;
|
||||
if (token.is(Token::Type::Ident)) {
|
||||
size_t lookahead = 1;
|
||||
while (input.peek_token(lookahead).is(Token::Type::Whitespace))
|
||||
++lookahead;
|
||||
could_be_declaration = input.peek_token(lookahead).is(Token::Type::Colon);
|
||||
}
|
||||
|
||||
// Otherwise, restore a mark from input, then consume a qualified rule from input,
|
||||
// with nested set to true, and <semicolon-token> as the stop token.
|
||||
else {
|
||||
input.restore_a_mark();
|
||||
consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit(
|
||||
// -> If nothing was returned
|
||||
[](Empty&) {
|
||||
// Do nothing
|
||||
},
|
||||
// -> If an invalid rule error was returned
|
||||
[&](InvalidRuleError&) {
|
||||
// If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations. (Otherwise, do nothing.)
|
||||
if (!declarations.is_empty()) {
|
||||
rules.append(move(declarations));
|
||||
declarations = {};
|
||||
}
|
||||
},
|
||||
// -> If a rule was returned
|
||||
[&](QualifiedRule rule) {
|
||||
// If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations.
|
||||
if (!declarations.is_empty()) {
|
||||
rules.append(move(declarations));
|
||||
declarations = {};
|
||||
}
|
||||
// Append the rule to rules.
|
||||
rules.append({ move(rule) });
|
||||
});
|
||||
auto flush_declarations = [&] {
|
||||
if (!declarations.is_empty()) {
|
||||
rules.append(move(declarations));
|
||||
declarations = {};
|
||||
}
|
||||
};
|
||||
|
||||
auto consume_qualified_rule = [&] {
|
||||
consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit([](Empty&) {}, [&](InvalidRuleError&) { flush_declarations(); }, [&](QualifiedRule rule) {
|
||||
flush_declarations();
|
||||
rules.append({ move(rule) }); });
|
||||
};
|
||||
|
||||
if (could_be_declaration) {
|
||||
// Mark input.
|
||||
input.mark();
|
||||
|
||||
// Consume a declaration from input, with nested set to true.
|
||||
// If a declaration was returned, append it to decls, and discard a mark from input.
|
||||
if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) {
|
||||
declarations.append(declaration.release_value());
|
||||
input.discard_a_mark();
|
||||
}
|
||||
|
||||
// Otherwise, restore a mark from input, then consume a qualified rule from input,
|
||||
// with nested set to true, and <semicolon-token> as the stop token.
|
||||
else {
|
||||
input.restore_a_mark();
|
||||
consume_qualified_rule();
|
||||
}
|
||||
} else {
|
||||
// Not a declaration, go straight to qualified rule parsing.
|
||||
consume_qualified_rule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user