From 37dc528935c09085242d82a200debde90e429ce4 Mon Sep 17 00:00:00 2001 From: Anthony Tseng Date: Thu, 18 Aug 2016 19:03:25 +0800 Subject: [PATCH] Add Autofill support fix #860 auditors: @bridiver, @bbondy --- app/extensions/brave/about-autofill.html | 19 ++ .../brave/locales/en-US/app.properties | 12 ++ .../brave/locales/en-US/autofill.properties | 19 ++ .../locales/en-US/preferences.properties | 3 + app/filtering.js | 53 +++++ app/index.js | 10 + js/about/aboutActions.js | 24 +++ js/about/autofill.js | 204 ++++++++++++++++++ js/about/entry.js | 3 + js/about/preferences.js | 8 + js/actions/appActions.js | 30 +++ js/actions/windowActions.js | 22 ++ js/components/autofillAddressPanel.js | 151 +++++++++++++ js/components/autofillCreditCardPanel.js | 112 ++++++++++ js/components/frame.js | 28 +++ js/components/main.js | 34 +++ js/constants/appConfig.js | 1 + js/constants/appConstants.js | 6 +- js/constants/messages.js | 8 + js/constants/settings.js | 2 + js/constants/windowConstants.js | 4 +- js/contextMenus.js | 39 ++++ js/lib/appUrlUtil.js | 3 +- js/state/privacy.js | 34 +++ js/stores/appStore.js | 62 ++++++ js/stores/windowStore.js | 24 +++ less/about/autofill.less | 59 +++++ less/about/preferences.less | 6 + less/forms.less | 28 +++ 29 files changed, 1005 insertions(+), 3 deletions(-) create mode 100644 app/extensions/brave/about-autofill.html create mode 100644 app/extensions/brave/locales/en-US/autofill.properties create mode 100644 js/about/autofill.js create mode 100644 js/components/autofillAddressPanel.js create mode 100644 js/components/autofillCreditCardPanel.js create mode 100644 js/state/privacy.js create mode 100644 less/about/autofill.less diff --git a/app/extensions/brave/about-autofill.html b/app/extensions/brave/about-autofill.html new file mode 100644 index 00000000000..40ec9641bcf --- /dev/null +++ b/app/extensions/brave/about-autofill.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + +
+ + diff --git a/app/extensions/brave/locales/en-US/app.properties b/app/extensions/brave/locales/en-US/app.properties index 01ab7bb9038..ec93ead057e 100644 --- a/app/extensions/brave/locales/en-US/app.properties +++ b/app/extensions/brave/locales/en-US/app.properties @@ -175,3 +175,15 @@ allSiteCookies=All site cookies clear=Clear clearDataWarning=Warning: Selected data, back to the day you installed Brave will be cleared and cannot be undone. clearBrowsingData=Clear browsing data +nameOnCard=Name +creditCardNumber=Card Number +expirationDate=Expiration date +nameOnAddress=Name +organization=Organization +streetAddress=Street Address +city=City +state=State +postalCode=Postal Code +country=Country +phone=Phone +email=Email diff --git a/app/extensions/brave/locales/en-US/autofill.properties b/app/extensions/brave/locales/en-US/autofill.properties new file mode 100644 index 00000000000..5c9bc2ad7c8 --- /dev/null +++ b/app/extensions/brave/locales/en-US/autofill.properties @@ -0,0 +1,19 @@ +autofillTitle=Autofill Settings +addresses=Addresses +creditCards=Credit Cards +addAddress=Add Address +addCreditCard=Add Credit Card +nameOnCard=Name on card +creditCardNumber=Credit card number +expirationDate=Expiration date +noCreditCardsSaved=No saved credit cards +nameOnAddress=Name +organization=Organization +streetAddress=Street Address +city=City +state=State +postalCode=Postal Code +country=Country +phone=Phone +email=Email +noAddressesSaved=No saved addresses diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties index 7e5191aafca..9e907c1adf2 100644 --- a/app/extensions/brave/locales/en-US/preferences.properties +++ b/app/extensions/brave/locales/en-US/preferences.properties @@ -164,3 +164,6 @@ allSiteCookies=All site cookies passwordsAndForms=Passwords and Forms tabSettings=Tab Settings clearBrowsingDataNow=Clear Browsing Data Now... +autofillSettings=Autofill Settings +manageAutofillData=Manage Autofill Data... +enableAutofill=Enable Autofill diff --git a/app/filtering.js b/app/filtering.js index ebab24ea6de..5c6dd503c1e 100644 --- a/app/filtering.js +++ b/app/filtering.js @@ -570,3 +570,56 @@ module.exports.setDefaultZoomLevel = (zoom) => { ses.userPrefs.setDefaultZoomLevel(zoom) } } + +module.exports.addAutofillAddress = (detail) => { + let guidMap = {} + for (let partition in registeredSessions) { + let ses = registeredSessions[partition] + let guid = ses.autofill.addProfile({ + full_name: detail.name, + company_name: detail.organization, + street_address: detail.streetAddress, + city: detail.city, + state: detail.state, + postal_code: detail.postalCode, + country_code: detail.country, + phone: detail.phone, + email: detail.email + }) + guidMap[partition] = guid + } + return guidMap +} + +module.exports.removeAutofillAddress = (guid) => { + for (let partition in registeredSessions) { + let ses = registeredSessions[partition] + if (guid[partition] !== undefined) { + ses.autofill.removeProfile(guid[partition]) + } + } +} + +module.exports.addAutofillCreditCard = (detail) => { + let guidMap = {} + for (let partition in registeredSessions) { + let ses = registeredSessions[partition] + let guid = ses.autofill.addCreditCard({ + name: detail.name, + card_number: detail.card, + expiration_month: detail.month, + expiration_year: detail.year + }) + guidMap[partition] = guid + } + return guidMap +} + +module.exports.removeAutofillCreditCard = (guid) => { + for (let partition in registeredSessions) { + let ses = registeredSessions[partition] + if (guid[partition] !== undefined) { + ses.autofill.removeCreditCard(guid[partition]) + } + } +} diff --git a/app/index.js b/app/index.js index ef3e1f6187e..5995c609623 100644 --- a/app/index.js +++ b/app/index.js @@ -61,6 +61,7 @@ const ledger = require('./ledger') const flash = require('../js/flash') const contentSettings = require('../js/state/contentSettings') const FrameStateUtil = require('../js/state/frameStateUtil') +const privacy = require('../js/state/privacy') // Used to collect the per window state when shutting down the application let perWindowState = [] @@ -398,6 +399,7 @@ app.on('ready', () => { return loadedPerWindowState }).then((loadedPerWindowState) => { contentSettings.init() + privacy.init() Extensions.init() Filtering.init() SiteHacks.init() @@ -749,6 +751,14 @@ app.on('ready', () => { } }) + ipcMain.on(messages.REMOVE_AUTOFILL_ADDRESS, (e, address) => { + appActions.removeAutofillAddress(address) + }) + + ipcMain.on(messages.REMOVE_AUTOFILL_CREDIT_CARD, (e, card) => { + appActions.removeAutofillCreditCard(card) + }) + // Setup the crash handling CrashHerald.init() diff --git a/js/about/aboutActions.js b/js/about/aboutActions.js index 4253df12e48..8321e17161b 100644 --- a/js/about/aboutActions.js +++ b/js/about/aboutActions.js @@ -133,6 +133,30 @@ const AboutActions = { setLedgerEnabled: function (enabled) { ipc.send(messages.LEDGER_ENABLE, enabled) + }, + + addAutofillAddress: function () { + ipc.sendToHost(messages.ADD_AUTOFILL_ADDRESS) + }, + + removeAutofillAddress: function (address) { + ipc.send(messages.REMOVE_AUTOFILL_ADDRESS, address) + }, + + editAutofillAddress: function (address) { + ipc.sendToHost(messages.EDIT_AUTOFILL_ADDRESS, address) + }, + + addAutofillCreditCard: function () { + ipc.sendToHost(messages.ADD_AUTOFILL_CREDIT_CARD) + }, + + removeAutofillCreditCard: function (card) { + ipc.send(messages.REMOVE_AUTOFILL_CREDIT_CARD, card) + }, + + editAutofillCreditCard: function (card) { + ipc.sendToHost(messages.EDIT_AUTOFILL_CREDIT_CARD, card) } } module.exports = AboutActions diff --git a/js/about/autofill.js b/js/about/autofill.js new file mode 100644 index 00000000000..bc4d81a5a76 --- /dev/null +++ b/js/about/autofill.js @@ -0,0 +1,204 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const React = require('react') +const messages = require('../constants/messages') +const Immutable = require('immutable') +const ImmutableComponent = require('../components/immutableComponent') +const aboutActions = require('./aboutActions') +const Button = require('../components/button') + +const ipc = window.chrome.ipc + +require('../../less/about/autofill.less') +require('../../node_modules/font-awesome/css/font-awesome.css') + +class AddressItem extends ImmutableComponent { + constructor () { + super() + this.onDelete = this.onDelete.bind(this) + this.onEdit = this.onEdit.bind(this) + } + + onDelete () { + aboutActions.removeAutofillAddress(this.props.address.toJS()) + } + + onEdit () { + aboutActions.editAutofillAddress(this.props.address.toJS()) + } + + render () { + const address = this.props.address + return + + + + + {address.get('name')} + {address.get('organization')} + {address.get('streetAddress')} + {address.get('city')} + {address.get('state')} + {address.get('postalCode')} + {address.get('country')} + {address.get('phone')} + {address.get('email')} + + + + + + } +} + +class CreditCardItem extends ImmutableComponent { + constructor () { + super() + this.onDelete = this.onDelete.bind(this) + this.onEdit = this.onEdit.bind(this) + } + + onDelete () { + aboutActions.removeAutofillCreditCard(this.props.creditCard.toJS()) + } + + onEdit () { + aboutActions.editAutofillCreditCard(this.props.creditCard.toJS()) + } + + render () { + const creditCard = this.props.creditCard + return + + + + + {creditCard.get('name')} + {creditCard.get('card')} + + {creditCard.get('month') + '/' + creditCard.get('year')} + + + + + + + } +} + +class AboutAutofill extends React.Component { + constructor () { + super() + this.state = { + addressesDetails: new Immutable.List(), + creditCardsDetails: new Immutable.List() + } + ipc.on(messages.AUTOFILL_ADDRESSES_UPDATED, (e, detail) => { + if (detail) { + this.setState({ + addressesDetails: Immutable.fromJS(detail) + }) + } + }) + ipc.on(messages.AUTOFILL_CREDIT_CARDS_UPDATED, (e, detail) => { + if (detail) { + this.setState({ + creditCardsDetails: Immutable.fromJS(detail) + }) + } + }) + this.onAddAddress = this.onAddAddress.bind(this) + this.onAddCreditCard = this.onAddCreditCard.bind(this) + } + + onAddAddress () { + aboutActions.addAutofillAddress() + } + + onAddCreditCard () { + aboutActions.addAutofillCreditCard() + } + + get isAddresssEmpty () { + return !this.state.addressesDetails || !this.state.addressesDetails.size + } + + get isCreditCardsEmpty () { + return !this.state.creditCardsDetails || !this.state.creditCardsDetails.size + } + + render () { + var savedAddresssPage = this.isAddresssEmpty + ?
+ :
+

