Skip to content

Reuse the detected browser for bot detection instead of parsing the user agent twice - #563

Open
czj wants to merge 1 commit into
fnando:mainfrom
czj:reuse-browser-for-bot-detection
Open

czj wants to merge 1 commit into
fnando:mainfrom
czj:reuse-browser-for-bot-detection

Conversation

@czj

@czj czj commented Oct 25, 2026

Copy link
Copy Markdown

Hi Nando! We call Browser.new on every request of our shop to read the name, version and platform, and to skip bots, so I took a look at what it allocates.

Most of it came from Browser::Base#bot: Bot.new(ua) builds a second Browser from the downcased user agent, which instantiates up to 30 browser matchers again just to hand that instance to the bot matchers. None of the built-in matchers read it (they all take _browser), and since detection runs on a downcased string, custom matchers always received a Browser::Unknown. This PR passes the browser that is already detected: Bot.new(ua, browser: self). Browser::Bot.new(ua) called on its own keeps working as before.

The second change is smaller: validate_size built a :"#{subject}_size_limit" symbol on every call, which allocates a string per matcher tried. The callers now pass the limit directly.

Allocated objects per call, Ruby 4.0.9 arm64:

Chrome desktop iPhone Safari Googlebot
Browser.new 59 → 31 76 → 47 63 → 33
Browser.new(ua).bot? 123 → 33 140 → 49 127 → 35
name, version, platform.name and bot? 165 → 75 153 → 62 151 → 59

With benchmark-ips, Browser.new(ua).bot? goes from 18.4k to 24.3k i/s (+32%).

One behavior change, noted in the changelog: custom bot matchers now receive the real browser instead of an Unknown one. That is what the README's ->(ua, browser) signature suggests, and a new test covers it. RuboCop reports the same 6 offenses as on main, none in the changed lines.

By the way, the test suite no longer boots on main with Rails 8.1 (delegate_missing_to is undefined). I ran it with RUBYOPT="-rlogger -ractive_support -ractive_support/core_ext/module/delegation", and I'm happy to send a separate fix if that helps.


🤖 Generated with Claude Code, but prompted, read, and verified by @czj, a caring human :)

Browser::Base#bot built a second Browser from the downcased user agent,
which runs every browser matcher again only to hand an Unknown instance
to the bot matchers. It now passes itself. validate_size also receives
its limit from the caller instead of interpolating a method name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant