Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
Add tor loading progress UI
Browse files Browse the repository at this point in the history
Fix #14043

Test Plan:

Success case:
1. start brave
2. immediately open a tor private tab
3. you should briefly see a loading message in the URLbar
4. once loading is finished, you should be able to type in the URLbar

Fail case:
1. start an HTTP listener on port 9250 (so that Tor cannot bind to it.
   this causes Tor to not be able to connect).
2. start brave
3. open tor tab and wait for 20s
4. you should see an error message pop up
5. the button and link in the error message should work as expected
  • Loading branch information
diracdeltas authored and bsclifton committed Jun 26, 2018
1 parent 0a0509d commit 0f08f52
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 22 deletions.
28 changes: 28 additions & 0 deletions app/browser/reducers/torReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* 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/. */

'use strict'

const filtering = require('../../filtering')
const appConstants = require('../../../js/constants/appConstants')

const torReducer = (state, action) => {
switch (action.actionType) {
case appConstants.APP_SET_TOR_NEW_IDENTITY:
filtering.setTorNewIdentity(action.url, action.tabId)
break
case appConstants.APP_ON_TOR_INIT_ERROR:
state = state.setIn(['tor', 'initializationError'], action.message)
break
case appConstants.APP_ON_TOR_INIT_SUCCESS:
state = state.setIn(['tor', 'initializationError'], false).setIn(['tor', 'percentInitialized'], null)
break
case appConstants.APP_ON_TOR_INIT_PERCENTAGE:
state = state.setIn(['tor', 'percentInitialized'], action.percentage)
break
}
return state
}

