Skip to content
Draft
Show file tree
Hide file tree
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
28 changes: 25 additions & 3 deletions src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,21 @@ const browser_appnames: Record<string, string[]> = {
};

// 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];
}
);
Expand All @@ -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<string, string> = {
chrome: '(?i)^(google[-_ ]?chrome|chrome|chromium)',
chrome: chrome_appname_regex,
firefox: '(?i)(firefox|librewolf|waterfox|nightly)',
opera: '(?i)(opera)',
brave: '(?i)(brave)',
Expand All @@ -277,6 +290,15 @@ export const browser_appname_regex: Record<string, string> = {
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 = `
Expand All @@ -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)});
Expand Down
37 changes: 36 additions & 1 deletion test/unit/queries.test.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down