Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/background/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@ import browser from 'webextension-polyfill'
import { FetchError } from 'aw-client'
import { getBrowserName, setBrowserName } from '../storage'

// Firefox forks share Firefox's userAgent (that's how they run Firefox
// extensions unchanged), so UA-based detection cannot distinguish them.
// browser.runtime.getBrowserInfo() is a Firefox-only API that returns
// {name, vendor, version, buildID} and is overridden by most forks to
// reflect their own brand. We use it to refine 'firefox' into a specific
// fork name when aw-webui's queries.ts has a dedicated entry for it
// (browser_appnames + browser_appname_regex). Forks not listed below
// (librewolf, waterfox, …) fall through and keep using the firefox bucket,
// which already covers them via the firefox regex.
const FIREFOX_FORK_BUCKETS: Record<string, string> = {
zen: 'zen',
floorp: 'floorp',
}

async function refineFirefoxFork(): Promise<string> {
// getBrowserInfo is Firefox-only; missing on Chromium and on Firefox < 51.
const getBrowserInfo = (browser.runtime as any).getBrowserInfo
if (typeof getBrowserInfo !== 'function') return 'firefox'
try {
const info = await getBrowserInfo.call(browser.runtime)
const name = (info?.name || '').toLowerCase()
for (const key of Object.keys(FIREFOX_FORK_BUCKETS)) {
if (name.includes(key)) return FIREFOX_FORK_BUCKETS[key]
}
Comment on lines +26 to +28

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
}

return 'firefox'
} catch {
return 'firefox'
}
}

export const getTab = (id: number) => browser.tabs.get(id)
export const getTabs = (query: browser.Tabs.QueryQueryInfoType = {}) =>
browser.tabs.query(query)
Expand Down Expand Up @@ -48,7 +78,11 @@ export const getBrowser = async (): Promise<string> => {
return storedName
}

const browserName = detectBrowser()
let browserName = detectBrowser()
// Refine Firefox-family detection so forks like Zen get their own bucket.
if (browserName === 'firefox') {
browserName = await refineFirefoxFork()
}

await setBrowserName(browserName)
return browserName
Expand Down