module.exports = torReducer
9 changes: 8 additions & 1 deletion app/extensions/brave/locales/en-US/app.properties
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ tabsSuggestionTitle=Tabs
topSiteSuggestionTitle=Top Site
torrentBlockedInTor=For your privacy, torrents are blocked in private tabs when Tor is enabled.
torrentWarningOk=Ok
torConnectionError=Unable to connect to the Tor network
torConnectionErrorInfo=Brave could not make a connection to the Tor network. Disable Tor to continue private browsing without Tor protection.
torConnectionErrorDisable=Disable Tor
torConnectionErrorRetry=To retry connecting to the Tor network,
torConnectionErrorRestart=click here to restart Brave.
turnOffNotifications=Turn off notifications
unknownError=Oops, something went wrong.
unmuteTab=Unmute tab
Expand All @@ -257,7 +262,9 @@ updateNow=Update
updateOops=Oops!
updateRequiresRelaunch=Requires a quick relaunch…
updateViewLog=View log
urlbar.placeholder=Enter a URL or search term
urlbarPlaceholder=Enter a URL or search term
urlbarPlaceholderTorSuccess=Successfully connected to the Tor network!
urlbarPlaceholderTorProgress=Connecting to the Tor network
urlCopied=URL copied to clipboard
useBrave=Use Brave
verifiedPublisher.title=This is a verified publisher. Click to enable this publisher for payments
Expand Down
47 changes: 37 additions & 10 deletions app/filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,11 @@ const initPartition = (partition) => {
options.parent_partition = ''
}
if (isTorPartition) {
setupTor()
try {
setupTor()
} catch (e) {
appActions.onTorInitError(`Could not start Tor: ${e}`)
}
// TODO(riastradh): Duplicate logic in app/browser/tabs.js.
options.isolated_storage = true
options.parent_partition = ''
Expand Down Expand Up @@ -764,40 +768,63 @@ const initPartition = (partition) => {
module.exports.initPartition = initPartition

function setupTor () {
let torInitialized = null
// If Tor has not successfully initialized or thrown an error within 20s,
// assume it's broken.
setTimeout(() => {
if (torInitialized === null) {
appActions.onTorInitError(`Tor could not start.`)
}
}, 20000)
// Set up the tor daemon watcher. (NOTE: We don't actually start
// the tor daemon here; that happens in C++ code. But we do talk to
// its control socket.)
const torDaemon = new tor.TorDaemon()
torDaemon.setup((err) => {
if (err) {
console.log(`tor: failed to make directories: ${err}`)
appActions.onTorInitError(`Tor failed to make directories: ${err}`)
torInitialized = false
return
}
torDaemon.on('exit', () => console.log('tor: daemon exited'))
torDaemon.on('exit', () => {
appActions.onTorInitError('The Tor process has stopped.')
torInitialized = false
})
torDaemon.on('launch', (socksAddr) => {
console.log(`tor: daemon listens on ${socksAddr}`)
const bootstrapped = (err, progress) => {
// TODO(riastradh): Visually update a progress bar!
if (err) {
console.log(`tor: bootstrap error: ${err}`)
appActions.onTorInitError(`Tor bootstrap error: ${err}`)
torInitialized = false
return
}
console.log(`tor: bootstrapped ${progress}%`)
appActions.onTorInitPercentage(progress)
}
const circuitEstablished = (err, ok) => {
if (ok) {
console.log(`tor: ready`)
console.log('Tor ready!')
appActions.onTorInitSuccess()
torInitialized = true
} else {
console.log(err ? `tor: not ready: ${err}` : `tor: not ready`)
if (err) {
appActions.onTorInitError(`Tor not ready: ${err}`)
torInitialized = false
} else {
// Simply log the error but don't show error UI since Tor might
// finish opening a circuit.
console.log('tor still not ready')
}
}
}
torDaemon.onBootstrap(bootstrapped, (err) => {
if (err) {
console.log(`tor: error subscribing to bootstrap: ${err}`)
appActions.onTorInitError(`Tor error bootstrapping: ${err}`)
torInitialized = false
}
torDaemon.onCircuitEstablished(circuitEstablished, (err) => {
if (err) {
console.log(`tor: error subscribing to circuit ready: ${err}`)
appActions.onTorInitError(`Tor error opening a circuit: ${err}`)
torInitialized = false
}
})
})
Expand Down
4 changes: 4 additions & 0 deletions app/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ var rendererIdentifiers = function () {
'smartphoneTitle',
'updateLater',
'updateHello',
'urlbarPlaceholder',
'urlbarPlaceholderTorSuccess',
'urlbarPlaceholderTorProgress',
'torConnectionError',
// notifications
'notificationPasswordWithUserName',
'notificationUpdatePasswordWithUserName',
Expand Down
7 changes: 5 additions & 2 deletions app/renderer/components/main/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ class Main extends React.Component {
const widevinePanelDetail = currentWindow.get('widevinePanelDetail', Immutable.Map())
const loginRequiredDetails = basicAuthState.getLoginRequiredDetail(state, activeTabId)
const focused = isFocused(state)
const isTor = frameStateUtil.isTor(activeFrame)
const torConnectionError = state.getIn(['tor', 'initializationError'])

const props = {}
// used in renderer
Expand All @@ -553,8 +555,9 @@ class Main extends React.Component {
props.captionButtonsVisible = isWindows
props.showContextMenu = currentWindow.has('contextMenuDetail')
props.showPopupWindow = currentWindow.has('popupWindowDetail')
props.showSiteInfo = currentWindow.getIn(['ui', 'siteInfo', 'isVisible']) &&
!isSourceAboutUrl(activeFrame.get('location'))
props.showSiteInfo = (currentWindow.getIn(['ui', 'siteInfo', 'isVisible']) &&
!isSourceAboutUrl(activeFrame.get('location'))) ||
(torConnectionError && isTor)
props.showBravery = shieldState.braveShieldsEnabled(activeFrame) &&
!!currentWindow.get('braveryPanelDetail')
props.showClearData = currentWindow.getIn(['ui', 'isClearBrowsingDataPanelVisible'], false)
Expand Down
64 changes: 63 additions & 1 deletion app/renderer/components/main/siteInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ const urlUtil = require('../../../../js/lib/urlutil')
const globalStyles = require('../styles/global')
const commonStyles = require('../styles/commonStyles')

// Constants
const settings = require('../../../../js/constants/settings')

class SiteInfo extends React.Component {
constructor (props) {
super(props)
this.onAllowRunInsecureContent = this.onAllowRunInsecureContent.bind(this)
this.onDenyRunInsecureContent = this.onDenyRunInsecureContent.bind(this)
this.onViewCertificate = this.onViewCertificate.bind(this)
this.onDisableTor = this.onDisableTor.bind(this)
}

onAllowRunInsecureContent () {
Expand All @@ -61,7 +65,20 @@ class SiteInfo extends React.Component {
windowActions.setSiteInfoVisible(false)
}

onDisableTor () {
appActions.changeSetting(settings.USE_TOR_PRIVATE_TABS, false)
appActions.recreateTorTab(false, this.props.activeTabId,
this.props.activeTabIndex)
}

onRestart () {
appActions.shuttingDown(true)
}

get secureIcon () {
if (this.props.torConnectionError) {
return <div className={css(styles.connectionInfo__header)} data-l10n-id='torConnectionError' />
}
if (this.props.isFullySecured) {
// fully secure
return <div className={css(styles.secureIcon)}>
Expand Down Expand Up @@ -149,7 +166,26 @@ class SiteInfo extends React.Component {
site: this.props.location
}

if (this.props.maybePhishingLocation) {
if (this.props.torConnectionError) {
// Log the error for advanced users to debug
console.log('Tor connection error:', this.props.torConnectionError)
return <div>
<div className={css(styles.torBody)}>
<div className={css(styles.torConnectionInfo)} data-l10n-id='torConnectionErrorInfo' />
<Button
l10nId='torConnectionErrorDisable'
className='primaryButton'
onClick={this.onDisableTor}
/>
</div>
<div className={css(styles.torFooter)}>
<div data-l10n-id='torConnectionErrorRetry' />
<div data-l10n-id='torConnectionErrorRestart'
className={css(styles.link)}
onClick={this.onRestart} />
</div>
</div>
} else if (this.props.maybePhishingLocation) {
return <div className={css(styles.connectionInfo)}>
<div data-l10n-id='phishingConnectionInfo' data-test-id='phishingConnectionInfo' />
</div>
Expand Down Expand Up @@ -232,10 +268,12 @@ class SiteInfo extends React.Component {
props.secureConnection = isSecure === true
props.partiallySecureConnection = isSecure === 1
props.certErrorConnection = isSecure === 2
props.torConnectionError = frameStateUtil.isTor(activeFrame) && state.getIn(['tor', 'initializationError'])

// used in other function
props.isPrivate = activeFrame.get('isPrivate')
props.activeTabId = activeFrame.get('tabId', tabState.TAB_ID_NONE)
props.activeTabIndex = frameStateUtil.getIndexByTabId(currentWindow, props.activeTabId)

return props
}
Expand Down Expand Up @@ -287,12 +325,36 @@ const styles = StyleSheet.create({
margin: `${globalStyles.spacing.dialogInsideMargin} 0 0 ${globalStyles.spacing.dialogInsideMargin}`
},

connectionInfo__header: {
color: globalStyles.color.braveOrange,
fontSize: '1rem'
},

connectionInfo__viewCertificateButton: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: globalStyles.spacing.dialogInsideMargin
},

torConnectionInfo: {
marginTop: '15px',
marginBottom: '20px'
},

torBody: {
paddingBottom: '15px',
lineHeight: '1.5em'
},

torFooter: {
lineHeight: '1.5em'
},

link: {
color: globalStyles.color.braveOrange,
cursor: 'pointer'
},

siteInfo: {
maxHeight: '300px',
maxWidth: '400px',
Expand Down
31 changes: 30 additions & 1 deletion app/renderer/components/navigation/urlBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const {normalizeLocation, getNormalizedSuggestion} = require('../../../common/li
const isDarwin = require('../../../common/lib/platformUtil').isDarwin()
const publisherUtil = require('../../../common/lib/publisherUtil')
const historyUtil = require('../../../common/lib/historyUtil')
const locale = require('../../../../js/l10n')

// Icons
const iconNoScript = require('../../../../img/url-bar-no-script.svg')
Expand Down Expand Up @@ -421,6 +422,23 @@ class UrlBar extends React.Component {
}
}

get placeholderValue () {
if (this.props.isTor) {
if (this.props.torInitializationError) {
return `${locale.translation('torConnectionError')}.`
} else if (this.props.torPercentInitialized) {
// Don't show 100% since it sometimes gets stuck at 100%
const percentInitialized = this.props.torPercentInitialized === '100' ? '99' : this.props.torPercentInitialized
return `${locale.translation('urlbarPlaceholderTorProgress')}: ${percentInitialized}%...`
} else if (this.props.torInitializationError === false) {
return locale.translation('urlbarPlaceholderTorSuccess')
} else {
return `${locale.translation('urlbarPlaceholderTorProgress')}...`
}
}
return locale.translation('urlbarPlaceholder')
}

get titleValue () {
// For about:newtab we don't want the top of the browser saying New Tab
// Instead just show "Brave"
Expand Down Expand Up @@ -468,6 +486,7 @@ class UrlBar extends React.Component {
const selectedIndex = urlbar.getIn(['suggestions', 'selectedIndex'])
const allSiteSettings = siteSettingsState.getAllSiteSettings(state, activeFrameIsPrivate)
const braverySettings = siteSettings.getSiteSettingsForURL(allSiteSettings, location)
const isTor = frameStateUtil.isTor(activeFrame)

// TODO(bridiver) - these definitely needs a helpers
const publisherKey = ledgerState.getLocationProp(state, baseUrl, 'publisher')
Expand All @@ -493,6 +512,9 @@ class UrlBar extends React.Component {
props.loading = activeFrame.get('loading')
props.navigationProgressPercent = tabState.getNavigationProgressPercent(state, activeTabId)

props.isTor = isTor
props.torPercentInitialized = state.getIn(['tor', 'percentInitialized'])
props.torInitializationError = state.getIn(['tor', 'initializationError'])
props.showDisplayTime = !props.titleMode && props.displayURL === location
props.showNoScriptInfo = enableNoScript && scriptsBlocked && scriptsBlocked.size
props.evCert = activeFrame.getIn(['security', 'evCert'])
Expand Down Expand Up @@ -528,6 +550,12 @@ class UrlBar extends React.Component {
return <span className='evCert' title={this.props.evCert}> {this.props.evCert} </span>
}

get shouldDisable () {
return (this.props.displayURL === undefined && this.loadTime === '') ||
(this.props.isTor &&
(this.props.torPercentInitialized || this.props.torInitializationError !== false))
}

setUrlBarRef (ref) {
this.urlBarRef = ref
}
Expand Down Expand Up @@ -567,7 +595,7 @@ class UrlBar extends React.Component {
</div>
: <input type='text'
spellCheck='false'
disabled={this.props.displayURL === undefined && this.loadTime === ''}
disabled={this.shouldDisable}
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
Expand All @@ -580,6 +608,7 @@ class UrlBar extends React.Component {
onClick={this.onClick}
onContextMenu={this.onContextMenu}
data-l10n-id='urlbar'
placeholder={this.placeholderValue}
className={cx({
testHookLoadDone: !this.props.loading
})}
Expand Down
3 changes: 3 additions & 0 deletions app/sessionStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ module.exports.cleanAppData = (immutableData, isShutdown) => {
immutableData = immutableData.set('notifications', Immutable.List())
// Delete temp site settings
immutableData = immutableData.set('temporarySiteSettings', Immutable.Map())
// Delete Tor init state
immutableData = immutableData.set('tor', Immutable.Map())

if (immutableData.getIn(['settings', settings.CHECK_DEFAULT_ON_STARTUP]) === true) {
// Delete defaultBrowserCheckComplete state since this is checked on startup
Expand Down Expand Up @@ -1135,6 +1137,7 @@ module.exports.defaultAppState = () => {
passwords: [],
notifications: [],
temporarySiteSettings: {},
tor: {},
autofill: {
addresses: {
guid: [],
Expand Down
4 changes: 4 additions & 0 deletions docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,10 @@ AppStore
// Same as siteSettings but never gets written to disk
// XXX: This was intended for Private Browsing but is currently unused.
},
tor: {
percentInitialized: number, // percentage initialized
initializationError: string|boolean, // error message. false means successfully initialized.
},
updates: {
lastCheckTimestamp: boolean,
metadata: {
Expand Down
Loading

0 comments on commit 0f08f52

Please sign in to comment.