Skip to content

fix(detect): auto-detect Zen/Floorp via getBrowserInfo (fixes #232)#233

Open
fanxing11 wants to merge 1 commit into
ActivityWatch:masterfrom
fanxing11:fix/detect-zen-firefox-fork
Open

fix(detect): auto-detect Zen/Floorp via getBrowserInfo (fixes #232)#233
fanxing11 wants to merge 1 commit into
ActivityWatch:masterfrom
fanxing11:fix/detect-zen-firefox-fork

Conversation

@fanxing11

Copy link
Copy Markdown

Fixes #232.

detectBrowser() only looks at navigator.userAgent, which is plain Firefox for Firefox forks (Zen, Floorp, LibreWolf, Waterfox, …) since that's how they run Firefox extensions unchanged. As a result, Zen users end up with a bucket named aw-watcher-web-firefox_<host>, but aw-webui's Browser view filters window events through the firefox regex (?i)(firefox|librewolf|waterfox|nightly). Zen's window-watcher app name doesn't match, so "Top Browser Domains/URLs" comes up empty even though the raw events show up in Timeline.

This refines the firefox branch with browser.runtime.getBrowserInfo() — a Firefox-only API that forks override to report their own name — and maps forks that have a dedicated entry in aw-webui's queries.ts (browser_appnames + browser_appname_regex) onto their own bucket: currently zen and floorp. Forks without a dedicated entry (librewolf, waterfox) fall through and keep the firefox bucket, which already covers them via the firefox regex.

getBrowserInfo is absent on Chromium and on Firefox < 51, so the lookup is guarded with a typeof check and a try/catch and falls back to 'firefox' on anything unexpected.

Notes:

  • Only touches the first-time auto-detect path (storedName short-circuits as before, so existing installs keep their current bucket — no migration, no data loss).
  • Users already on the firefox bucket can switch over via Settings → Browser → Zen (the dropdown entry already exists), or just reinstall the extension.
  • No new dependencies, no API changes outside helpers.ts. tsc --noEmit and vite build both clean.

…yWatch#232)

detectBrowser() only checks navigator.userAgent, which is plain Firefox
for Firefox forks (Zen, Floorp, LibreWolf, Waterfox, ...) since that's
how they run Firefox extensions unchanged. As a result, Zen users got
a bucket named aw-watcher-web-firefox_<host>, but the aw-webui Browser
view's firefox regex (firefox|librewolf|waterfox|nightly) does not match
zen's window-watcher app name, so 'Top Browser Domains/URLs' came up
empty even though Timeline showed the raw events.

Refine the 'firefox' branch with browser.runtime.getBrowserInfo() (a
Firefox-only API that forks override to report their own name) and map
known forks that have a dedicated entry in aw-webui queries.ts
(browser_appnames + browser_appname_regex) onto their bucket: zen,
floorp. Forks without a dedicated entry stay on the firefox bucket.

getBrowserInfo is absent on Chromium and on Firefox < 51, so we guard
the lookup with a typeof check and a try/catch and fall back to
'firefox' on any failure.
@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR refines Firefox-family browser detection by calling browser.runtime.getBrowserInfo() — a Firefox-only API that forks override with their own brand name — after a UA-based detection of 'firefox', mapping known forks (Zen, Floorp) to their own ActivityWatch buckets while unknown forks fall through to the generic firefox bucket.

  • Adds refineFirefoxFork() with proper guards: a typeof check for non-Firefox environments and a try/catch for unexpected failures, both falling back safely to 'firefox'.
  • Integrates cleanly into getBrowser() only on the first-run path — storedName still short-circuits, so existing users are unaffected.
  • Uses name.includes(key) substring matching for fork identification, which works for current entries but could produce false positives if a future fork's name happens to contain "zen" or "floorp" as a substring.

Confidence Score: 4/5

Safe to merge; the change is isolated to the one-time auto-detect path and falls back to 'firefox' on any unexpected condition.

The logic is well-guarded and the fallback path is correct. The one concern is the substring-based fork lookup: a false-positive match would write an incorrect bucket name to storage and stick permanently, though no currently-known Firefox fork triggers this.

src/background/helpers.ts — specifically the fork name matching logic in refineFirefoxFork()

Important Files Changed

Filename Overview
src/background/helpers.ts Adds refineFirefoxFork() to distinguish Zen/Floorp from plain Firefox via getBrowserInfo(); guarded against Chromium and old Firefox with a typeof check and try/catch; integrated into getBrowser() after the initial detectBrowser() call.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[getBrowser called] --> B{storedName exists?}
    B -- Yes --> C[return storedName]
    B -- No --> D[detectBrowser via userAgent]
    D --> E{browserName === 'firefox'?}
    E -- No --> H[use detected name]
    E -- Yes --> F[refineFirefoxFork]
    F --> G{getBrowserInfo available?}
    G -- No / throws --> I[return 'firefox']
    G -- Yes --> J[call getBrowserInfo]
    J --> K{info.name includes known fork key?}
    K -- Yes --> L[return 'zen' or 'floorp']
    K -- No match --> I
    L --> H
    I --> H
    H --> M[setBrowserName and return]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[getBrowser called] --> B{storedName exists?}
    B -- Yes --> C[return storedName]
    B -- No --> D[detectBrowser via userAgent]
    D --> E{browserName === 'firefox'?}
    E -- No --> H[use detected name]
    E -- Yes --> F[refineFirefoxFork]
    F --> G{getBrowserInfo available?}
    G -- No / throws --> I[return 'firefox']
    G -- Yes --> J[call getBrowserInfo]
    J --> K{info.name includes known fork key?}
    K -- Yes --> L[return 'zen' or 'floorp']
    K -- No match --> I
    L --> H
    I --> H
    H --> M[setBrowserName and return]
Loading

Reviews (1): Last reviewed commit: "fix(detect): auto-detect Zen/Floorp via ..." | Re-trigger Greptile

Comment thread src/background/helpers.ts
Comment on lines +26 to +28
for (const key of Object.keys(FIREFOX_FORK_BUCKETS)) {
if (name.includes(key)) return FIREFOX_FORK_BUCKETS[key]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Using name.includes(key) for the fork lookup means any substring match triggers a bucket assignment. For example, a hypothetical fork whose getBrowserInfo returns "Zenith" or "Gozen" would silently resolve to the zen bucket. Since the bucket name is persisted via setBrowserName and short-circuits future calls, a false-positive match would stick permanently until the user manually resets it. An exact-match check (after stripping trailing words like "browser") is more defensive.

Suggested change
for (const key of Object.keys(FIREFOX_FORK_BUCKETS)) {
if (name.includes(key)) return FIREFOX_FORK_BUCKETS[key]
}
for (const [key, bucket] of Object.entries(FIREFOX_FORK_BUCKETS)) {
// Match exactly or as the first word (e.g. "zen browser" → "zen")
if (name === key || name.startsWith(key + ' ')) return bucket
}

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.

Data is tracked, but does not show up on Browser Activity (Zen Browser)

1 participant