Skip to content

Commit 88134d4

Browse files
authored
rules: Avoid tokenizing too much when nesting declarations that look like selectors. (#359)
This speeds up the test-case in Mozilla bug 1851814 to be ~5s to ~300ms on a local opt build. [1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1851814
1 parent d5631d8 commit 88134d4

File tree

2 files changed

+50
-9
lines changed

2 files changed

+50
-9
lines changed

src/parser.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,28 @@ impl ParserState {
3939
}
4040
}
4141

42+
/// When parsing until a given token, sometimes the caller knows that parsing is going to restart
43+
/// at some earlier point, and consuming until we find a top level delimiter is just wasted work.
44+
///
45+
/// In that case, callers can pass ParseUntilErrorBehavior::Stop to avoid doing all that wasted
46+
/// work.
47+
///
48+
/// This is important for things like CSS nesting, where something like:
49+
///
50+
/// foo:is(..) {
51+
/// ...
52+
/// }
53+
///
54+
/// Would need to scan the whole {} block to find a semicolon, only for parsing getting restarted
55+
/// as a qualified rule later.
56+
#[derive(Clone, Copy, Debug, PartialEq)]
57+
pub enum ParseUntilErrorBehavior {
58+
/// Consume until we see the relevant delimiter or the end of the stream.
59+
Consume,
60+
/// Eagerly error.
61+
Stop,
62+
}
63+
4264
/// Details about a `BasicParseError`
4365
#[derive(Clone, Debug, PartialEq)]
4466
pub enum BasicParseErrorKind<'i> {
@@ -766,7 +788,7 @@ impl<'i: 't, 't> Parser<'i, 't> {
766788
where
767789
F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
768790
{
769-
parse_until_before(self, delimiters, parse)
791+
parse_until_before(self, delimiters, ParseUntilErrorBehavior::Consume, parse)
770792
}
771793

772794
/// Like `parse_until_before`, but also consume the delimiter token.
@@ -783,7 +805,7 @@ impl<'i: 't, 't> Parser<'i, 't> {
783805
where
784806
F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
785807
{
786-
parse_until_after(self, delimiters, parse)
808+
parse_until_after(self, delimiters, ParseUntilErrorBehavior::Consume, parse)
787809
}
788810

789811
/// Parse a <whitespace-token> and return its value.
@@ -1013,6 +1035,7 @@ impl<'i: 't, 't> Parser<'i, 't> {
10131035
pub fn parse_until_before<'i: 't, 't, F, T, E>(
10141036
parser: &mut Parser<'i, 't>,
10151037
delimiters: Delimiters,
1038+
error_behavior: ParseUntilErrorBehavior,
10161039
parse: F,
10171040
) -> Result<T, ParseError<'i, E>>
10181041
where
@@ -1028,6 +1051,9 @@ where
10281051
stop_before: delimiters,
10291052
};
10301053
result = delimited_parser.parse_entirely(parse);
1054+
if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() {
1055+
return result;
1056+
}
10311057
if let Some(block_type) = delimited_parser.at_start_of {
10321058
consume_until_end_of_block(block_type, &mut delimited_parser.input.tokenizer);
10331059
}
@@ -1051,12 +1077,16 @@ where
10511077
pub fn parse_until_after<'i: 't, 't, F, T, E>(
10521078
parser: &mut Parser<'i, 't>,
10531079
delimiters: Delimiters,
1080+
error_behavior: ParseUntilErrorBehavior,
10541081
parse: F,
10551082
) -> Result<T, ParseError<'i, E>>
10561083
where
10571084
F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
10581085
{
1059-
let result = parser.parse_until_before(delimiters, parse);
1086+
let result = parse_until_before(parser, delimiters, error_behavior, parse);
1087+
if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() {
1088+
return result;
1089+
}
10601090
let next_byte = parser.input.tokenizer.next_byte();
10611091
if next_byte.is_some()
10621092
&& !parser

src/rules_and_declarations.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use super::{
88
BasicParseError, BasicParseErrorKind, Delimiter, Delimiters, ParseError, Parser, Token,
99
};
1010
use crate::cow_rc_str::CowRcStr;
11-
use crate::parser::{parse_nested_block, parse_until_after, parse_until_before, ParserState};
11+
use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavior, ParserState};
1212

1313
/// Parse `!important`.
1414
///
@@ -266,14 +266,25 @@ where
266266
// that in a slightly more straight-forward way
267267
Token::Ident(ref name) if self.parser.parse_declarations() => {
268268
let name = name.clone();
269+
let parse_qualified = self.parser.parse_qualified();
269270
let result = {
271+
let error_behavior = if parse_qualified {
272+
ParseUntilErrorBehavior::Stop
273+
} else {
274+
ParseUntilErrorBehavior::Consume
275+
};
270276
let parser = &mut self.parser;
271-
parse_until_after(self.input, Delimiter::Semicolon, |input| {
272-
input.expect_colon()?;
273-
parser.parse_value(name, input)
274-
})
277+
parse_until_after(
278+
self.input,
279+
Delimiter::Semicolon,
280+
error_behavior,
281+
|input| {
282+
input.expect_colon()?;
283+
parser.parse_value(name, input)
284+
},
285+
)
275286
};
276-
if result.is_err() && self.parser.parse_qualified() {
287+
if result.is_err() && parse_qualified {
277288
self.input.reset(&start);
278289
// We ignore the resulting error here. The property declaration parse error
279290
// is likely to be more relevant.

0 commit comments

Comments
 (0)