diff --git a/src/queries.ts b/src/queries.ts index 406a3431..f95c780e 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -245,11 +245,21 @@ const browser_appnames: Record = { }; // Returns a list of (browserName, bucketId) pairs for found browser buckets +function browserBucketMatches(bucketId: string, browserName: string): boolean { + return _.includes(bucketId, browserName); +} + +function browserBucketExists(browserbuckets: string[], browserName: string): boolean { + return _.some(browserbuckets, bucket_id => browserBucketMatches(bucket_id, browserName)); +} + function browsersWithBuckets(browserbuckets: string[]): [string, string][] { const browsername_to_bucketid: [string, string | undefined][] = _.map( Object.keys(browser_appnames), browserName => { - const bucketId = _.find(browserbuckets, bucket_id => _.includes(bucket_id, browserName)); + const bucketId = _.find(browserbuckets, bucket_id => + browserBucketMatches(bucket_id, browserName) + ); return [browserName, bucketId]; } ); @@ -262,8 +272,11 @@ function browsersWithBuckets(browserbuckets: string[]): [string, string][] { // Used with filter_keyvals_regex in addition to the exact names in browser_appnames. // The full set of historical app names these patterns replace is documented in the unit tests. // See: test/unit/queries.test.node.ts, https://github.com/ActivityWatch/aw-webui/issues/749 +const chrome_appname_regex = '(?i)^(google[-_ ]?chrome|chrome|chromium)'; +const chrome_appname_regex_with_helium = '(?i)^(google[-_ ]?chrome|chrome|chromium|helium)'; + export const browser_appname_regex: Record = { - chrome: '(?i)^(google[-_ ]?chrome|chrome|chromium)', + chrome: chrome_appname_regex, firefox: '(?i)(firefox|librewolf|waterfox|nightly)', opera: '(?i)(opera)', brave: '(?i)(brave)', @@ -277,6 +290,15 @@ export const browser_appname_regex: Record = { helium: '(?i)(helium)', }; +function browserAppnameRegex(browserName: string, browserbuckets: string[]): string | undefined { + // Helium can use the Chrome Web Store extension, which emits aw-watcher-web-chrome buckets. + // Only match Helium there when no dedicated Helium bucket exists, to avoid double-counting. + if (browserName === 'chrome' && !browserBucketExists(browserbuckets, 'helium')) { + return chrome_appname_regex_with_helium; + } + return browser_appname_regex[browserName]; +} + // Returns a list of active browser events (where the browser was the active window) from all browser buckets function browserEvents(params: DesktopQueryParams): string { let code = ` @@ -289,7 +311,7 @@ function browserEvents(params: DesktopQueryParams): string { window_${browserName} = filter_keyvals(events, "app", ${browser_appnames_str});`; // Add regex-based matching to cover case/spacing/versioning variants (e.g., Firefox.exe, firefox-esr-esr140) - const pattern = browser_appname_regex[browserName]; + const pattern = browserAppnameRegex(browserName, params.bid_browsers); if (pattern) { code += ` window_${browserName}_re = filter_keyvals_regex(events, "app", ${JSON.stringify(pattern)}); diff --git a/test/unit/queries.test.node.ts b/test/unit/queries.test.node.ts index 19dab25f..722aca7c 100644 --- a/test/unit/queries.test.node.ts +++ b/test/unit/queries.test.node.ts @@ -53,7 +53,7 @@ * (Flatpak app ID retained: 'one.ablaze.floorp') */ -import { browser_appname_regex } from '~/queries'; +import { browser_appname_regex, fullDesktopQuery } from '~/queries'; // Convert ActivityWatch (?i) patterns to JS RegExp with i flag for testing. // AW server uses Python-style (?i) inline flag; JS uses RegExp 'i' flag instead. @@ -62,6 +62,18 @@ function toRegex(pattern: string): RegExp { return new RegExp(stripped, 'i'); } +function browserQueryForBuckets(bid_browsers: string[]): string { + return fullDesktopQuery({ + bid_window: 'aw-watcher-window_testhost', + bid_afk: 'aw-watcher-afk_testhost', + bid_browsers, + filter_afk: true, + categories: [], + filter_categories: [], + include_audible: false, + }).join('\n'); +} + describe('browser_appname_regex', () => { test('chrome pattern matches all known Chrome/Chromium app names', () => { const re = toRegex(browser_appname_regex.chrome); @@ -95,6 +107,29 @@ describe('browser_appname_regex', () => { expect(re.test('Electron')).toBe(false); }); + test('chrome web watcher bucket matches Helium when there is no Helium bucket', () => { + const query = browserQueryForBuckets(['aw-watcher-web-chrome_testhost']); + expect(query).toContain( + 'window_chrome_re = filter_keyvals_regex(events, "app", "(?i)^(google[-_ ]?chrome|chrome|chromium|helium)")' + ); + }); + + test('chrome web watcher bucket does not match Helium when a Helium bucket exists', () => { + const query = browserQueryForBuckets([ + 'aw-watcher-web-chrome_testhost', + 'aw-watcher-web-helium_testhost', + ]); + expect(query).toContain( + 'window_chrome_re = filter_keyvals_regex(events, "app", "(?i)^(google[-_ ]?chrome|chrome|chromium)")' + ); + expect(query).toContain( + 'window_helium_re = filter_keyvals_regex(events, "app", "(?i)(helium)")' + ); + expect(query).not.toContain( + 'window_chrome_re = filter_keyvals_regex(events, "app", "(?i)^(google[-_ ]?chrome|chrome|chromium|helium)")' + ); + }); + test('firefox pattern matches all known Firefox/LibreWolf/Waterfox app names', () => { const re = toRegex(browser_appname_regex.firefox); // Every entry from the old exact-match list