From 2d910418845ad2d23135915ef82597d6bb7df963 Mon Sep 17 00:00:00 2001 From: Anthony Tseng Date: Fri, 16 Sep 2016 16:48:24 +0800 Subject: [PATCH] Add Importer support fix #2107 requires https://github.com/brave/electron/pull/57 Auditors: @bridiver, @bbondy, @bsclifton --- app/aboutDialog.js | 14 ++ app/browser/menu.js | 20 +- app/common/commonMenu.js | 24 +-- .../brave/locales/en-US/app.properties | 8 + .../brave/locales/en-US/menu.properties | 2 +- .../locales/en-US/preferences.properties | 2 + app/importer.js | 181 ++++++++++++++++++ app/index.js | 10 + app/locale.js | 5 +- .../components/importBrowserDataPanel.js | 121 ++++++++++++ docs/appActions.md | 10 + docs/state.md | 18 ++ docs/windowActions.md | 20 ++ js/about/aboutActions.js | 4 + js/about/preferences.js | 12 ++ js/actions/appActions.js | 11 ++ js/actions/windowActions.js | 22 +++ js/components/main.js | 21 ++ js/constants/appConstants.js | 1 + js/constants/messages.js | 4 +- js/constants/windowConstants.js | 2 + js/contextMenus.js | 6 +- js/stores/appStore.js | 12 ++ js/stores/windowStore.js | 30 ++- less/about/preferences.less | 1 + less/forms.less | 49 +++++ 26 files changed, 552 insertions(+), 58 deletions(-) create mode 100644 app/importer.js create mode 100644 app/renderer/components/importBrowserDataPanel.js diff --git a/app/aboutDialog.js b/app/aboutDialog.js index 291ee48993d..1bdebb1c572 100644 --- a/app/aboutDialog.js +++ b/app/aboutDialog.js @@ -25,3 +25,17 @@ ${locale.translation('licenseText')}`, }) }, 50) } + +module.exports.showImportWarning = function () { + // The timeout is in case there's a call just after the modal to hide the menu. + // showMessageBox is a modal and blocks everything otherwise, so menu would remain open + // while the dialog is displayed. + setTimeout(() => { + dialog.showMessageBox({ + title: 'Brave', + message: `${locale.translation('closeFirefoxWarning')}`, + icon: path.join(__dirname, '..', 'app', 'extensions', 'brave', 'img', 'braveAbout.png'), + buttons: ['Ok'] + }) + }, 50) +} diff --git a/app/browser/menu.js b/app/browser/menu.js index f0e7a8934ad..5bc928acde6 100644 --- a/app/browser/menu.js +++ b/app/browser/menu.js @@ -62,18 +62,6 @@ const createFileSubmenu = () => { } }, CommonMenu.separatorMenuItem, - /* - { - label: locale.translation('importFrom'), - visible: false - submenu: [ - {label: 'Google Chrome...'}, - {label: 'Firefox...'}, - {label: 'Safari...'} - ] - }, - CommonMenu.separatorMenuItem, - */ { // this should be disabled when // no windows are active @@ -391,9 +379,7 @@ const createBookmarksSubmenu = () => { }, CommonMenu.separatorMenuItem, CommonMenu.bookmarksManagerMenuItem(), - CommonMenu.bookmarksToolbarMenuItem(), - CommonMenu.separatorMenuItem, - CommonMenu.importBookmarksMenuItem() + CommonMenu.bookmarksToolbarMenuItem() ] const bookmarks = menuUtil.createBookmarkMenuItems(appStore.getState().get('sites')) @@ -473,6 +459,8 @@ const createHelpSubmenu = () => { submenu.push(CommonMenu.separatorMenuItem) submenu.push(CommonMenu.checkForUpdateMenuItem()) submenu.push(CommonMenu.separatorMenuItem) + submenu.push(CommonMenu.importBrowserDataMenuItem()) + submenu.push(CommonMenu.separatorMenuItem) submenu.push(CommonMenu.aboutBraveMenuItem()) } @@ -555,6 +543,8 @@ const createMenu = () => { CommonMenu.separatorMenuItem, CommonMenu.checkForUpdateMenuItem(), CommonMenu.separatorMenuItem, + CommonMenu.importBrowserDataMenuItem(), + CommonMenu.separatorMenuItem, CommonMenu.preferencesMenuItem(), CommonMenu.separatorMenuItem, { diff --git a/app/common/commonMenu.js b/app/common/commonMenu.js index 7c568472ad1..7635dd86412 100644 --- a/app/common/commonMenu.js +++ b/app/common/commonMenu.js @@ -243,31 +243,17 @@ module.exports.passwordsMenuItem = () => { } } -module.exports.importBookmarksMenuItem = () => { +module.exports.importBrowserDataMenuItem = () => { return { - label: locale.translation('importBookmarks'), + label: locale.translation('importBrowserData'), click: function (item, focusedWindow) { - if (BrowserWindow.getAllWindows().length === 0) { - appActions.newWindow(undefined, undefined, undefined, function () { - // The timeout here isn't necessary but giving the window a bit of time to popup - // before the modal file picker pops up seems to work nicer. - setTimeout(() => - module.exports.sendToFocusedWindow(BrowserWindow.getAllWindows()[0], [messages.IMPORT_BOOKMARKS]), 100) - }) - return + if (process.type === 'browser') { + process.emit(messages.IMPORT_BROWSER_DATA_NOW) } else { - setTimeout(() => - module.exports.sendToFocusedWindow(BrowserWindow.getAllWindows()[0], [messages.IMPORT_BOOKMARKS]), 100) + electron.ipcRenderer.send(messages.IMPORT_BROWSER_DATA_NOW) } } } - /* - submenu: [ - {label: 'Google Chrome...'}, - {label: 'Firefox...'}, - {label: 'Safari...'} - ] - */ } module.exports.reportAnIssueMenuItem = () => { diff --git a/app/extensions/brave/locales/en-US/app.properties b/app/extensions/brave/locales/en-US/app.properties index bffc6ff33f5..40e6075b5c8 100644 --- a/app/extensions/brave/locales/en-US/app.properties +++ b/app/extensions/brave/locales/en-US/app.properties @@ -206,3 +206,11 @@ windowCaptionButtonMinimize=Minimize windowCaptionButtonMaximize=Maximize windowCaptionButtonRestore=Restore Down windowCaptionButtonClose=Close +importBrowserData=Import browser data +import=Import +importDataWarning=Note: Each browser has a different set of importable data. +importDataCloseBrowserWarning=Please make sure the selected browser is closed before importing your data. +closeFirefoxWarning=Firefox must be closed during data import. Please close and try again. +favoritesOrBookmarks=Favorites/Bookmarks +mergeIntoBookmarksToolbar=Merge Favorites into Bookmarks Toolbar +cookies=Cookies diff --git a/app/extensions/brave/locales/en-US/menu.properties b/app/extensions/brave/locales/en-US/menu.properties index 5188e23ad65..7e85ec68d66 100644 --- a/app/extensions/brave/locales/en-US/menu.properties +++ b/app/extensions/brave/locales/en-US/menu.properties @@ -78,7 +78,7 @@ checkForUpdates=Check for Updates… preferences=Preferences… settings=Settings… bookmarksManager=Bookmarks Manager… -importBookmarks=Import Bookmarks (from HTML export)… +importBrowserData=Import Browser Data… reportAnIssue=Report an Issue submitFeedback=Submit Feedback… bookmarksToolbar=Bookmarks Toolbar diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties index 7427dd98096..0b26dcad5ea 100644 --- a/app/extensions/brave/locales/en-US/preferences.properties +++ b/app/extensions/brave/locales/en-US/preferences.properties @@ -216,3 +216,5 @@ clearBrowsingDataNow=Clear Browsing Data Now… autofillSettings=Autofill Settings manageAutofillData=Manage Autofill Data… enableAutofill=Enable Autofill +importBrowserData=Import Browser Data +importNow=Import now… diff --git a/app/importer.js b/app/importer.js new file mode 100644 index 00000000000..a28dbf3e887 --- /dev/null +++ b/app/importer.js @@ -0,0 +1,181 @@ +/* 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/. */ + +'strict mode' + +const electron = require('electron') +const importer = electron.importer +const dialog = electron.dialog +const BrowserWindow = electron.BrowserWindow +const session = electron.session +const Immutable = require('immutable') +const showImportWarning = require('./aboutDialog').showImportWarning +const siteUtil = require('../js/state/siteUtil') +const AppStore = require('../js/stores/appStore') +const siteTags = require('../js/constants/siteTags') +const appActions = require('../js/actions/appActions') +const messages = require('../js/constants/messages') + +var isMergeFavorites = false + +exports.init = () => { + importer.initialize() +} + +exports.importData = (selected) => { + if (selected.get('mergeFavorites')) { + isMergeFavorites = true + } + if (selected !== undefined) { + importer.importData(selected.toJS()) + } +} + +exports.importHTML = (selected) => { + if (selected.get('mergeFavorites')) { + isMergeFavorites = true + } + const files = dialog.showOpenDialog({ + properties: ['openFile'], + filters: [{ + name: 'HTML', + extensions: ['html', 'htm'] + }] + }) + if (files && files.length > 0) { + const file = files[0] + importer.importHTML(file) + } +} + +importer.on('update-supported-browsers', (e, detail) => { + isMergeFavorites = false + if (BrowserWindow.getFocusedWindow()) { + BrowserWindow.getFocusedWindow().webContents.send(messages.IMPORTER_LIST, detail) + } +}) + +importer.on('show-warning-dialog', (e) => { +}) + +importer.on('add-password-form', (e, detail) => { +}) + +importer.on('add-history-page', (e, history, visitSource) => { + let sites = [] + for (let i = 0; i < history.length; ++i) { + const site = { + title: history[i].title, + location: history[i].url, + lastAccessedTime: history[i].last_visit * 1000 + } + sites.push(site) + } + appActions.addSite(Immutable.fromJS(sites)) +}) + +importer.on('add-homepage', (e, detail) => { +}) + +importer.on('add-bookmarks', (e, bookmarks, topLevelFolder) => { + let nextFolderId = siteUtil.getNextFolderId(AppStore.getState().get('sites')) + let pathMap = {} + let sites = [] + let topLevelFolderId = 0 + if (!isMergeFavorites) { + topLevelFolderId = nextFolderId++ + sites.push({ + title: topLevelFolder, + folderId: topLevelFolderId, + parentFolderId: 0, + lastAccessedTime: (new Date()).getTime(), + tags: [siteTags.BOOKMARK_FOLDER] + }) + } else { + // Merge into existing bookmark toolbar + pathMap[topLevelFolder] = topLevelFolderId + pathMap['Bookmarks Toolbar'] = 0 // Firefox + pathMap['Bookmarks Bar'] = 0 // Chrome on mac + pathMap['Other Bookmarks'] = -1 // Chrome on mac + pathMap['Bookmarks bar'] = 0 // Chrome on win/linux + pathMap['Other bookmarks'] = -1 // Chrome on win/linux + pathMap['Bookmark Bar'] = 0 // Safari + pathMap['Links'] = 0 // Edge, IE + } + for (let i = 0; i < bookmarks.length; ++i) { + const pathLen = bookmarks[i].path.length + let parentFolderId = topLevelFolderId + if (pathLen) { + const parentFolder = bookmarks[i].path[pathLen - 1] + parentFolderId = pathMap[parentFolder] + if (parentFolderId === undefined) { + parentFolderId = nextFolderId++ + pathMap[parentFolder] = parentFolderId + const folder = { + title: parentFolder, + folderId: parentFolderId, + parentFolderId: pathMap[bookmarks[i].path[pathLen - 2]] === undefined ? topLevelFolderId : pathMap[bookmarks[i].path[pathLen - 2]], + lastAccessedTime: (new Date()).getTime(), + tags: [siteTags.BOOKMARK_FOLDER] + } + sites.push(folder) + } + } + if (bookmarks[i].is_folder) { + const folderId = nextFolderId++ + pathMap[bookmarks[i].title] = folderId + const folder = { + title: bookmarks[i].title, + folderId: folderId, + parentFolderId: parentFolderId, + lastAccessedTime: bookmarks[i].creation_time * 1000, + tags: [siteTags.BOOKMARK_FOLDER] + } + sites.push(folder) + } else { + const site = { + title: bookmarks[i].title, + location: bookmarks[i].url, + parentFolderId: parentFolderId, + lastAccessedTime: bookmarks[i].creation_time * 1000, + tags: [siteTags.BOOKMARK] + } + sites.push(site) + } + } + appActions.addSite(Immutable.fromJS(sites)) +}) + +importer.on('add-favicons', (e, detail) => { +}) + +importer.on('add-keywords', (e, templateUrls, uniqueOnHostAndPath) => { +}) + +importer.on('add-autofill-form-data-entries', (e, detail) => { +}) + +importer.on('add-cookies', (e, cookies) => { + for (let i = 0; i < cookies.length; ++i) { + const cookie = { + url: cookies[i].url, + name: cookies[i].name, + value: cookies[i].value, + domain: cookies[i].domain, + path: cookies[i].path, + secure: cookies[i].secure, + httpOnly: cookies[i].httponly, + expirationDate: cookies[i].expiry_date + } + session.defaultSession.cookies.set(cookie, (error) => { + if (error) { + console.error(error) + } + }) + } +}) + +importer.on('show-warning-dialog', (e) => { + showImportWarning() +}) diff --git a/app/index.js b/app/index.js index 52e733a2d0d..cabc55280ac 100644 --- a/app/index.js +++ b/app/index.js @@ -34,6 +34,7 @@ const ipcMain = electron.ipcMain const Immutable = require('immutable') const Menu = require('./browser/menu') const Updater = require('./updater') +const Importer = require('./importer') const messages = require('../js/constants/messages') const appConfig = require('../js/constants/appConfig') const appActions = require('../js/actions/appActions') @@ -686,6 +687,10 @@ app.on('ready', () => { } }) + ipcMain.on(messages.IMPORT_BROWSER_DATA_NOW, () => { + Importer.init() + }) + // Setup the crash handling CrashHerald.init() @@ -709,6 +714,11 @@ app.on('ready', () => { process.on(messages.UPDATE_META_DATA_RETRIEVED, (metadata) => { console.log(metadata) }) + + // This is fired by a menu entry + process.on(messages.IMPORT_BROWSER_DATA_NOW, () => { + Importer.init() + }) }) }) }) diff --git a/app/locale.js b/app/locale.js index 14006d0b5b8..9417d1b428c 100644 --- a/app/locale.js +++ b/app/locale.js @@ -145,7 +145,7 @@ var rendererIdentifiers = function () { 'preferences', 'settings', 'bookmarksManager', - 'importBookmarks', + 'importBrowserData', 'reportAnIssue', 'submitFeedback', 'bookmarksToolbar', @@ -212,7 +212,8 @@ var rendererIdentifiers = function () { 'windowCaptionButtonMinimize', 'windowCaptionButtonMaximize', 'windowCaptionButtonRestore', - 'windowCaptionButtonClose' + 'windowCaptionButtonClose', + 'closeFirefoxWarning' ] } diff --git a/app/renderer/components/importBrowserDataPanel.js b/app/renderer/components/importBrowserDataPanel.js new file mode 100644 index 00000000000..09042743e77 --- /dev/null +++ b/app/renderer/components/importBrowserDataPanel.js @@ -0,0 +1,121 @@ +/* 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('../../../js/components/immutableComponent') +const Dialog = require('../../../js/components/dialog') +const Button = require('../../../js/components/button') +const SwitchControl = require('../../../js/components/switchControl') +const windowActions = require('../../../js/actions/windowActions') +const appActions = require('../../../js/actions/appActions') + +class ImportBrowserDataPanel extends ImmutableComponent { + constructor () { + super() + this.onToggleHistory = this.onToggleSetting.bind(this, 'history') + this.onToggleFavorites = this.onToggleSetting.bind(this, 'favorites') + this.onToggleMergeFavorites = this.onToggleSetting.bind(this, 'mergeFavorites') + this.onToggleCookies = this.onToggleSetting.bind(this, 'cookies') + this.onImport = this.onImport.bind(this) + this.onChange = this.onChange.bind(this) + } + onToggleSetting (setting, e) { + if (setting === 'favorites') { + this.props.importBrowserDataSelected = + this.props.importBrowserDataSelected.set('mergeFavorites', e.target.value) + } + windowActions.setImportBrowserDataSelected(this.props.importBrowserDataSelected.set(setting, e.target.value)) + } + get browserData () { + let index = this.props.importBrowserDataSelected.get('index') + if (index === undefined) { + index = '0' + } + return this.props.importBrowserDataDetail.get(index) + } + get supportHistory () { + let browserData = this.browserData + if (browserData === undefined) { + return false + } + return browserData.get('history') + } + get supportFavorites () { + let browserData = this.browserData + if (browserData === undefined) { + return false + } + return browserData.get('favorites') + } + get supportCookies () { + let browserData = this.browserData + if (browserData === undefined) { + return false + } + return browserData.get('cookies') + } + onImport () { + let index = this.props.importBrowserDataSelected.get('index') + if (index === undefined) { + this.props.importBrowserDataSelected = this.props.importBrowserDataSelected.set('index', '0') + } + let browserData = this.browserData + if (browserData !== undefined) { + let type = browserData.get('type') + this.props.importBrowserDataSelected = this.props.importBrowserDataSelected.set('type', type) + } + appActions.importBrowserData(this.props.importBrowserDataSelected) + this.props.onHide() + } + onChange (e) { + windowActions.setImportBrowserDataSelected( + this.props.importBrowserDataSelected.set('index', e.target.value)) + } + get selectedBrowser () { + let index = this.props.importBrowserDataSelected.get('index') + return index !== undefined ? index : '0' + } + render () { + var browsers = [] + if (this.props.importBrowserDataDetail !== undefined) { + this.props.importBrowserDataDetail.toJS().forEach((browser) => { + browsers.push() + }) + } + return +
e.stopPropagation()}> +
+
+ + + +
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+ } +} + +module.exports = ImportBrowserDataPanel diff --git a/docs/appActions.md b/docs/appActions.md index 0b8e110151a..906c03d9739 100644 --- a/docs/appActions.md +++ b/docs/appActions.md @@ -349,6 +349,16 @@ Clears the data specified in dataDetail +### importBrowserData(selected) + +Import browser data specified in selected + +**Parameters** + +**selected**: `object`, the browser data to import as per doc/state.md's importBrowserDataSelected + + + ### addAutofillAddress(detail, originalDetail) Add address data diff --git a/docs/state.md b/docs/state.md index 9ddfe464ba0..bcc3129dc97 100644 --- a/docs/state.md +++ b/docs/state.md @@ -515,6 +515,24 @@ WindowStore month: string, year: string, guid: Object.> // map of (partition, id) used to access the autofill entry in database + }, + importBrowserDataDetail: [ + { + index: string, + type: number, + name: string, + history: boolean, + favorites: boolean, + cookies: boolean + } + ], + importBrowserDataSelected: { + index: string, + type: number, + history: boolean, + favorites: boolean, + mergeFavorites: boolean, + cookies: boolean } } ``` diff --git a/docs/windowActions.md b/docs/windowActions.md index 4a6c6c7fcb1..80179b9f1a2 100644 --- a/docs/windowActions.md +++ b/docs/windowActions.md @@ -751,6 +751,26 @@ Sets the clear browsing data popup detail +### setImportBrowserDataDetail(importBrowserDataDetail) + +Sets the import browser data popup detail + +**Parameters** + +**importBrowserDataDetail**: `Array`, list of supported browsers + + + +### setImportBrowserDataSelected(selected) + +Sets the selected import browser data + +**Parameters** + +**selected**: `Object`, selected browser data to import + + + ### setAutofillAddressDetail(currentDetail, originalDetail) Sets the manage autofill address popup detail diff --git a/js/about/aboutActions.js b/js/about/aboutActions.js index 27e612aa99f..cde4175a0be 100644 --- a/js/about/aboutActions.js +++ b/js/about/aboutActions.js @@ -168,6 +168,10 @@ const AboutActions = { ipc.sendToHost(messages.CLEAR_BROWSING_DATA_NOW, clearBrowsingDataDetail) }, + importBrowerDataNow: function () { + ipc.send(messages.IMPORT_BROWSER_DATA_NOW) + }, + createWallet: function () { ipc.send(messages.LEDGER_CREATE_WALLET) }, diff --git a/js/about/preferences.js b/js/about/preferences.js index 1046e0c025a..31b96301a5d 100644 --- a/js/about/preferences.js +++ b/js/about/preferences.js @@ -532,6 +532,15 @@ class PaymentHistoryRow extends ImmutableComponent { } class GeneralTab extends ImmutableComponent { + constructor (e) { + super() + this.importBrowserDataNow = this.importBrowserDataNow.bind(this) + } + + importBrowserDataNow () { + aboutActions.importBrowerDataNow() + } + enabled (keyArray) { return keyArray.every((key) => getSetting(key, this.props.settings) === true) } @@ -581,6 +590,9 @@ class GeneralTab extends ImmutableComponent { } +
+