diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 08a64f6154d..a5994a031aa 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -729,5 +729,14 @@ }, "environmentSaved": { "message": "The environment URLs have been saved." + }, + "enableAutoFillOnPageLoad": { + "message": "Enable Auto-fill On Page Load." + }, + "enableAutoFillOnPageLoadDesc": { + "message": "If a login form is detected, automatically perform an auto-fill when the web page loads." + }, + "experimentalFeature": { + "message": "This is currently an experimental feature. Use at your own risk." } } diff --git a/src/background.js b/src/background.js index 64dd2e4481e..ae0bfea0ac4 100644 --- a/src/background.js +++ b/src/background.js @@ -15,9 +15,9 @@ var bg_lockService = new LockService(bg_constantsService, bg_cryptoService, bg_f refreshBadgeAndMenu); var bg_syncService = new SyncService(bg_loginService, bg_folderService, bg_userService, bg_apiService, bg_settingsService, bg_cryptoService, logout); -var bg_autofillService = new AutofillService(); var bg_passwordGenerationService = new PasswordGenerationService(); var bg_totpService = new TotpService(bg_constantsService); +var bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_loginService); if (chrome.commands) { chrome.commands.onCommand.addListener(function (command) { @@ -63,7 +63,7 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { messageTab(sender.tab.id, 'closeNotificationBar'); } else if (msg.command === 'bgCollectPageDetails') { - collectPageDetailsForContentScript(sender.tab); + collectPageDetailsForContentScript(sender.tab, msg.sender); } else if (msg.command === 'bgAddLogin') { addLogin(msg.login, sender.tab); @@ -78,13 +78,18 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { saveNever(sender.tab); } else if (msg.command === 'collectPageDetailsResponse') { - if (msg.contentScript) { + if (msg.sender === 'notificationBar') { var forms = bg_autofillService.getFormsWithPasswordFields(msg.details); - messageTab(msg.tabId, 'pageDetails', { details: msg.details, forms: forms }); + messageTab(msg.tab.id, 'pageDetails', { details: msg.details, forms: forms }); + } + else if (msg.sender === 'autofiller') { + bg_autofillService.doAutoFillForFirstLogin([{ + frameId: sender.frameId, tab: msg.tab, details: msg.details + }]); } else { clearTimeout(autofillTimeout); - pageDetailsToAutoFill.push({ frameId: sender.frameId, tabId: msg.tabId, details: msg.details }); + pageDetailsToAutoFill.push({ frameId: sender.frameId, tab: msg.tab, details: msg.details }); autofillTimeout = setTimeout(autofillPage, 300); } } else if (msg.command === 'bgUpdateContextMenu') { @@ -396,8 +401,8 @@ function messageTab(tabId, command, data, callback) { }); } -function collectPageDetailsForContentScript(tab) { - chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tabId: tab.id, contentScript: true }, function () { +function collectPageDetailsForContentScript(tab, sender) { + chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tab: tab, sender: sender }, function () { }); } @@ -555,74 +560,28 @@ function checkbg_loginsToAdd(tab, callback) { function startAutofillPage(login) { loginToAutoFill = login; chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { - var tabId = null; + var tab = null; if (tabs.length > 0) { - tabId = tabs[0].id; + tab = tabs[0]; } else { return; } - if (!tabId) { + if (!tab) { return; } - chrome.tabs.sendMessage(tabId, { command: 'collectPageDetails', tabId: tabId }, function () { + chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tab: tab }, function () { }); }); } function autofillPage() { - chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { - var tabId = null; - if (tabs.length > 0) { - tabId = tabs[0].id; - } - else { - return; - } - - if (!tabId) { - return; - } - - if (loginToAutoFill && pageDetailsToAutoFill && pageDetailsToAutoFill.length) { - for (var i = 0; i < pageDetailsToAutoFill.length; i++) { - // make sure we're still on correct tab - if (pageDetailsToAutoFill[i].tabId !== tabId) { - continue; - } - - var fillScript = bg_autofillService.generateFillScript(pageDetailsToAutoFill[i].details, - loginToAutoFill.username, loginToAutoFill.password); - if (tabId && fillScript && fillScript.script && fillScript.script.length) { - chrome.tabs.sendMessage(tabId, { - command: 'fillForm', - fillScript: fillScript - }, { frameId: pageDetailsToAutoFill[i].frameId }); - - if (!bg_utilsService.isFirefox() && loginToAutoFill.totp && bg_tokenService.getPremium()) { - var totpKey = loginToAutoFill.totp; - bg_totpService.isAutoCopyEnabled().then(function (enabled) { - if (enabled) { - return bg_totpService.getCode(totpKey); - } - - return null; - }).then(function (code) { - if (code) { - bg_utilsService.copyToClipboard(code); - } - }); - } - } - } - } - - // reset - loginToAutoFill = null; - pageDetailsToAutoFill = []; - }); + bg_autofillService.doAutoFill(loginToAutoFill, pageDetailsToAutoFill, true); + // reset + loginToAutoFill = null; + pageDetailsToAutoFill = []; } function sortLogins(logins) { diff --git a/src/content/autofill.js b/src/content/autofill.js index ebe7da0fdb4..d2d3a7e3963 100644 --- a/src/content/autofill.js +++ b/src/content/autofill.js @@ -962,9 +962,9 @@ var pageDetailsObj = JSON.parse(pageDetails); chrome.runtime.sendMessage({ command: 'collectPageDetailsResponse', - tabId: msg.tabId, + tab: msg.tab, details: pageDetailsObj, - contentScript: msg.contentScript ? true : false + sender: msg.sender }); sendResponse(); return true; diff --git a/src/content/autofiller.js b/src/content/autofiller.js new file mode 100644 index 00000000000..d402d83851a --- /dev/null +++ b/src/content/autofiller.js @@ -0,0 +1,15 @@ +document.addEventListener('DOMContentLoaded', function (event) { + chrome.storage.local.get('enableAutoFillOnPageLoad', function (obj) { + if (obj && obj.enableAutoFillOnPageLoad === true) { + setTimeout(fill, 500); + window.addEventListener('popstate', fill); + } + }); + + function fill() { + chrome.runtime.sendMessage({ + command: 'bgCollectPageDetails', + sender: 'autofiller' + }); + } +}); diff --git a/src/content/notificationBar.js b/src/content/notificationBar.js index e47696a4e0b..5370f951b58 100644 --- a/src/content/notificationBar.js +++ b/src/content/notificationBar.js @@ -36,7 +36,8 @@ chrome.storage.local.get('disableAddLoginNotification', function (obj) { if (!obj || !obj.disableAddLoginNotification) { chrome.runtime.sendMessage({ - command: 'bgCollectPageDetails' + command: 'bgCollectPageDetails', + sender: 'notificationBar' }); } }); diff --git a/src/manifest.json b/src/manifest.json index 58a108cd036..eab60613cdb 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -27,6 +27,18 @@ ], "run_at": "document_start" }, + { + "all_frames": true, + "js": [ + "content/autofiller.js" + ], + "matches": [ + "http://*/*", + "https://*/*", + "file:///*" + ], + "run_at": "document_start" + }, { "all_frames": false, "js": [ diff --git a/src/popup/app/current/currentController.js b/src/popup/app/current/currentController.js index 20c80e899d9..d2eb7d34541 100644 --- a/src/popup/app/current/currentController.js +++ b/src/popup/app/current/currentController.js @@ -6,7 +6,6 @@ angular $scope.i18n = i18nService; var pageDetails = [], - tabId = null, url = null, domain = null, canAutofill = false; @@ -22,7 +21,6 @@ angular chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { if (tabs.length > 0) { url = tabs[0].url; - tabId = tabs[0].id; } else { $scope.loaded = true; @@ -37,9 +35,10 @@ angular return; } - chrome.tabs.sendMessage(tabId, { command: 'collectPageDetails', tabId: tabId }, function () { - canAutofill = true; - }); + chrome.tabs.sendMessage(tabs[0].id, + { command: 'collectPageDetails', tab: tabs[0], sender: 'currentController' }, function () { + canAutofill = true; + }); $q.when(loginService.getAllDecryptedForDomain(domain)).then(function (logins) { $scope.loaded = true; @@ -68,49 +67,18 @@ angular }; $scope.fillLogin = function (login) { - var didAutofill = false; - - if (login && canAutofill && pageDetails && pageDetails.length) { - for (var i = 0; i < pageDetails.length; i++) { - if (pageDetails[i].tabId !== tabId) { - continue; - } - - var fillScript = autofillService.generateFillScript(pageDetails[i].details, login.username, login.password); - if (tabId && fillScript && fillScript.script && fillScript.script.length) { - didAutofill = true; - $analytics.eventTrack('Autofilled'); - chrome.tabs.sendMessage(tabId, { - command: 'fillForm', - fillScript: fillScript - }, { frameId: pageDetails[i].frameId }, function () { - if (login.totp && tokenService.getPremium()) { - totpService.isAutoCopyEnabled().then(function (enabled) { - if (enabled) { - return totpService.getCode(login.totp); - } - - return null; - }).then(function (code) { - if (code) { - utilsService.copyToClipboard(code); - } - - $window.close(); - }); - } - else { - $window.close(); - } - }); - } - } - } - - if (!didAutofill) { + if (!canAutofill) { $analytics.eventTrack('Autofilled Error'); toastr.error(i18nService.autofillError); } + + autofillService.doAutoFill(login, pageDetails, false).then(function () { + $analytics.eventTrack('Autofilled'); + $window.close(); + }, function () { + $analytics.eventTrack('Autofilled Error'); + toastr.error(i18nService.autofillError); + }); }; $scope.viewLogin = function (login) { diff --git a/src/popup/app/global/mainController.js b/src/popup/app/global/mainController.js index 47c4ccaf499..8ca8c71daec 100644 --- a/src/popup/app/global/mainController.js +++ b/src/popup/app/global/mainController.js @@ -38,7 +38,7 @@ angular else if (msg.command === 'collectPageDetailsResponse') { $scope.$broadcast('collectPageDetailsResponse', { frameId: sender.frameId, - tabId: msg.tabId, + tab: msg.tab, details: msg.details }); } diff --git a/src/popup/app/settings/settingsFeaturesController.js b/src/popup/app/settings/settingsFeaturesController.js index 44e02694b44..6c739410228 100644 --- a/src/popup/app/settings/settingsFeaturesController.js +++ b/src/popup/app/settings/settingsFeaturesController.js @@ -7,6 +7,14 @@ $scope.disableGa = false; $scope.disableAddLoginNotification = false; $scope.disableContextMenuItem = false; + $scope.disableAutoTotpCopy = false; + $scope.enableAutoFillOnPageLoad = false; + + chrome.storage.local.get(constantsService.enableAutoFillOnPageLoadKey, function (obj) { + $timeout(function () { + $scope.enableAutoFillOnPageLoad = obj && obj[constantsService.enableAutoFillOnPageLoadKey] === true; + }); + }); chrome.storage.local.get(constantsService.disableGaKey, function (obj) { $timeout(function () { @@ -145,4 +153,27 @@ }); }); }; + + $scope.updateAutoFillOnPageLoad = function () { + chrome.storage.local.get(constantsService.enableAutoFillOnPageLoadKey, function (obj) { + if (obj[constantsService.enableAutoFillOnPageLoadKey]) { + // disable + obj[constantsService.enableAutoFillOnPageLoadKey] = false; + } + else { + // enable + $analytics.eventTrack('Enable Auto-fill Page Load'); + obj[constantsService.enableAutoFillOnPageLoadKey] = true; + } + + chrome.storage.local.set(obj, function () { + $timeout(function () { + $scope.enableAutoFillOnPageLoad = obj[constantsService.enableAutoFillOnPageLoadKey]; + }); + if (!obj[constantsService.enableAutoFillOnPageLoadKey]) { + $analytics.eventTrack('Disable Auto-fill Page Load'); + } + }); + }); + }; }); diff --git a/src/popup/app/settings/views/settingsFeatures.html b/src/popup/app/settings/views/settingsFeatures.html index e4fa25f58cf..4594fc60a62 100644 --- a/src/popup/app/settings/views/settingsFeatures.html +++ b/src/popup/app/settings/views/settingsFeatures.html @@ -6,6 +6,19 @@
+
+
+
+ + +
+
+ +
diff --git a/src/services/autofillService.js b/src/services/autofillService.js index 4fb40a9bd05..b7f231a43a9 100644 --- a/src/services/autofillService.js +++ b/src/services/autofillService.js @@ -1,4 +1,9 @@ -function AutofillService() { +function AutofillService(utilsService, totpService, tokenService, loginService) { + this.utilsService = utilsService; + this.totpService = totpService; + this.tokenService = tokenService; + this.loginService = loginService; + initAutofill(); } @@ -130,6 +135,106 @@ function initAutofill() { return formData; }; + AutofillService.prototype.doAutoFill = function (login, pageDetails, fromBackground, skipTotp) { + var deferred = Q.defer(); + var self = this; + + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + var tab = null; + if (tabs.length > 0) { + tab = tabs[0]; + } + else { + deferred.reject(); + return; + } + + if (!tab || !login || !pageDetails || !pageDetails.length) { + deferred.reject(); + return; + } + + var didAutofill = false; + for (var i = 0; i < pageDetails.length; i++) { + // make sure we're still on correct tab + if (pageDetails[i].tab.id !== tab.id || pageDetails[i].tab.url !== tab.url) { + continue; + } + + var fillScript = self.generateFillScript(pageDetails[i].details, + login.username, login.password); + if (!fillScript || !fillScript.script || !fillScript.script.length) { + continue; + } + + didAutofill = true; + + chrome.tabs.sendMessage(tab.id, { + command: 'fillForm', + fillScript: fillScript + }, { frameId: pageDetails[i].frameId }); + + if ((fromBackground && self.utilsService.isFirefox()) || + skipTotp || !login.totp || !self.tokenService.getPremium()) { + deferred.resolve(); + return; + } + + self.totpService.isAutoCopyEnabled().then(function (enabled) { + if (enabled) { + return self.totpService.getCode(login.totp); + } + + return null; + }).then(function (code) { + if (code) { + self.utilsService.copyToClipboard(code); + } + + deferred.resolve(); + return; + }); + + break; + } + + if (!didAutofill) { + deferred.reject(); + return; + } + }); + + return deferred.promise; + }; + + AutofillService.prototype.doAutoFillForFirstLogin = function (pageDetails) { + var self = this; + + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + var tab = null; + if (tabs.length > 0) { + tab = tabs[0]; + } + + if (!tab || !tab.url) { + return; + } + + var tabDomain = self.utilsService.getDomain(tab.url); + if (!tabDomain) { + return; + } + + self.loginService.getAllDecryptedForDomain(tabDomain).then(function (logins) { + if (!logins.length) { + return; + } + + self.doAutoFill(logins[0], pageDetails, true, true); + }); + }); + } + function loadPasswordFields(pageDetails, canBeHidden) { var arr = []; for (var i = 0; i < pageDetails.fields.length; i++) { diff --git a/src/services/constantsService.js b/src/services/constantsService.js index 69524e386fd..42f07b09183 100644 --- a/src/services/constantsService.js +++ b/src/services/constantsService.js @@ -7,6 +7,7 @@ function ConstantsService(i18nService) { disableAddLoginNotificationKey: 'disableAddLoginNotification', disableContextMenuItemKey: 'disableContextMenuItem', disableAutoTotpCopyKey: 'disableAutoTotpCopy', + enableAutoFillOnPageLoadKey: 'enableAutoFillOnPageLoad', lockOptionKey: 'lockOption', lastActiveKey: 'lastActive', encType: {