Skip to content

perf: split lookbehind patterns out of the partial-selector regex (WebKit 3–6× faster parse) - #440

Open
tarek101 wants to merge 1 commit into
kepano:mainfrom
tarek101:webkit-partial-selector-regex
Open

tarek101 wants to merge 1 commit into
kepano:mainfrom
tarek101:webkit-partial-selector-regex

Conversation

@tarek101

Copy link
Copy Markdown

Fixes #439.

JavaScriptCore (Safari, WKWebView) can't JIT-compile a regex that contains a lookbehind. Three PARTIAL_SELECTORS patterns have one, so the whole ~550-way PARTIAL_SELECTORS_REGEX ran in JSC's interpreter. In WebKit, removeBySelector took 150–1,900 ms per parse on long pages, against 3–5 ms in Chrome.

Change

  • compileAlternation() in src/constants.ts compiles the patterns without a lookbehind into one regex and those with one into another. It returns { test }, which is true when either matches. A string matches an alternation exactly when it matches one of its parts, so results don't change.
  • It applies to PARTIAL_SELECTORS_REGEX and PARTIAL_SELECTORS_ANCHORED_REGEX. Both are internal and only called with .test() (src/removals/selectors.ts).
  • tests/partial-selectors-regex.test.ts checks that both testers agree with the combined regex: on edge cases for each lookbehind pattern and on every literal token (1,109 cases).

Results

WKWebView (Safari 26.2 engine), defuddle full build on a cloneNode(true) copy, median of 3 runs. Output content is byte-identical before and after on all three pages.

Page removeBySelector before → after parse() before → after
Quanta Magazine article 153 → 13 ms 170 → 29 ms
Wikipedia, Moons of Jupiter 448 → 48 ms 540 → 158 ms
Wikipedia, Fourier transform 1,882 → 154 ms 2,200 → 620 ms

npm test passes: all library tests, including the new file. The only failures are the website test files, which need the website/ dependencies installed. npm run lint:unused and npm run build also pass.

🤖 Generated with Claude Code

JavaScriptCore (Safari, WKWebView) can't JIT-compile a regex that
contains a lookbehind, so the ~550-way PARTIAL_SELECTORS alternation ran
in its interpreter: removeBySelector took 150-1900 ms per parse in WebKit
on long pages, against 3-5 ms in Chrome.

Patterns with a lookbehind now get a regex of their own; a string matches
the alternation exactly when it matches one of the two parts. Applied to
PARTIAL_SELECTORS_REGEX and PARTIAL_SELECTORS_ANCHORED_REGEX (both only
call .test). A test checks both agree with the combined regex.

Fixes kepano#439

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

removeBySelector is 25×+ slower in Safari/WebKit: a lookbehind in PARTIAL_SELECTORS_REGEX disables JavaScriptCore's regex JIT

1 participant