diff --git a/src/background/commands.background.ts b/src/background/commands.background.ts index 26a3089e87..0b16f3b78a 100644 --- a/src/background/commands.background.ts +++ b/src/background/commands.background.ts @@ -2,15 +2,21 @@ import { BrowserApi } from '../browser/browserApi'; import MainBackground from './main.background'; -import { PasswordGenerationService } from 'jslib/abstractions'; +import { + PasswordGenerationService, + PlatformUtilsService, +} from 'jslib/abstractions'; import { UtilsService } from 'jslib/services/utils.service'; export default class CommandsBackground { private commands: any; + private isSafari: boolean; - constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService) { - this.commands = chrome.commands; + constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService, + private platformUtilsService: PlatformUtilsService) { + this.isSafari = this.platformUtilsService.isSafari(); + this.commands = this.isSafari ? safari.application : chrome.commands; } async init() { @@ -18,18 +24,34 @@ export default class CommandsBackground { return; } - this.commands.onCommand.addListener(async (command: any) => { - switch (command) { - case 'generate_password': - await this.generatePasswordToClipboard(); - break; - case 'autofill_login': - await this.autoFillLogin(); - break; - default: - break; - } - }); + if (this.isSafari) { + this.commands.addEventListener('message', async (msgEvent: any) => { + const msg = msgEvent.message; + if (msg.command === 'keyboardShortcutTriggered' && msg.command.shortcut) { + await this.processCommand(msg.command.shortcut); + } + }, false); + } else { + this.commands.onCommand.addListener(async (command: any) => { + await this.processCommand(command); + }); + } + } + + private async processCommand(command: string) { + switch (command) { + case 'generate_password': + await this.generatePasswordToClipboard(); + break; + case 'autofill_login': + await this.autoFillLogin(); + break; + case 'open_popup': + await this.openPopup(); + break; + default: + break; + } } private async generatePasswordToClipboard() { @@ -57,4 +79,12 @@ export default class CommandsBackground { eventAction: 'Autofilled From Command', }); } + + private async openPopup() { + if (!this.isSafari || !safari.extension.toolbarItems || !safari.extension.toolbarItems.length) { + return; + } + + safari.extension.toolbarItems[0].showPopover(); + } } diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 405a834296..8e6f6388bd 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -149,9 +149,10 @@ export default class MainBackground { this.runtimeBackground = new RuntimeBackground(this, this.autofillService, this.cipherService, this.platformUtilsService, this.storageService, this.i18nService); this.tabsBackground = new TabsBackground(this, this.platformUtilsService); + this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, + this.platformUtilsService); if (!this.isSafari) { - this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService); this.contextMenusBackground = new ContextMenusBackground(this, this.cipherService, this.passwordGenerationService); this.idleBackground = new IdleBackground(this, this.lockService, this.storageService); @@ -166,9 +167,9 @@ export default class MainBackground { await this.runtimeBackground.init(); await this.tabsBackground.init(); + await this.commandsBackground.init(); if (!this.isSafari) { - await this.commandsBackground.init(); await this.contextMenusBackground.init(); await this.idleBackground.init(); await this.webRequestBackground.init(); diff --git a/src/content/shortcuts.js b/src/content/shortcuts.js new file mode 100644 index 0000000000..7eb2940924 --- /dev/null +++ b/src/content/shortcuts.js @@ -0,0 +1,44 @@ +document.addEventListener('DOMContentLoaded', (event) => { + const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 && + navigator.userAgent.indexOf('Chrome') === -1; + + if (!isSafari) { + return; + } + + /* mousetrap v1.6.1 craig.is/killing/mice */ + (function(r,v,f){function w(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function A(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return p[a.which]?p[a.which]:t[a.which]?t[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function x(a){return"shift"==a||"ctrl"==a||"alt"==a|| + "meta"==a}function B(a,b){var g,c,d,f=[];g=a;"+"===g?g=["+"]:(g=g.replace(/\+{2}/g,"+plus"),g=g.split("+"));for(d=0;dq||p.hasOwnProperty(q)&&(n[p[q]]=q)}d=n[g]?"keydown":"keypress"}"keypress"==d&&f.length&&(d="keydown");return{key:c,modifiers:f,action:d}}function E(a,b){return null===a||a===v?!1:a===b?!0:E(a.parentNode,b)}function c(a){function b(a){a= + a||{};var b=!1,l;for(l in n)a[l]?b=!0:n[l]=0;b||(y=!1)}function g(a,b,u,e,c,g){var l,m,k=[],f=u.type;if(!h._callbacks[a])return[];"keyup"==f&&x(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},C={option:"alt",command:"meta","return":"enter", + escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},n;for(f=1;20>f;++f)p[111+f]="f"+f;for(f=0;9>=f;++f)p[f+96]=f.toString();c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={}; + this._directMap={};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||E(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(p[b]=a[b]);n=null};c.init=function(){var a=c(v),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a, + arguments)}}(b))};c.init();r.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports=c);"function"===typeof define&&define.amd&&define(function(){return c})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); + /* mousetrap v1.6.1 craig.is/killing/mice */ + + Mousetrap.bind('mod+shift+l', () => { + sendMessage('autofill_login'); + }); + + Mousetrap.bind('mod+shift+y', () => { + sendMessage('open_popup'); + }); + + Mousetrap.bind('mod+shift+9', () => { + sendMessage('generate_password'); + }); + + function sendMessage(shortcut) { + if (isSafari) { + safari.self.tab.dispatchMessage('bitwarden', { + command: 'keyboardShortcutTriggered', + shortcut: shortcut + }); + } else { + // not supported at this time. + } + } +});