diff --git a/extension/src/service-worker/router/__tests__/router.test.ts b/extension/src/service-worker/router/__tests__/router.test.ts index b00b0e1..f503b61 100644 --- a/extension/src/service-worker/router/__tests__/router.test.ts +++ b/extension/src/service-worker/router/__tests__/router.test.ts @@ -921,3 +921,36 @@ describe('parse_lastpass_csv / import_lastpass_commit sender check', () => { expect(result).toEqual({ ok: false, error: 'unauthorized_sender' }); }); }); + +// --- get_active_tab_url --- + +describe('get_active_tab_url', () => { + it('get_active_tab_url returns active tab url + title', async () => { + // happy-dom does not provide chrome.tabs; stub it. + (globalThis as any).chrome = { + ...((globalThis as any).chrome ?? {}), + tabs: { + query: (q: any, cb: (tabs: any[]) => void) => { + cb([{ url: 'https://github.com/login', title: 'Sign in to GitHub' }]); + }, + }, + }; + const resp = await route({ type: 'get_active_tab_url' } as any, makeState(), makePopupSender()); + expect(resp.ok).toBe(true); + expect(resp.data).toEqual({ url: 'https://github.com/login', title: 'Sign in to GitHub' }); + }); + + it('get_active_tab_url returns null for chrome:// pages', async () => { + (globalThis as any).chrome = { + ...((globalThis as any).chrome ?? {}), + tabs: { + query: (q: any, cb: (tabs: any[]) => void) => { + cb([{ url: 'chrome://newtab/', title: 'New Tab' }]); + }, + }, + }; + const resp = await route({ type: 'get_active_tab_url' } as any, makeState(), makePopupSender()); + expect(resp.ok).toBe(true); + expect(resp.data).toBeNull(); + }); +}); diff --git a/extension/src/service-worker/router/popup-only.ts b/extension/src/service-worker/router/popup-only.ts index 905e949..c289be6 100644 --- a/extension/src/service-worker/router/popup-only.ts +++ b/extension/src/service-worker/router/popup-only.ts @@ -146,6 +146,19 @@ export async function handle( case 'rate_passphrase': return { ok: true, data: state.wasm.rate_passphrase(msg.passphrase) }; + case 'get_active_tab_url': { + const tabs = await new Promise((resolve) => { + chrome.tabs.query({ active: true, lastFocusedWindow: true }, (t) => resolve(t)); + }); + const tab = tabs[0]; + if (!tab?.url) return { ok: true, data: null }; + // Filter out chrome:// and extension URLs — autofill doesn't apply. + if (/^(chrome|chrome-extension|moz-extension|edge|about|file):/i.test(tab.url)) { + return { ok: true, data: null }; + } + return { ok: true, data: { url: tab.url, title: tab.title ?? '' } }; + } + case 'generate_password': { const password = state.wasm.generate_password(JSON.stringify(msg.request)); return { ok: true, data: { password } }; diff --git a/extension/src/shared/messages.ts b/extension/src/shared/messages.ts index f5a73a7..ecbcafe 100644 --- a/extension/src/shared/messages.ts +++ b/extension/src/shared/messages.ts @@ -36,6 +36,7 @@ export type PopupMessage = | { type: 'update_vault_settings'; settings: VaultSettings } | { type: 'get_blacklist' } | { type: 'remove_blacklist'; hostname: string } + | { type: 'get_active_tab_url' } | { type: 'upload_attachment'; itemId: string; filename: string; mimeType: string; bytes: ArrayBuffer } | { type: 'download_attachment'; itemId: string; attachmentId: string } | { type: 'list_devices' } @@ -157,7 +158,7 @@ export const POPUP_ONLY_TYPES: ReadonlySet = new Set([ 'fill_credentials', 'ack_autofill_origin', 'get_settings', 'update_settings', 'get_vault_settings', 'update_vault_settings', 'get_blacklist', - 'remove_blacklist', 'upload_attachment', 'download_attachment', + 'remove_blacklist', 'get_active_tab_url', 'upload_attachment', 'download_attachment', 'list_devices', 'add_device', 'register_this_device', 'revoke_device', 'list_trashed', 'restore_item', 'purge_item', 'purge_all_trash', 'get_field_history',