diff --git a/app/common/state/defaultBrowserState.js b/app/common/state/defaultBrowserState.js new file mode 100644 index 00000000000..9e2c833e4c8 --- /dev/null +++ b/app/common/state/defaultBrowserState.js @@ -0,0 +1,12 @@ +/* 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 getSetting = require('../../../js/settings').getSetting +const settings = require('../../../js/constants/settings') + +module.exports.shouldDisplayDialog = (state) => { + return !getSetting(settings.IS_DEFAULT_BROWSER) && + !state.get('defaultBrowserCheckComplete') && + getSetting(settings.CHECK_DEFAULT_ON_STARTUP) +} diff --git a/app/extensions/brave/locales/en-US/app.properties b/app/extensions/brave/locales/en-US/app.properties index 905a6555e2e..787ea3ffa7c 100644 --- a/app/extensions/brave/locales/en-US/app.properties +++ b/app/extensions/brave/locales/en-US/app.properties @@ -223,3 +223,7 @@ cookies=Cookies licenseTextOk=Ok closeFirefoxWarningOk=Ok importSuccessOk=Ok +makeBraveDefault=Ready to make Brave your default Browser? +checkDefaultOnStartup=Always check on startup +useBrave=Use Brave +notNow=Not Now diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties index 6bca8a185a8..8ee99a137ac 100644 --- a/app/extensions/brave/locales/en-US/preferences.properties +++ b/app/extensions/brave/locales/en-US/preferences.properties @@ -249,3 +249,7 @@ sendUsageStatistics=Automatically send usage statistics to Brave bookmarksBarTextOnly=Text only bookmarksBarTextAndFavicon=Text and Favicons bookmarksBarFaviconOnly=Favicons only +defaultBrowser=Brave is your default browser. +notDefaultBrowser=Brave is not your default browser. +setAsDefault=Set as default… +checkDefaultOnStartup=Always check on startup diff --git a/app/index.js b/app/index.js index e2fb842f523..f4a4386a8a4 100644 --- a/app/index.js +++ b/app/index.js @@ -82,6 +82,7 @@ const privacy = require('../js/state/privacy') const basicAuth = require('./browser/basicAuth') const async = require('async') const tabs = require('./browser/tabs') +const settings = require('../js/constants/settings') // temporary fix for #4517, #4518 and #4472 app.commandLine.appendSwitch('enable-use-zoom-for-dsf', 'false') @@ -107,6 +108,8 @@ const prefsRestartLastValue = {} const unsafeTestMasterKey = 'c66af15fc6555ebecf7cee3a5b82c108fd3cb4b587ab0b299d28e39c79ecc708' +const defaultProtocols = ['http', 'https'] + const sessionStoreQueue = async.queue((task, callback) => { task(callback) }, 1) @@ -441,6 +444,10 @@ app.on('ready', () => { } process.emit(messages.APP_INITIALIZED) + // Default browser checking + let isDefaultBrowser = defaultProtocols.every(p => app.isDefaultProtocolClient(p)) + appActions.changeSetting(settings.IS_DEFAULT_BROWSER, isDefaultBrowser) + if (CmdLine.newWindowURL) { appActions.newWindow(Immutable.fromJS({ location: CmdLine.newWindowURL diff --git a/app/renderer/components/checkDefaultBrowserDialog.js b/app/renderer/components/checkDefaultBrowserDialog.js new file mode 100644 index 00000000000..41f06bf4134 --- /dev/null +++ b/app/renderer/components/checkDefaultBrowserDialog.js @@ -0,0 +1,51 @@ +/* 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 appActions = require('../../../js/actions/appActions') +const windowActions = require('../../../js/actions/windowActions') +const settings = require('../../../js/constants/settings') + +class CheckDefaultBrowserDialog extends ImmutableComponent { + constructor () { + super() + this.onCheckDefaultOnStartup = this.onCheckDefaultOnStartup.bind(this) + this.onNotNow = this.onNotNow.bind(this) + this.onUseBrave = this.onUseBrave.bind(this) + } + + onCheckDefaultOnStartup (e) { + windowActions.setModalDialogDetail('checkDefaultBrowserDialog', {checkDefaultOnStartup: e.target.value}) + } + onNotNow () { + appActions.defaultBrowserUpdated(false) + appActions.defaultBrowserCheckComplete() + appActions.changeSetting(settings.CHECK_DEFAULT_ON_STARTUP, this.props.checkDefaultOnStartup) + } + onUseBrave () { + appActions.defaultBrowserUpdated(true) + appActions.defaultBrowserCheckComplete() + appActions.changeSetting(settings.CHECK_DEFAULT_ON_STARTUP, this.props.checkDefaultOnStartup) + } + render () { + return +
e.stopPropagation()}> +
+
+ +
+
+
+
+ } +} + +module.exports = CheckDefaultBrowserDialog diff --git a/app/sessionStore.js b/app/sessionStore.js index 3309bf64b44..ea3da5b6265 100644 --- a/app/sessionStore.js +++ b/app/sessionStore.js @@ -234,6 +234,8 @@ module.exports.cleanAppData = (data, isShutdown) => { data.temporarySiteSettings = {} // Delete Flash state since this is checked on startup delete data.flashInitialized + // Delete defaultBrowserCheckComplete state since this is checked on startup + delete data.defaultBrowserCheckComplete // Delete Recovery status on shut down try { delete data.ui.about.preferences.recoverySucceeded diff --git a/docs/appActions.md b/docs/appActions.md index a8647eac992..2d1ceb23418 100644 --- a/docs/appActions.md +++ b/docs/appActions.md @@ -466,6 +466,22 @@ Dispatches a message to submit feedback +### defaultBrowserUpdated(useBrave) + +Dispatch a message to set default browser + +**Parameters** + +**useBrave**: `boolean`, whether set Brave as default browser + + + +### defaultBrowserCheckComplete() + +Dispatch a message to indicate default browser check is complete + + + * * * diff --git a/docs/state.md b/docs/state.md index c8844ee51ec..3bafe3e2d30 100644 --- a/docs/state.md +++ b/docs/state.md @@ -150,6 +150,8 @@ AppStore 'general.downloads.default-save-path': string, // default path for saving files 'general.autohide-menu': boolean, // true if the Windows menu should be autohidden 'general.disable-title-mode': boolean, // true if title mode should always be disabled + 'general.check-default-on-startup': boolean, // true to check whether brave is default browser on startup + 'general.is-default-browser': boolean, // true if brave is default browser 'search.default-search-engine': string, // name of search engine, from js/data/searchProviders.js 'search.offer-search-suggestions': boolean, // true if suggestions should be offered from the default search engine when available. 'tabs.switch-to-new-tabs': boolean, // true if newly opened tabs should be focused immediately @@ -205,7 +207,8 @@ AppStore }, menu: { template: object // used on Windows and by our tests: template object with Menubar control - } + }, + defaultBrowserCheckComplete: boolean // true to indicate default browser check is complete } ``` @@ -527,6 +530,12 @@ WindowStore favorites: boolean, mergeFavorites: boolean, cookies: boolean + }, + modalDialogDetail: { + [className]: { + Object // props + }, + ... } } ``` diff --git a/docs/windowActions.md b/docs/windowActions.md index bd7426feca1..b7a9c27a35c 100644 --- a/docs/windowActions.md +++ b/docs/windowActions.md @@ -914,6 +914,18 @@ Fired when window receives or loses focus +### setModalDialogDetail(className, props) + +Set Modal Dialog detail + +**Parameters** + +**className**: `string`, name of modal dialog + +**props**: `Object`, properties of the modal dialog + + + * * * diff --git a/js/about/aboutActions.js b/js/about/aboutActions.js index 0b49bdc1ae3..76fbf8159a6 100644 --- a/js/about/aboutActions.js +++ b/js/about/aboutActions.js @@ -328,6 +328,16 @@ const aboutActions = { originalDetail, destinationDetail }) + }, + + /** + * Dispatch a message to set default browser + */ + setAsDefaultBrowser: function () { + aboutActions.dispatchAction({ + actionType: appConstants.APP_DEFAULT_BROWSER_UPDATED, + useBrave: true + }) } } module.exports = aboutActions diff --git a/js/about/preferences.js b/js/about/preferences.js index daab6c2f1f9..08eba2cebe6 100644 --- a/js/about/preferences.js +++ b/js/about/preferences.js @@ -581,6 +581,7 @@ class GeneralTab extends ImmutableComponent { super() this.importBrowserDataNow = this.importBrowserDataNow.bind(this) this.onChangeSetting = this.onChangeSetting.bind(this) + this.setAsDefaultBrowser = this.setAsDefaultBrowser.bind(this) } importBrowserDataNow () { @@ -598,6 +599,10 @@ class GeneralTab extends ImmutableComponent { this.props.onChangeSetting(key, value) } + setAsDefaultBrowser () { + aboutActions.setAsDefaultBrowser() + } + enabled (keyArray) { return keyArray.every((key) => getSetting(key, this.props.settings) === true) } @@ -613,6 +618,13 @@ class GeneralTab extends ImmutableComponent { const disableShowHomeButton = !homepage || !homepage.length const disableBookmarksBarSelect = !getSetting(settings.SHOW_BOOKMARKS_TOOLBAR, this.props.settings) const defaultLanguage = this.props.languageCodes.find((lang) => lang.includes(navigator.language)) || 'en-US' + const defaultBrowser = getSetting(settings.IS_DEFAULT_BROWSER, this.props.settings) + ?
+ :
+
+
return
@@ -661,6 +673,11 @@ class GeneralTab extends ImmutableComponent { onClick={this.importBrowserDataNow} /> + + {defaultBrowser} + + } } diff --git a/js/actions/appActions.js b/js/actions/appActions.js index 30c5748ca4c..c033eff76f1 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -545,6 +545,27 @@ const appActions = { AppDispatcher.dispatch({ actionType: AppConstants.APP_SUBMIT_FEEDBACK }) + }, + + /** + * Dispatch a message to set default browser + * + * @param {boolean} useBrave - whether set Brave as default browser + */ + defaultBrowserUpdated: function (useBrave) { + AppDispatcher.dispatch({ + actionType: AppConstants.APP_DEFAULT_BROWSER_UPDATED, + useBrave + }) + }, + + /** + * Dispatch a message to indicate default browser check is complete + */ + defaultBrowserCheckComplete: function () { + AppDispatcher.dispatch({ + actionType: AppConstants.APP_DEFAULT_BROWSER_CHECK_COMPLETE + }) } } diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js index 38494b70104..f2e02049991 100644 --- a/js/actions/windowActions.js +++ b/js/actions/windowActions.js @@ -1146,6 +1146,19 @@ const windowActions = { actionType: WindowConstants.WINDOW_ON_FOCUS_CHANGED, hasFocus }) + }, + + /** + * Set Modal Dialog detail + * @param {string} className - name of modal dialog + * @param {Object} props - properties of the modal dialog + */ + setModalDialogDetail: function (className, props) { + dispatch({ + actionType: WindowConstants.WINDOW_SET_MODAL_DIALOG_DETAIL, + className, + props + }) } } diff --git a/js/components/main.js b/js/components/main.js index 3776458ebed..49386246a30 100644 --- a/js/components/main.js +++ b/js/components/main.js @@ -41,6 +41,7 @@ const NoScriptInfo = require('./noScriptInfo') const LongPressButton = require('./longPressButton') const Menubar = require('../../app/renderer/components/menubar') const WindowCaptionButtons = require('../../app/renderer/components/windowCaptionButtons') +const CheckDefaultBrowserDialog = require('../../app/renderer/components/checkDefaultBrowserDialog') // Constants const config = require('../constants/config') @@ -59,6 +60,7 @@ const basicAuthState = require('../../app/common/state/basicAuthState') const extensionState = require('../../app/common/state/extensionState') const FrameStateUtil = require('../state/frameStateUtil') const searchProviders = require('../data/searchProviders') +const defaultBrowserState = require('../../app/common/state/defaultBrowserState') // Util const cx = require('../lib/classSet') @@ -92,6 +94,7 @@ class Main extends ImmutableComponent { this.onHideAutofillCreditCardPanel = this.onHideAutofillCreditCardPanel.bind(this) this.onHideNoScript = this.onHideNoScript.bind(this) this.onHideReleaseNotes = this.onHideReleaseNotes.bind(this) + this.onHideCheckDefaultBrowserDialog = this.onHideCheckDefaultBrowserDialog.bind(this) this.onBraveMenu = this.onBraveMenu.bind(this) this.onHamburgerMenu = this.onHamburgerMenu.bind(this) this.onTabContextMenu = this.onTabContextMenu.bind(this) @@ -623,6 +626,10 @@ class Main extends ImmutableComponent { windowActions.setReleaseNotesVisible(false) } + onHideCheckDefaultBrowserDialog () { + windowActions.setModalDialogDetail('checkDefaultBrowserDialog') + } + enableNoScript (settings) { return siteSettings.activeSettings(settings, this.props.appState, appConfig).noScript === true } @@ -826,6 +833,8 @@ class Main extends ImmutableComponent { const activeRequestedLocation = this.activeRequestedLocation const noScriptIsVisible = this.props.windowState.getIn(['ui', 'noScriptInfo', 'isVisible']) const releaseNotesIsVisible = this.props.windowState.getIn(['ui', 'releaseNotes', 'isVisible']) + const checkDefaultBrowserDialogIsVisible = + currentWindow.isFocused() && defaultBrowserState.shouldDisplayDialog(this.props.appState) const braverySettings = siteSettings.activeSettings(activeSiteSettings, this.props.appState, appConfig) const loginRequiredDetail = activeFrame ? basicAuthState.getLoginRequiredDetail(this.props.appState, activeFrame.get('tabId')) : null const customTitlebar = this.customTitlebar @@ -838,6 +847,7 @@ class Main extends ImmutableComponent { !autofillAddressPanelIsVisible && !autofillCreditCardPanelIsVisible && !releaseNotesIsVisible && + !checkDefaultBrowserDialogIsVisible && !noScriptIsVisible && activeFrame && !activeFrame.getIn(['security', 'loginRequiredDetail']) && !customTitlebar.menubarSelectedIndex @@ -1018,6 +1028,17 @@ class Main extends ImmutableComponent { onHide={this.onHideReleaseNotes} /> : null } + { + checkDefaultBrowserDialogIsVisible + ? + : null + } { diff --git a/js/constants/appConfig.js b/js/constants/appConfig.js index b4f1ffa04f7..caeaa800ee3 100644 --- a/js/constants/appConfig.js +++ b/js/constants/appConfig.js @@ -96,6 +96,7 @@ module.exports = { 'general.show-home-button': false, 'general.useragent.value': null, // Set at runtime 'general.autohide-menu': true, + 'general.check-default-on-startup': true, 'search.default-search-engine': 'Google', 'search.offer-search-suggestions': false, // false by default for privacy reasons 'tabs.switch-to-new-tabs': false, diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 041e9f9c2a5..498a1a93508 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -60,7 +60,9 @@ const AppConstants = { APP_SET_MENUBAR_TEMPLATE: _, APP_UPDATE_ADBLOCK_DATAFILES: _, APP_UPDATE_ADBLOCK_CUSTOM_RULES: _, - APP_SUBMIT_FEEDBACK: _ + APP_SUBMIT_FEEDBACK: _, + APP_DEFAULT_BROWSER_UPDATED: _, + APP_DEFAULT_BROWSER_CHECK_COMPLETE: _ } module.exports = mapValuesByKeys(AppConstants) diff --git a/js/constants/settings.js b/js/constants/settings.js index f754f54f713..2e494e19444 100644 --- a/js/constants/settings.js +++ b/js/constants/settings.js @@ -14,6 +14,8 @@ const settings = { BOOKMARKS_TOOLBAR_MODE: 'general.bookmarks-toolbar-mode', SHOW_BOOKMARKS_TOOLBAR: 'bookmarks.toolbar.show', LANGUAGE: 'general.language', + CHECK_DEFAULT_ON_STARTUP: 'general.check-default-on-startup', + IS_DEFAULT_BROWSER: 'general.is-default-browser', // Search tab DEFAULT_SEARCH_ENGINE: 'search.default-search-engine', OFFER_SEARCH_SUGGESTIONS: 'search.offer-search-suggestions', diff --git a/js/constants/windowConstants.js b/js/constants/windowConstants.js index 2da41ade7d5..f2af206e62f 100644 --- a/js/constants/windowConstants.js +++ b/js/constants/windowConstants.js @@ -81,7 +81,8 @@ const windowConstants = { WINDOW_SET_LAST_FOCUSED_SELECTOR: _, WINDOW_GOT_RESPONSE_DETAILS: _, WINDOW_SET_BOOKMARKS_TOOLBAR_SELECTED_FOLDER_ID: _, - WINDOW_ON_FOCUS_CHANGED: _ + WINDOW_ON_FOCUS_CHANGED: _, + WINDOW_SET_MODAL_DIALOG_DETAIL: _ } module.exports = mapValuesByKeys(windowConstants) diff --git a/js/stores/appStore.js b/js/stores/appStore.js index 86253317be3..3252e4241d3 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -44,6 +44,8 @@ const isWindows = process.platform === 'win32' // Only used internally const CHANGE_EVENT = 'app-state-change' +const defaultProtocols = ['https', 'http'] + let appState let lastEmittedState @@ -723,6 +725,18 @@ const handleAppAction = (action) => { const subject = encodeURIComponent(`Brave ${platform} ${os.arch()} ${app.getVersion()}${channel()} feedback`) electron.shell.openExternal(`${appConfig.contactUrl}?subject=${subject}`) break + case AppConstants.APP_DEFAULT_BROWSER_UPDATED: + if (action.useBrave) { + for (const p of defaultProtocols) { + app.setAsDefaultProtocolClient(p) + } + } + let isDefaultBrowser = defaultProtocols.every(p => app.isDefaultProtocolClient(p)) + appState = appState.setIn(['settings', settings.IS_DEFAULT_BROWSER], isDefaultBrowser) + break + case AppConstants.APP_DEFAULT_BROWSER_CHECK_COMPLETE: + appState = appState.set('defaultBrowserCheckComplete', {}) + break default: } diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index 9fe070bba8a..891be3d88c7 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -842,6 +842,15 @@ const doAction = (action) => { case WindowConstants.WINDOW_ON_FOCUS_CHANGED: windowState = windowState.setIn(['ui', 'hasFocus'], action.hasFocus) break + case WindowConstants.WINDOW_SET_MODAL_DIALOG_DETAIL: + if (action.className && action.props === undefined) { + windowState = windowState.deleteIn(['modalDialogDetail', action.className]) + } else if (action.className) { + windowState = windowState.setIn(['modalDialogDetail', action.className], Immutable.fromJS(action.props)) + } + // Since the input values of address are bound, we need to notify the controls sync. + windowStore.emitChanges() + break default: } diff --git a/less/forms.less b/less/forms.less index 04b66e9d72b..0f2783154cb 100644 --- a/less/forms.less +++ b/less/forms.less @@ -196,6 +196,63 @@ } } +.checkDefaultBrowserDialog { + .checkDefaultBrowser { + .flyoutDialog; + background-color: #f7f7f7; + border-radius: @borderRadius; + max-width: 422px; + padding: 1; + text-align: left; + width: 614px; + height: 120px; + -webkit-user-select: none; + cursor: default; + color: #3B3B3B; + overflow-x: hidden; + overflow-y: hidden; + max-height: 100%; + + .clickable { + color: #5B5B5B; + &:hover { + color: #000; + } + } + + .checkDefaultBrowserButtons { + text-align: right; + padding: 16px 10px; + position: relative; + top: -30px; + } + + .makeBraveDefault { + font-weight: bold; + display: inline-block; + position: relative; + top: -35px; + margin-left: 15px; + } + + .braveIcon { + background-image: -webkit-image-set(url(../app/extensions/brave/img/braveAbout.png) 2x); + background-repeat: no-repeat; + height: 64px; + width: 64px; + display: inline-block; + position: relative; + top: 10px; + } + + .checkDefaultOnStartup { + left: 75px; + position: relative; + top: -25px; + } + } +} + .braveryPanelContainer { .braveryPanel {