From 3b947887d530612a47fb7b1cad39f3f838372907 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 8b50f402de8..8e5c9e3fcdc 100644 --- a/app/browser/menu.js +++ b/app/browser/menu.js @@ -61,18 +61,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 @@ -390,9 +378,7 @@ const createBookmarksSubmenu = () => { }, CommonMenu.separatorMenuItem, CommonMenu.bookmarksManagerMenuItem(), - CommonMenu.bookmarksToolbarMenuItem(), - CommonMenu.separatorMenuItem, - CommonMenu.importBookmarksMenuItem() + CommonMenu.bookmarksToolbarMenuItem() ] const bookmarks = menuUtil.createBookmarkMenuItems(appStore.getState().get('sites')) @@ -472,6 +458,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()) } @@ -554,6 +542,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 03c1b48e0b9..ffc355fa776 100644 --- a/app/extensions/brave/locales/en-US/app.properties +++ b/app/extensions/brave/locales/en-US/app.properties @@ -202,3 +202,11 @@ dismissDenyRunInsecureContent=Stay Insecure denyRunInsecureContent=Stop Loading Unsafe Scripts runInsecureContentWarning=This page is trying to load scripts from insecure sources. If you allow this content to run it will not be encrypted and it may transmit unencrypted data to other sites. denyRunInsecureContentWarning=This page is currently loading scripts from insecure sources. +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 fd0b648d0fb..be6e3a4051b 100644 --- a/app/locale.js +++ b/app/locale.js @@ -145,7 +145,7 @@ var rendererIdentifiers = function () { 'preferences', 'settings', 'bookmarksManager', - 'importBookmarks', + 'importBrowserData', 'reportAnIssue', 'submitFeedback', 'bookmarksToolbar', @@ -207,7 +207,8 @@ var rendererIdentifiers = function () { 'downloadItemDelete', 'downloadItemClear', 'downloadToolbarHide', - 'downloadItemClearCompleted' + 'downloadItemClearCompleted', + '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 0cc3cbde426..7b70c33939d 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 b6cf143c2ee..7ca876d4b8f 100644 --- a/docs/state.md +++ b/docs/state.md @@ -506,6 +506,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 8031d182346..1d99b4cc0a8 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 { } +
+