+
+ + + + + + + + + + + + + + + + + { + this.state.addressesDetails.sort((a, b) => { + return a.get('name') > b.get('name') ? 1 : -1 + }).map((item) => + ) + } + +
+
+

+ + var savedCreditCardsPage = this.isCreditCardsEmpty + ?
+ :
+

+
+ + + + + + + + + + + { + this.state.creditCardsDetails.sort((a, b) => { + return a.get('name') > b.get('name') ? 1 : -1 + }).map((item) => + ) + } + +
+
+

+ return
+

+ {savedAddresssPage} +

+ } +} + +module.exports = diff --git a/js/about/entry.js b/js/about/entry.js index 8891698c559..20351284766 100644 --- a/js/about/entry.js +++ b/js/about/entry.js @@ -38,6 +38,9 @@ switch (getBaseUrl(getSourceAboutUrl(window.location.href))) { break case 'about:history': element = require('./history') + break + case 'about:autofill': + element = require('./autofill') } if (element) { diff --git a/js/about/preferences.js b/js/about/preferences.js index 8b6564fe109..00f15f4519f 100644 --- a/js/about/preferences.js +++ b/js/about/preferences.js @@ -803,6 +803,14 @@ class SecurityTab extends ImmutableComponent { : null } +
+ + +
+
+ + + } +} + +module.exports = AutofillAddressPanel diff --git a/js/components/autofillCreditCardPanel.js b/js/components/autofillCreditCardPanel.js new file mode 100644 index 00000000000..0867dc3cdff --- /dev/null +++ b/js/components/autofillCreditCardPanel.js @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const React = require('react') +const ImmutableComponent = require('./immutableComponent') +const Dialog = require('./dialog') +const Button = require('./button') +const windowActions = require('../actions/windowActions') +const appActions = require('../actions/appActions') +const KeyCodes = require('../constants/keyCodes') + +class AutofillCreditCardPanel extends ImmutableComponent { + constructor () { + super() + this.onNameChange = this.onNameChange.bind(this) + this.onCardChange = this.onCardChange.bind(this) + this.onExpMonthChange = this.onExpMonthChange.bind(this) + this.onExpYearChange = this.onExpYearChange.bind(this) + this.onKeyDown = this.onKeyDown.bind(this) + this.onSave = this.onSave.bind(this) + this.onClick = this.onClick.bind(this) + } + onNameChange (e) { + let currentDetail = this.props.currentDetail + currentDetail = currentDetail.set('name', e.target.value) + windowActions.setAutofillCreditCardDetail(currentDetail, this.props.originalDetail) + } + onCardChange (e) { + let currentDetail = this.props.currentDetail + currentDetail = currentDetail.set('card', e.target.value) + windowActions.setAutofillCreditCardDetail(currentDetail, this.props.originalDetail) + } + onExpMonthChange (e) { + let currentDetail = this.props.currentDetail + currentDetail = currentDetail.set('month', e.target.value) + windowActions.setAutofillCreditCardDetail(currentDetail, this.props.originalDetail) + } + onExpYearChange (e) { + let currentDetail = this.props.currentDetail + currentDetail = currentDetail.set('year', e.target.value) + windowActions.setAutofillCreditCardDetail(currentDetail, this.props.originalDetail) + } + onKeyDown (e) { + switch (e.keyCode) { + case KeyCodes.ENTER: + this.onSave() + break + case KeyCodes.ESC: + this.props.onHide() + break + } + } + onSave () { + appActions.addAutofillCreditCard(this.props.currentDetail, this.props.originalDetail) + this.props.onHide() + } + onClick (e) { + e.stopPropagation() + } + get displayNameOnCard () { + return this.props.currentDetail.get('name') + } + get displayCreditCardNumber () { + return this.props.currentDetail.get('card') + } + render () { + var ExpMonth = [] + for (let i = 1; i <= 12; ++i) { + ExpMonth.push() + } + var ExpYear = [] + var today = new Date() + var year = today.getFullYear() + for (let i = year; i <= year + 9; ++i) { + ExpYear.push() + } + + return +
+
+
+
+
+
+
+
+
+
+
+
+
+ } +} + +module.exports = AutofillCreditCardPanel diff --git a/js/components/frame.js b/js/components/frame.js index 9b8c09b097b..74a700a9b6a 100644 --- a/js/components/frame.js +++ b/js/components/frame.js @@ -105,6 +105,9 @@ class Frame extends ImmutableComponent { } } else if (location === 'about:flash') { this.webview.send(messages.BRAVERY_DEFAULTS_UPDATED, this.braveryDefaults) + } else if (location === 'about:autofill') { + this.webview.send(messages.AUTOFILL_ADDRESSES_UPDATED, this.props.autofillAddresses.toJS()) + this.webview.send(messages.AUTOFILL_CREDIT_CARDS_UPDATED, this.props.autofillCreditCards.toJS()) } // send state to about pages @@ -593,6 +596,19 @@ class Frame extends ImmutableComponent { this.webview.addEventListener('page-title-updated', ({title}) => { windowActions.setFrameTitle(this.frame, title) }) + this.webview.addEventListener('show-autofill-settings', (e) => { + windowActions.newFrame({ location: 'about:autofill' }, true) + }) + this.webview.addEventListener('update-autofill-popup-data-list-values', (e) => { + console.log(e) + }) + this.webview.addEventListener('show-autofill-popup', (e) => { + contextMenus.onShowAutofillMenu(e.suggestions, e.rect, this.frame) + }) + this.webview.addEventListener('hide-autofill-popup', (e) => { + // TODO(Anthony): conflict with contextmenu + // windowActions.setContextMenuDetail() + }) this.webview.addEventListener('ipc-message', (e) => { let method = () => {} switch (e.channel) { @@ -651,6 +667,18 @@ class Frame extends ImmutableComponent { case messages.CLEAR_BROWSING_DATA_NOW: windowActions.setClearBrowsingDataDetail({}) break + case messages.ADD_AUTOFILL_ADDRESS: + windowActions.setAutofillAddressDetail({}, {}) + break + case messages.EDIT_AUTOFILL_ADDRESS: + windowActions.setAutofillAddressDetail(e.args[0], e.args[0]) + break + case messages.ADD_AUTOFILL_CREDIT_CARD: + windowActions.setAutofillCreditCardDetail({month: '1', year: new Date().getFullYear()}, {}) + break + case messages.EDIT_AUTOFILL_CREDIT_CARD: + windowActions.setAutofillCreditCardDetail(e.args[0], e.args[0]) + break } method.apply(this, e.args) }) diff --git a/js/components/main.js b/js/components/main.js index 2776a67c908..19a17786686 100644 --- a/js/components/main.js +++ b/js/components/main.js @@ -28,6 +28,8 @@ const Button = require('./button') const SiteInfo = require('./siteInfo') const BraveryPanel = require('./braveryPanel') const ClearBrowsingDataPanel = require('./clearBrowsingDataPanel') +const AutofillAddressPanel = require('./autofillAddressPanel') +const AutofillCreditCardPanel = require('./autofillCreditCardPanel') const AddEditBookmark = require('./addEditBookmark') const LoginRequired = require('./loginRequired') const ReleaseNotes = require('./releaseNotes') @@ -79,6 +81,8 @@ class Main extends ImmutableComponent { this.onHideSiteInfo = this.onHideSiteInfo.bind(this) this.onHideBraveryPanel = this.onHideBraveryPanel.bind(this) this.onHideClearBrowsingDataPanel = this.onHideClearBrowsingDataPanel.bind(this) + this.onHideAutofillAddressPanel = this.onHideAutofillAddressPanel.bind(this) + this.onHideAutofillCreditCardPanel = this.onHideAutofillCreditCardPanel.bind(this) this.onHideNoScript = this.onHideNoScript.bind(this) this.onHideReleaseNotes = this.onHideReleaseNotes.bind(this) this.onBraveMenu = this.onBraveMenu.bind(this) @@ -541,6 +545,14 @@ class Main extends ImmutableComponent { windowActions.setClearBrowsingDataDetail() } + onHideAutofillAddressPanel () { + windowActions.setAutofillAddressDetail() + } + + onHideAutofillCreditCardPanel () { + windowActions.setAutofillCreditCardDetail() + } + onHideNoScript () { windowActions.setNoScriptVisible(false) } @@ -687,6 +699,8 @@ class Main extends ImmutableComponent { const braveShieldsDisabled = this.braveShieldsDisabled const braveryPanelIsVisible = !braveShieldsDisabled && this.props.windowState.get('braveryPanelDetail') const clearBrowsingDataPanelIsVisible = this.props.windowState.get('clearBrowsingDataDetail') + const autofillAddressPanelIsVisible = this.props.windowState.get('autofillAddressDetail') + const autofillCreditCardPanelIsVisible = this.props.windowState.get('autofillCreditCardDetail') const activeRequestedLocation = this.activeRequestedLocation const noScriptIsVisible = this.props.windowState.getIn(['ui', 'noScriptInfo', 'isVisible']) const releaseNotesIsVisible = this.props.windowState.getIn(['ui', 'releaseNotes', 'isVisible']) @@ -697,6 +711,8 @@ class Main extends ImmutableComponent { !siteInfoIsVisible && !braveryPanelIsVisible && !clearBrowsingDataPanelIsVisible && + !autofillAddressPanelIsVisible && + !autofillCreditCardPanelIsVisible && !releaseNotesIsVisible && !noScriptIsVisible && activeFrame && !activeFrame.getIn(['security', 'loginRequiredDetail']) @@ -788,6 +804,22 @@ class Main extends ImmutableComponent { onHide={this.onHideClearBrowsingDataPanel} /> : null } + { + autofillAddressPanelIsVisible + ? + : null + } + { + autofillCreditCardPanelIsVisible + ? + : null + } { activeFrame && activeFrame.getIn(['security', 'loginRequiredDetail']) ? @@ -939,6 +971,8 @@ class Main extends ImmutableComponent { enableNoScript={this.enableNoScript(this.frameSiteSettings(frame.get('location')))} isPreview={frame.get('key') === this.props.windowState.get('previewFrameKey')} isActive={FrameStateUtil.isFrameKeyActive(this.props.windowState, frame.get('key'))} + autofillCreditCards={this.props.appState.getIn(['autofill', 'creditCards'])} + autofillAddresses={this.props.appState.getIn(['autofill', 'addresses'])} />) } diff --git a/js/constants/appConfig.js b/js/constants/appConfig.js index 5168aa55a44..a60fcd50a17 100644 --- a/js/constants/appConfig.js +++ b/js/constants/appConfig.js @@ -96,6 +96,7 @@ module.exports = { 'bookmarks.toolbar.showOnlyFavicon': false, 'payments.enabled': false, 'payments.contribution-amount': 5, // USD + 'privacy.privacy.autofill-enable': false, 'privacy.do-not-track': false, 'security.passwords.active-password-manager': null, // Set in settings.js by passwordManagerDefault (defaults to built in) 'security.passwords.manager-enabled': true, diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 907cc7c2869..da69d31ab5a 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -34,7 +34,11 @@ const AppConstants = { APP_HIDE_MESSAGE_BOX: _, /** @param {string} message */ APP_CLEAR_MESSAGE_BOXES: _, /** @param {string} origin */ APP_ADD_WORD: _, /** @param {string} word, @param {boolean} learn */ - APP_SET_DICTIONARY: _ /** @param {string} locale */ + APP_SET_DICTIONARY: _, /** @param {string} locale */ + APP_ADD_AUTOFILL_ADDRESS: _, + APP_REMOVE_AUTOFILL_ADDRESS: _, + APP_ADD_AUTOFILL_CREDIT_CARD: _, + APP_REMOVE_AUTOFILL_CREDIT_CARD: _ } module.exports = mapValuesByKeys(AppConstants) diff --git a/js/constants/messages.js b/js/constants/messages.js index b67b6724484..a161482724b 100644 --- a/js/constants/messages.js +++ b/js/constants/messages.js @@ -136,6 +136,14 @@ const messages = { CHECK_FLASH_INSTALLED: _, ABOUT_COMPONENT_INITIALIZED: _, CLEAR_BROWSING_DATA_NOW: _, + ADD_AUTOFILL_ADDRESS: _, + REMOVE_AUTOFILL_ADDRESS: _, + EDIT_AUTOFILL_ADDRESS: _, + ADD_AUTOFILL_CREDIT_CARD: _, + REMOVE_AUTOFILL_CREDIT_CARD: _, + EDIT_AUTOFILL_CREDIT_CARD: _, + AUTOFILL_ADDRESSES_UPDATED: _, + AUTOFILL_CREDIT_CARDS_UPDATED: _, // HTTPS CERT_ERROR_ACCEPTED: _, /** @arg {string} url where a cert error was accepted */ CHECK_CERT_ERROR_ACCEPTED: _, /** @arg {string} url to check cert error, @arg {number} key of frame */ diff --git a/js/constants/settings.js b/js/constants/settings.js index 2f4f2d459d0..b9f6422812e 100644 --- a/js/constants/settings.js +++ b/js/constants/settings.js @@ -32,6 +32,8 @@ const settings = { SHUTDOWN_CLEAR_DOWNLOADS: 'shutdown.clear-downloads', SHUTDOWN_CLEAR_CACHE: 'shutdown.clear-cache', SHUTDOWN_CLEAR_ALL_SITE_COOKIES: 'shutdown.clear-all-site-cookies', + // Autofill + AUTOFILL_ENABLED: 'privacy.autofill-enabled', // Security Tab: DEPRECATED but still required (for now) PASSWORD_MANAGER_ENABLED: 'security.passwords.manager-enabled', ONE_PASSWORD_ENABLED: 'security.passwords.one-password-enabled', diff --git a/js/constants/windowConstants.js b/js/constants/windowConstants.js index 9085a0e3718..295165007c1 100644 --- a/js/constants/windowConstants.js +++ b/js/constants/windowConstants.js @@ -64,7 +64,9 @@ const windowConstants = { WINDOW_SET_LOGIN_REQUIRED_DETAIL: _, WINDOW_SET_STATE: _, WINDOW_SET_LAST_ZOOM_PERCENTAGE: _, - WINDOW_SET_CLEAR_BROWSING_DATA_DETAIL: _ + WINDOW_SET_CLEAR_BROWSING_DATA_DETAIL: _, + WINDOW_SET_AUTOFILL_ADDRESS_DETAIL: _, + WINDOW_SET_AUTOFILL_CREDIT_CARD_DETAIL: _ } module.exports = mapValuesByKeys(windowConstants) diff --git a/js/contextMenus.js b/js/contextMenus.js index 5c635ace847..7988ae47c4b 100644 --- a/js/contextMenus.js +++ b/js/contextMenus.js @@ -353,6 +353,35 @@ function usernameTemplateInit (usernames, origin, action) { return items } +function autofillTemplateInit (suggestions, frame) { + let items = [] + for (let i = 0; i < suggestions.length; ++i) { + let value + let frontendId = suggestions[i].frontend_id + if (frontendId >= 0) { // POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY and Autofill Entry + value = suggestions[i].value + } else if (frontendId === -1) { // POPUP_ITEM_ID_WARNING_MESSAGE + value = 'Disabled due to unsecure connection.' + } else if (frontendId === -4) { // POPUP_ITEM_ID_CLEAR_FORM + value = 'Clear Form' + } else if (frontendId === -5) { // POPUP_ITEM_ID_AUTOFILL_OPTIONS + value = 'Autofill Settings' + } + if (frontendId === -3) { // POPUP_ITEM_ID_SEPARATOR + items.push(CommonMenu.separatorMenuItem) + } else { + items.push({ + label: value, + click: (item, focusedWindow) => { + ipc.send('autofill-selection-clicked', frame.get('tabId'), value, frontendId, i) + windowActions.setContextMenuDetail() + } + }) + } + } + return items +} + function tabTemplateInit (frameProps) { const frameKey = frameProps.get('key') const items = [] @@ -1037,6 +1066,15 @@ function onShowUsernameMenu (usernames, origin, action, boundingRect, })) } +function onShowAutofillMenu (suggestions, boundingRect, frame) { + const menuTemplate = autofillTemplateInit(suggestions, frame) + windowActions.setContextMenuDetail(Immutable.fromJS({ + left: boundingRect.x, + top: boundingRect.y, + template: menuTemplate + })) +} + function onMoreBookmarksMenu (activeFrame, allBookmarkItems, overflowItems, e) { const menuTemplate = moreBookmarksTemplateInit(allBookmarkItems, overflowItems, activeFrame) const rect = e.target.getBoundingClientRect() @@ -1144,6 +1182,7 @@ module.exports = { onBookmarkContextMenu, onShowBookmarkFolderMenu, onShowUsernameMenu, + onShowAutofillMenu, onMoreBookmarksMenu, onBackButtonHistoryMenu, onForwardButtonHistoryMenu diff --git a/js/lib/appUrlUtil.js b/js/lib/appUrlUtil.js index da24c34e1ba..285660cbce6 100644 --- a/js/lib/appUrlUtil.js +++ b/js/lib/appUrlUtil.js @@ -68,7 +68,8 @@ module.exports.aboutUrls = new Immutable.Map({ 'about:safebrowsing': module.exports.getAppUrl('about-safebrowsing.html'), 'about:passwords': module.exports.getAppUrl('about-passwords.html'), 'about:flash': module.exports.getAppUrl('about-flash.html'), - 'about:error': module.exports.getAppUrl('about-error.html') + 'about:error': module.exports.getAppUrl('about-error.html'), + 'about:autofill': module.exports.getAppUrl('about-autofill.html') }) module.exports.isIntermediateAboutPage = (location) => diff --git a/js/state/privacy.js b/js/state/privacy.js new file mode 100644 index 00000000000..89abe7d4fa7 --- /dev/null +++ b/js/state/privacy.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const AppDispatcher = require('../dispatcher/appDispatcher') +const AppStore = require('../stores/appStore') +const AppConstants = require('../constants/appConstants') +const settings = require('../constants/settings') +const { registerUserPrefs } = require('./userPrefs') + +const getAutofillEnabled = (appState) => { + let appSettings = appState.get('settings') + return appSettings.get(settings.AUTOFILL_ENABLED) +} + +const getPrivacySettings = (appState) => { + return { 'autofill.enabled': getAutofillEnabled(appState) } +} + +let updateTrigger + +// Register callback to handle all updates +const doAction = (action) => { + if (action.actionType === AppConstants.APP_CHANGE_SETTING) { + AppDispatcher.waitFor([AppStore.dispatchToken], () => { + updateTrigger() + }) + } +} + +module.exports.init = () => { + updateTrigger = registerUserPrefs(() => getPrivacySettings(AppStore.getState())) + AppDispatcher.register(doAction) +} diff --git a/js/stores/appStore.js b/js/stores/appStore.js index dff54de3e1b..ca6befc3e59 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -540,6 +540,68 @@ const handleAppAction = (action) => { Filtering.clearStorageData() } break + case AppConstants.APP_ADD_AUTOFILL_ADDRESS: + { + const Filtering = require('../../app/filtering') + if (appState.getIn(['autofill', 'addresses']) === undefined) { + appState = appState.setIn(['autofill', 'addresses'], new Immutable.List()) + } + appState = appState.setIn(['autofill', 'addresses'], + appState.getIn(['autofill', 'addresses']).filterNot((address) => { + return Immutable.is(address, action.originalDetail) + })) + if (action.originalDetail.toJS().guid !== undefined) { + Filtering.removeAutofillAddress(action.originalDetail.toJS().guid) + } + + let addresses = appState.getIn(['autofill', 'addresses']) + const guid = Filtering.addAutofillAddress(action.detail.toJS()) + let detail = action.detail + detail = detail.set('guid', Immutable.fromJS(guid)) + appState = appState.setIn(['autofill', 'addresses'], addresses.push(Immutable.fromJS(detail))) + break + } + case AppConstants.APP_REMOVE_AUTOFILL_ADDRESS: + { + const Filtering = require('../../app/filtering') + appState = appState.setIn(['autofill', 'addresses'], + appState.getIn(['autofill', 'addresses']).filterNot((address) => { + return Immutable.is(address, Immutable.fromJS(action.detail)) + })) + Filtering.removeAutofillAddress(action.detail.guid) + break + } + case AppConstants.APP_ADD_AUTOFILL_CREDIT_CARD: + { + const Filtering = require('../../app/filtering') + if (appState.getIn(['autofill', 'creditCards']) === undefined) { + appState = appState.setIn(['autofill', 'creditCards'], new Immutable.List()) + } + appState = appState.setIn(['autofill', 'creditCards'], + appState.getIn(['autofill', 'creditCards']).filterNot((card) => { + return Immutable.is(card, action.originalDetail) + })) + if (action.originalDetail.toJS().guid !== undefined) { + Filtering.removeAutofillCreditCard(action.originalDetail.toJS().guid) + } + + let creditCards = appState.getIn(['autofill', 'creditCards']) + const guid = Filtering.addAutofillCreditCard(action.detail.toJS()) + let detail = action.detail + detail = detail.set('guid', Immutable.fromJS(guid)) + appState = appState.setIn(['autofill', 'creditCards'], creditCards.push(Immutable.fromJS(detail))) + break + } + case AppConstants.APP_REMOVE_AUTOFILL_CREDIT_CARD: + { + const Filtering = require('../../app/filtering') + appState = appState.setIn(['autofill', 'creditCards'], + appState.getIn(['autofill', 'creditCards']).filterNot((card) => { + return Immutable.is(card, Immutable.fromJS(action.detail)) + })) + Filtering.removeAutofillCreditCard(action.detail.guid) + break + } default: } diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index a34c0bbb739..741d6702282 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -698,6 +698,30 @@ const doAction = (action) => { windowState = windowState.set('clearBrowsingDataDetail', Immutable.fromJS(action.clearBrowsingDataDetail)) } break + case WindowConstants.WINDOW_SET_AUTOFILL_ADDRESS_DETAIL: + if (!action.currentDetail && !action.originalDetail) { + windowState = windowState.delete('autofillAddressDetail') + } else { + windowState = windowState.mergeIn(['autofillAddressDetail'], { + currentDetail: action.currentDetail, + originalDetail: action.originalDetail + }) + } + // Since the input values of addresses are bound, we need to notify the controls sync. + windowStore.emitChanges() + break + case WindowConstants.WINDOW_SET_AUTOFILL_CREDIT_CARD_DETAIL: + if (!action.currentDetail && !action.originalDetail) { + windowState = windowState.delete('autofillCreditCardDetail') + } else { + windowState = windowState.mergeIn(['autofillCreditCardDetail'], { + currentDetail: action.currentDetail, + originalDetail: action.originalDetail + }) + } + // Since the input values of credit cards are bound, we need to notify the controls sync. + windowStore.emitChanges() + break case WindowConstants.WINDOW_SET_DOWNLOADS_TOOLBAR_VISIBLE: windowState = windowState.setIn(['ui', 'downloadsToolbar', 'isVisible'], action.isVisible) break diff --git a/less/about/autofill.less b/less/about/autofill.less new file mode 100644 index 00000000000..65f47015f92 --- /dev/null +++ b/less/about/autofill.less @@ -0,0 +1,59 @@ +@import "./common.less"; + +.autofillPage { + margin: 20px; + + .autofillPageContent { + border-top: 1px solid @chromeBorderColor; + + .autofillList { + padding-top: 10px; + overflow: hidden; + } + } +} + +th { + padding: 8px; + text-align: left; +} + +tr { + cursor: default; + overflow: hidden; + white-space: nowrap; + + padding: 12px; + -webkit-user-select: none; + + .autofillItem { + display: flex; + } + + &:hover :not(th) { + background-color: lighten(@highlightBlue, 30%); + } +} + +td:not(.autofillActions) { + padding-right: 8px; + padding-left: 8px; +} + +.autofillAction { + cursor: pointer; + padding: 8px; + &:hover { + background-color: lighten(@highlightBlue, 20%); + } +} + +.autofillPageFooter { + padding: 10px; + margin-bottom: 20px; + span { + color: grey; + cursor: pointer; + text-decoration: underline; + } +} diff --git a/less/about/preferences.less b/less/about/preferences.less index 91aa5c774a8..70019c98ed7 100644 --- a/less/about/preferences.less +++ b/less/about/preferences.less @@ -292,6 +292,12 @@ span.browserButton.primaryButton.clearBrowsingDataButton { margin-top: 20px; } +span.browserButton.primaryButton.manageAutofillDataButton { + font-size: 0.9em; + padding: 5px 20px; + margin-top: 20px; +} + .settingsList { .settingItem { diff --git a/less/forms.less b/less/forms.less index b1ffc72421e..324098a195b 100644 --- a/less/forms.less +++ b/less/forms.less @@ -99,6 +99,34 @@ } } +.manageAutofillDataPanel { + .manageAutofillData { + .flyoutDialog; + background-color: #f7f7f7; + border-radius: @borderRadius; + max-width: 450px; + padding: 0; + text-align: left; + width: 473px; + -webkit-user-select: none; + cursor: default; + color: #3B3B3B; + overflow-y: auto; + max-height: 100%; + + .clickable { + color: #5B5B5B; + &:hover { + color: #000; + } + } + + .formSelect { + width: 5.5em + } + } +} + .braveryPanelContainer { .braveryPanel {