Skip to content
Open
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
6 changes: 6 additions & 0 deletions applications/pass-extension/manifest-chrome.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@
"default": "Ctrl+Shift+L"
},
"description": "Open Proton Pass in a larger window"
},
"autofill": {
"suggested_key": {
"default": "Ctrl+Shift+U"
},
"description": "Autofill login credentials"
}
},
"icons": {
Expand Down
6 changes: 6 additions & 0 deletions applications/pass-extension/manifest-firefox.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
"default": "Ctrl+Shift+L"
},
"description": "Open Proton Pass in a larger window"
},
"autofill": {
"suggested_key": {
"default": "Ctrl+Shift+U"
},
"description": "Autofill login credentials"
}
},
"icons": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,25 @@ export const createAutofillService = ({ controller }: ContentScriptContextFactor
}
);

const onAutofillTrigger = withContext((ctx) => {
const fields = ctx?.service.formManager.getFields();
const loginField = fields?.find(
(field) => field.action?.type === DropdownAction.AUTOFILL_LOGIN
);

if (loginField) {
ctx?.service.inline.dropdown.toggle({
type: 'field',
action: DropdownAction.AUTOFILL_LOGIN,
autofocused: false,
autofilled: loginField.autofilled !== null,
field: loginField,
});
}
});

controller.channel.register(WorkerMessageType.AUTOFILL_SEQUENCE, onAutofillRequest);
controller.channel.register(WorkerMessageType.AUTOFILL_TRIGGER, onAutofillTrigger);

return {
get processing() {
Expand All @@ -338,6 +356,7 @@ export const createAutofillService = ({ controller }: ContentScriptContextFactor
sync,
destroy: () => {
controller.channel.unregister(WorkerMessageType.AUTOFILL_SEQUENCE, onAutofillRequest);
controller.channel.unregister(WorkerMessageType.AUTOFILL_TRIGGER, onAutofillTrigger);
},
};
};
Expand Down
58 changes: 58 additions & 0 deletions applications/pass-extension/src/lib/extension/commands.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { handleExtensionCommand } from './commands';
import browser from '@proton/pass/lib/globals/browser';
import { WorkerMessageType } from 'proton-pass-extension/types/messages';

jest.mock('@proton/pass/lib/globals/browser', () => ({
tabs: {
create: jest.fn(() => Promise.resolve()),
query: jest.fn(() => Promise.resolve([{ id: 42 }])),
sendMessage: jest.fn(() => Promise.resolve()),
},
runtime: {
getURL: jest.fn((path: string) => `chrome-extension://abc/${path}`),
},
commands: {
getAll: jest.fn(() => Promise.resolve([])),
},
}));

describe('handleExtensionCommand', () => {
beforeEach(() => jest.clearAllMocks());

it('should open larger window for open-larger-window command', async () => {
await handleExtensionCommand('open-larger-window');
expect(browser.tabs.create).toHaveBeenCalledWith({
url: 'chrome-extension://abc/popup.html#',
});
});

it('should send AUTOFILL_TRIGGER to active tab for autofill command', async () => {
await handleExtensionCommand('autofill');
expect(browser.tabs.query).toHaveBeenCalledWith({ active: true, currentWindow: true });
expect(browser.tabs.sendMessage).toHaveBeenCalledWith(
42,
expect.objectContaining({ type: WorkerMessageType.AUTOFILL_TRIGGER })
);
});

it('should send message when tab id is 0', async () => {
(browser.tabs.query as jest.Mock).mockResolvedValueOnce([{ id: 0 }]);
await handleExtensionCommand('autofill');
expect(browser.tabs.sendMessage).toHaveBeenCalledWith(
0,
expect.objectContaining({ type: WorkerMessageType.AUTOFILL_TRIGGER })
);
});

it('should not send message when no active tab', async () => {
(browser.tabs.query as jest.Mock).mockResolvedValueOnce([]);
await handleExtensionCommand('autofill');
expect(browser.tabs.sendMessage).not.toHaveBeenCalled();
});

it('should do nothing for unknown commands', async () => {
await handleExtensionCommand('unknown-command');
expect(browser.tabs.create).not.toHaveBeenCalled();
expect(browser.tabs.sendMessage).not.toHaveBeenCalled();
});
});
12 changes: 11 additions & 1 deletion applications/pass-extension/src/lib/extension/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import browser from '@proton/pass/lib/globals/browser';
import noop from '@proton/utils/noop';

import { backgroundMessage } from 'proton-pass-extension/lib/message/send-message';
import { WorkerMessageType } from 'proton-pass-extension/types/messages';

export type Shortcut = { name: string; description: string; shortcut: string };

type BrowserCommand = Awaited<ReturnType<typeof browser.commands.getAll>>[number];
Expand All @@ -10,12 +13,19 @@ export const resolveShortcuts = (commands: BrowserCommand[], supported: Record<s
.filter((cmd): cmd is BrowserCommand & { name: string } => Boolean(cmd.name && cmd.name in supported))
.map(({ name, shortcut }) => ({ name, shortcut: shortcut ?? '', description: supported[name] }));

export const handleExtensionCommand = (command: string) => {
export const handleExtensionCommand = async (command: string) => {
if (command === 'open-larger-window') {
browser.tabs
.create({
url: browser.runtime.getURL('popup.html#'),
})
.catch(noop);
}

if (command === 'autofill') {
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (tab?.id != null) {
browser.tabs.sendMessage(tab.id, backgroundMessage({ type: WorkerMessageType.AUTOFILL_TRIGGER })).catch(noop);
}
}
};
3 changes: 3 additions & 0 deletions applications/pass-extension/src/types/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export enum WorkerMessageType {
AUTOFILL_OTP_CHECK = 'AUTOFILL_OTP_CHECK',
AUTOFILL_SEQUENCE = 'AUTOFILL_SEQUENCE',
AUTOFILL_SYNC = 'AUTOFILL_SYNC',
AUTOFILL_TRIGGER = 'AUTOFILL_TRIGGER',

AUTOSAVE_REQUEST = 'AUTOSAVE_REQUEST',
AUTOSUGGEST_ALIAS = 'AUTOSUGGEST_ALIAS',
Expand Down Expand Up @@ -217,6 +218,7 @@ export type AutofillOTPCheckMessage = { type: WorkerMessageType.AUTOFILL_OTP_CHE
export type AutofillPasswordOptionsMessage = { type: WorkerMessageType.AUTOSUGGEST_PASSWORD };
export type AutofillSequenceMessage = WithPayload<WorkerMessageType.AUTOFILL_SEQUENCE, AutofillRequest>;
export type AutofillSyncMessage = { type: WorkerMessageType.AUTOFILL_SYNC };
export type AutofillTriggerMessage = { type: WorkerMessageType.AUTOFILL_TRIGGER };

export type AutoSaveRequestMessage = WithPayload<WorkerMessageType.AUTOSAVE_REQUEST, AutosaveRequest>;
export type AutosuggestAliasMessage = { type: WorkerMessageType.AUTOSUGGEST_ALIAS };
Expand Down Expand Up @@ -320,6 +322,7 @@ export type WorkerMessage =
| AutofillPasswordOptionsMessage
| AutofillSequenceMessage
| AutofillSyncMessage
| AutofillTriggerMessage
| AutoSaveRequestMessage
| AutosuggestAliasMessage
| B2BEventMessage
Expand Down