From 4bd34598b181df4b66ef265ef4ef12c23df3e907 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 30 Dec 2016 02:09:54 -0500 Subject: [PATCH] detect login form submitted and show notification --- src/background.js | 95 +++++++++++++++++++++--- src/content/autofill.js | 3 +- src/content/notificationBar.js | 104 ++++++++++++++++++++++++++- src/notification/bar.css | 54 ++++++++++++++ src/notification/bar.html | 99 +++++++++---------------- src/notification/bar.js | 48 +++++++++++++ src/services/autofillService.js | 124 +++++++++++++++++++++----------- 7 files changed, 403 insertions(+), 124 deletions(-) create mode 100644 src/notification/bar.css create mode 100644 src/notification/bar.js diff --git a/src/background.js b/src/background.js index 573ed40054..2941b84bff 100644 --- a/src/background.js +++ b/src/background.js @@ -1,4 +1,5 @@ var isBackground = true; +var loginsToAdd = []; var i18nService = new i18nService(); var constantsService = new ConstantsService(); var utilsService = new UtilsService(); @@ -56,15 +57,28 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { messageCurrentTab('closeOverlayPopup'); } else if (msg.command === 'bgOpenNotificationBar') { - messageCurrentTab('openNotificationBar'); + messageCurrentTab('openNotificationBar', msg.data); } else if (msg.command === 'bgCloseNotificationBar') { messageCurrentTab('closeNotificationBar'); } + else if (msg.command === 'bgCollectPageDetails') { + collectPageDetailsForContentScript(sender.tab); + } + else if (msg.command === 'bgAddLogin') { + addLogin(msg.login, sender.tab); + } else if (msg.command === 'collectPageDetailsResponse') { - clearTimeout(autofillTimeout); - pageDetailsToAutoFill.push({ frameId: sender.frameId, tabId: msg.tabId, details: msg.details }); - autofillTimeout = setTimeout(autofillPage, 300); + // messageCurrentTab('openNotificationBar', { type: 'add', typeData: null }); + if (msg.contentScript) { + var forms = autofillService.getFormsWithPasswordFields(msg.details); + messageTab(msg.tabId, 'pageDetails', { details: msg.details, forms: forms }); + } + else { + clearTimeout(autofillTimeout); + pageDetailsToAutoFill.push({ frameId: sender.frameId, tabId: msg.tabId, details: msg.details }); + autofillTimeout = setTimeout(autofillPage, 300); + } } }); @@ -156,6 +170,7 @@ function buildContextMenu(callback) { } chrome.tabs.onActivated.addListener(function (activeInfo) { + checkLoginsToAdd(); refreshBadgeAndMenu(); }); @@ -165,6 +180,7 @@ chrome.tabs.onReplaced.addListener(function (addedTabId, removedTabId) { return; } onReplacedRan = true; + checkLoginsToAdd(); refreshBadgeAndMenu(); }); @@ -174,6 +190,7 @@ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { return; } onUpdatedRan = true; + checkLoginsToAdd(); refreshBadgeAndMenu(); }); @@ -300,6 +317,60 @@ chrome.contextMenus.onClicked.addListener(function (info, tab) { }); function messageCurrentTab(command, data) { + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + var tabId = null; + if (tabs.length > 0) { + tabId = tabs[0].id; + } + else { + return; + } + + messageTab(tabId, command, data); + }); +} + +function messageTab(tabId, command, data) { + if (!tabId) { + return; + } + + var obj = { + command: command + }; + + if (data) { + obj['data'] = data; + } + + chrome.tabs.sendMessage(tabId, obj); +} + +function collectPageDetailsForContentScript(tab) { + chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tabId: tab.id, contentScript: true }, function () { }); +} + +function addLogin(login, tab) { + var loginDomain = tldjs.getDomain(login.url); + if (!loginDomain) { + return; + } + + loginsToAdd.push({ + username: login.username, + password: login.password, + name: loginDomain, + uri: login.url, + tabId: tab.id + }); + checkLoginsToAdd(); +} + +function checkLoginsToAdd() { + if (!loginsToAdd.length) { + return; + } + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { var tabId = null; if (tabs.length > 0) { @@ -313,15 +384,17 @@ function messageCurrentTab(command, data) { return; } - var obj = { - command: command - }; - - if (data) { - obj['data'] = data; + var tabDomain = tldjs.getDomain(tabs[0].url); + if (!tabDomain) { + return; } - chrome.tabs.sendMessage(tabId, obj); + for (var i = 0; i < loginsToAdd.length; i++) { + if (loginsToAdd[i].tabId === tabId && loginsToAdd[i].name === tabDomain) { + messageTab(tabId, 'openNotificationBar', { type: 'add' }); + break; + } + } }); } diff --git a/src/content/autofill.js b/src/content/autofill.js index 4c0157f0c3..d3361f13ef 100644 --- a/src/content/autofill.js +++ b/src/content/autofill.js @@ -90,7 +90,8 @@ chrome.runtime.sendMessage({ command: 'collectPageDetailsResponse', tabId: msg.tabId, - details: pageDetailsObj + details: pageDetailsObj, + contentScript: msg.contentScript ? true : false }); sendResponse(); return true; diff --git a/src/content/notificationBar.js b/src/content/notificationBar.js index f518893c6e..4ca3fbdd49 100644 --- a/src/content/notificationBar.js +++ b/src/content/notificationBar.js @@ -1,8 +1,14 @@ !(function () { + var pageDetails = [], + formData = []; + chrome.runtime.sendMessage({ + command: 'bgCollectPageDetails' + }); + chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { if (msg.command === 'openNotificationBar') { closeBar(); - openBar(); + openBar(msg.data.type, msg.data.typeData); sendResponse(); return true; } @@ -11,11 +17,103 @@ sendResponse(); return true; } + else if (msg.command === 'pageDetails') { + console.log(msg.data); + pageDetails.push(msg.data.details); + watchForms(msg.data.forms); + sendResponse(); + return true; + } }); - function openBar() { + function watchForms(forms) { + if (!forms || !forms.length) { + return; + } + + for (var i = 0; i < forms.length; i++) { + var form = null, + formId = forms[i].form ? forms[i].form.htmlID : null; + + if (formId && formId !== '') { + form = document.getElementById(formId); + } + + if (!form) { + var index = parseInt(forms[i].form.opid.split('__')[2]); + form = document.getElementsByTagName('form')[index]; + } + + if (form) { + forms[i].formElement = form; + formData.push(forms[i]); + form.addEventListener('submit', formSubmitted, false); + } + } + } + + function formSubmitted(e) { + for (var i = 0; i < formData.length; i++) { + if (formData[i].formElement === e.target) { + var password = null, + username = null, + passwordId = formData[i].password ? formData[i].password.htmlID : null, + usernameId = formData[i].username ? formData[i].username.htmlID : null; + + if (passwordId && passwordId !== '') { + password = document.getElementById(passwordId); + } + else if (formData[i].password) { + password = document.getElementsByTagName('input')[formData[i].password.elementNumber]; + } + + if (usernameId && usernameId !== '') { + username = document.getElementById(usernameId); + } + else if (formData[i].username) { + username = document.getElementsByTagName('input')[formData[i].username.elementNumber]; + } + + var login = { + username: username.value, + password: password.value, + url: document.URL + }; + + if (login.password && login.password !== '') { + chrome.runtime.sendMessage({ + command: 'bgAddLogin', + login: login + }); + break; + } + } + } + } + + function openBar(type, typeData) { + var barPage = 'notification/bar.html'; + switch (type) { + case 'info': + barPage = barPage + '?info=' + typeData.text; + break; + case 'warning': + barPage = barPage + '?warning=' + typeData.text; + break; + case 'error': + barPage = barPage + '?error=' + typeData.text; + break; + case 'success': + barPage = barPage + '?success=' + typeData.text; + break; + case 'add': + barPage = barPage + '?add=1'; + default: + break; + } + var iframe = document.createElement('iframe'); - iframe.src = chrome.extension.getURL('notification/bar.html'); + iframe.src = chrome.extension.getURL(barPage); iframe.style.cssText = 'height: 41px; width: 100%; border: 0;'; var frameDiv = document.createElement('div'); diff --git a/src/notification/bar.css b/src/notification/bar.css new file mode 100644 index 0000000000..cc882e533a --- /dev/null +++ b/src/notification/bar.css @@ -0,0 +1,54 @@ +body { + background-color: #ffffff; + padding: 0; + margin: 0; + height: 100%; + font-size: 14px; + line-height: 16px; + color: #333333; + font-family: Arial, Helvetica, sans-serif; +} + +table { + width: 100%; +} + +.outter-table > tbody > tr > td { + padding: 0 0 0 10px; + border-bottom: 1px solid #333333; + height: 40px; +} + + .outter-table > tbody > tr > td:last-child { + padding-right: 10px; + } + +.inner-table td { + padding: 0 10px 0 0; +} + + .inner-table td:last-child { + padding: 0; + } + + .inner-table td button { + margin-left: 5px; + } + +img { + border: 0; + margin: 0; + padding: 0; +} + +#logo { + width: 24px; + height: 24px; + display: block; +} + +#close { + width: 18px; + height: 18px; + display: block; +} diff --git a/src/notification/bar.html b/src/notification/bar.html index feefa448bd..6d8a273ed0 100644 --- a/src/notification/bar.html +++ b/src/notification/bar.html @@ -3,74 +3,41 @@ - - + - - - - - - +
- - - This is the notification bar - - - X - -
+ + + + + + +
+ + + + X + +
+ + + diff --git a/src/notification/bar.js b/src/notification/bar.js new file mode 100644 index 0000000000..fc34040757 --- /dev/null +++ b/src/notification/bar.js @@ -0,0 +1,48 @@ +$(function () { + var content = document.getElementById('content'), + template_add = document.getElementById('template-add'), + template_alert = document.getElementById('template-alert'); + + if (getQueryVariable('add')) { + setContent(template_add); + + var add = $('#template-add-clone'), + addButton = $('#template-add-clone.add-save'), + neverButton = $('#template-add-clone.add-never'); + } + else if (getQueryVariable('info')) { + setContent(template_alert); + $('#template-alert-clone').text(getQueryVariable('info')); + } + + $('#close-button').click(function (e) { + e.preventDefault(); + chrome.runtime.sendMessage({ + command: 'bgCloseNotificationBar' + }); + }); + + function getQueryVariable(variable) { + var query = window.location.search.substring(1); + var vars = query.split('&'); + + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split('='); + if (pair[0] == variable) { + return pair[1]; + } + } + + return null; + } + + function setContent(element) { + while (content.firstChild) { + content.removeChild(content.firstChild); + } + + var newElement = element.cloneNode(true); + newElement.id = newElement.id + '-clone'; + content.appendChild(newElement); + } +}); diff --git a/src/services/autofillService.js b/src/services/autofillService.js index e8b73fc5f6..d039e399a6 100644 --- a/src/services/autofillService.js +++ b/src/services/autofillService.js @@ -23,31 +23,10 @@ function initAutofill() { pf = null, username = null; - function loadPasswordFields(canBeHidden) { - for (var i = 0; i < pageDetails.fields.length; i++) { - if (pageDetails.fields[i].type === 'password' && (canBeHidden || pageDetails.fields[i].viewable)) { - passwordFields.push(pageDetails.fields[i]); - } - } - } - - loadPasswordFields(false); + passwordFields = loadPasswordFields(pageDetails, false); if (!passwordFields.length) { // not able to find any viewable password fields. maybe there are some "hidden" ones? - loadPasswordFields(true); - } - - function findUsernameField(passwordField, canBeHidden) { - for (var i = 0; i < pageDetails.fields.length; i++) { - var f = pageDetails.fields[i]; - if (f.form === passwordField.form && (canBeHidden || f.viewable) - && (f.type === 'text' || f.type === 'email' || f.type === 'tel') - && f.elementNumber < passwordField.elementNumber) { - return f; - } - } - - return null; + passwordFields = loadPasswordFields(pageDetails, true); } for (var formKey in pageDetails.forms) { @@ -63,11 +42,11 @@ function initAutofill() { passwords.push(pf); if (fillUsername) { - username = findUsernameField(pf, false); + username = findUsernameField(pageDetails, pf, false); if (!username) { // not able to find any viewable username fields. maybe there are some "hidden" ones? - username = findUsernameField(pf, true); + username = findUsernameField(pageDetails, pf, true); } if (username) { @@ -77,22 +56,6 @@ function initAutofill() { } } - function findUsernameFieldWithoutForm(passwordField, canBeHidden) { - var usernameField = null; - for (var i = 0; i < pageDetails.fields.length; i++) { - var f = pageDetails.fields[i]; - if (f.elementNumber > passwordField.elementNumber) { - break; - } - - if ((canBeHidden || f.viewable) && (f.type === 'text' || f.type === 'email' || f.type === 'tel')) { - usernameField = f; - } - } - - return usernameField; - } - if (passwordFields.length && !passwords.length) { // The page does not have any forms with password fields. Use the first password field on the page and the // input field just before it as the username. @@ -101,11 +64,11 @@ function initAutofill() { passwords.push(pf); if (fillUsername && pf.elementNumber > 0) { - username = findUsernameFieldWithoutForm(pf, false); + username = findUsernameFieldWithoutForm(pageDetails, pf, false); if (!username) { // not able to find any viewable username fields. maybe there are some "hidden" ones? - username = findUsernameFieldWithoutForm(pf, true); + username = findUsernameFieldWithoutForm(pageDetails, pf, true); } if (username) { @@ -130,4 +93,79 @@ function initAutofill() { return fillScript; }; + + AutofillService.prototype.getFormsWithPasswordFields = function (pageDetails) { + var passwordFields = [], + formData = []; + + passwordFields = loadPasswordFields(pageDetails, false); + if (!passwordFields.length) { + // not able to find any viewable password fields. maybe there are some "hidden" ones? + passwordFields = loadPasswordFields(pageDetails, true); + } + + if (passwordFields.length) { + for (var formKey in pageDetails.forms) { + for (var i = 0; i < passwordFields.length; i++) { + var pf = passwordFields[i]; + if (formKey === pf.form) { + var uf = findUsernameField(pageDetails, pf, false); + if (!uf) { + // not able to find any viewable username fields. maybe there are some "hidden" ones? + uf = findUsernameField(pageDetails, pf, true); + } + + formData.push({ + form: pageDetails.forms[formKey], + password: pf, + username: uf + }); + break; + } + } + } + } + + return formData; + }; + + function loadPasswordFields(pageDetails, canBeHidden) { + var arr = []; + for (var i = 0; i < pageDetails.fields.length; i++) { + if (pageDetails.fields[i].type === 'password' && (canBeHidden || pageDetails.fields[i].viewable)) { + arr.push(pageDetails.fields[i]); + } + } + + return arr; + } + + function findUsernameField(pageDetails, passwordField, canBeHidden) { + for (var i = 0; i < pageDetails.fields.length; i++) { + var f = pageDetails.fields[i]; + if (f.form === passwordField.form && (canBeHidden || f.viewable) + && (f.type === 'text' || f.type === 'email' || f.type === 'tel') + && f.elementNumber < passwordField.elementNumber) { + return f; + } + } + + return null; + } + + function findUsernameFieldWithoutForm(pageDetails, passwordField, canBeHidden) { + var usernameField = null; + for (var i = 0; i < pageDetails.fields.length; i++) { + var f = pageDetails.fields[i]; + if (f.elementNumber > passwordField.elementNumber) { + break; + } + + if ((canBeHidden || f.viewable) && (f.type === 'text' || f.type === 'email' || f.type === 'tel')) { + usernameField = f; + } + } + + return usernameField; + } };