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

Commit 276fac0

Browse files
committed
make tab preview based on idle mouse time
- Fix #8860 - Auditors: @bsclifton, @bradleyrichter
1 parent 6fdc525 commit 276fac0

File tree

14 files changed

+107
-17
lines changed

14 files changed

+107
-17
lines changed

app/common/constants/settingsEnums.js

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ const tabCloseAction = {
2727
PARENT: 'parent'
2828
}
2929

30+
// timing in milliseconds
31+
const tabPreviewTiming = {
32+
LONG: 2000,
33+
NORMAL: 1000,
34+
SHORT: 500
35+
}
36+
3037
const fullscreenOption = {
3138
ALWAYS_ASK: 'alwaysAsk',
3239
ALWAYS_ALLOW: 'alwaysAllow'
@@ -42,6 +49,7 @@ module.exports = {
4249
newTabMode,
4350
bookmarksToolbarMode,
4451
tabCloseAction,
52+
tabPreviewTiming,
4553
fullscreenOption,
4654
autoplayOption
4755
}

app/extensions/brave/locales/en-US/preferences.properties

+4
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ paintTabs=Show tabs in page theme color
192192
tabsPerTabPage=Number of tabs per tab set:
193193
tabCloseAction=When closing an active tab:
194194
showTabPreviews=Show tab previews on hover
195+
tabPreviewTiming=Time to wait before previewing a tab
196+
long=Long
197+
normal=Normal
198+
short=Short
195199
showHistoryMatches=Show history matches
196200
showBookmarkMatches=Show bookmark matches
197201
showOpenedTabMatches=Show tab matches

app/renderer/components/main/main.js

+1
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,7 @@ class Main extends ImmutableComponent {
851851
paintTabs={getSetting(settings.PAINT_TABS)}
852852
shouldAllowWindowDrag={shouldAllowWindowDrag}
853853
dragData={this.props.appState.getIn(['dragData', 'type']) === dragTypes.TAB && this.props.appState.get('dragData')}
854+
previewMode={this.props.windowState.getIn(['ui', 'tabs', 'previewMode'])}
854855
previewTabs={getSetting(settings.SHOW_TAB_PREVIEWS)}
855856
tabsPerTabPage={tabsPerPage}
856857
tabPageIndex={this.props.windowState.getIn(['ui', 'tabs', 'tabPageIndex'])}

app/renderer/components/tabs/tab.js

+36-15
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const windowStore = require('../../../../js/stores/windowStore')
2626
// Constants
2727
const dragTypes = require('../../../../js/constants/dragTypes')
2828
const messages = require('../../../../js/constants/messages')
29+
const settings = require('../../../../js/constants/settings')
2930

3031
// Styles
3132
const styles = require('../styles/tab')
@@ -42,10 +43,12 @@ const {getTabBreakpoint, tabUpdateFrameRate} = require('../../lib/tabUtil')
4243
const {isWindows} = require('../../../common/lib/platformUtil')
4344
const {getCurrentWindowId} = require('../../currentWindow')
4445
const UrlUtil = require('../../../../js/lib/urlutil')
46+
const getSetting = require('../../../../js/settings').getSetting
4547

4648
class Tab extends ImmutableComponent {
4749
constructor () {
4850
super()
51+
this.onMouseMove = this.onMouseMove.bind(this)
4952
this.onMouseEnter = this.onMouseEnter.bind(this)
5053
this.onMouseLeave = this.onMouseLeave.bind(this)
5154
this.onUpdateTabSize = this.onUpdateTabSize.bind(this)
@@ -163,27 +166,46 @@ class Tab extends ImmutableComponent {
163166
!this.props.frame.get('provisionalLocation').startsWith('chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/'))
164167
}
165168

166-
onMouseLeave () {
169+
onMouseLeave (e) {
170+
windowActions.setTabHoverState(this.props.frame.get('key'), false)
171+
167172
if (this.props.previewTabs) {
168-
window.clearTimeout(this.hoverTimeout)
169-
windowActions.setPreviewFrame(null)
173+
clearTimeout(this.mouseTimeout)
174+
// Matches tab_, tabArea, tabTitle, tabId, closeTab and
175+
// all tab icons such as favicon, private and new session
176+
const tabComponents = /tab(?=_|area|title|id)|closetab|icon/i
177+
const tabAsRelatedTarget = tabComponents.test(e.relatedTarget.classList)
178+
179+
// We are taking for granted that user hovering over another tab
180+
// means that he wants to sequentially preview a set of tabs,
181+
// so if previewMode was set by defined mouse idle time,
182+
// only cancel previewMode if the next event doesn't happen in another tab.
183+
if (!tabAsRelatedTarget) {
184+
windowActions.setPreviewFrame(null)
185+
windowActions.setPreviewMode(false)
186+
}
170187
}
171-
windowActions.setTabHoverState(this.props.frame.get('key'), false)
172188
}
173189

174190
onMouseEnter (e) {
175-
// relatedTarget inside mouseenter checks which element before this event was the pointer on
176-
// if this element has a tab-like class, then it's likely that the user was previewing
177-
// a sequency of tabs. Called here as previewMode.
178-
const previewMode = /tab(?!pages)/i.test(e.relatedTarget.classList)
191+
windowActions.setTabHoverState(this.props.frame.get('key'), true)
179192

180-
// If user isn't in previewMode, we add a bit of delay to avoid tab from flashing out
181-
// as reported here: https://github.com/brave/browser-laptop/issues/1434
193+
this.mouseTimeout = null
194+
if (this.props.previewTabs && this.props.previewMode) {
195+
windowActions.setPreviewFrame(this.props.frame.get('key'))
196+
}
197+
}
198+
199+
onMouseMove (e) {
200+
// previewMode is only triggered if mouse is idle over a tab
201+
// for a given amount of time based on timing defined in prefs->tabs
182202
if (this.props.previewTabs) {
183-
this.hoverTimeout =
184-
window.setTimeout(windowActions.setPreviewFrame.bind(null, this.props.frame.get('key')), previewMode ? 0 : 200)
203+
clearTimeout(this.mouseTimeout)
204+
this.mouseTimeout = setTimeout(() => {
205+
windowActions.setPreviewFrame(this.props.frame.get('key'))
206+
windowActions.setPreviewMode(true)
207+
}, getSetting(settings.TAB_PREVIEW_TIMING))
185208
}
186-
windowActions.setTabHoverState(this.props.frame.get('key'), true)
187209
}
188210

189211
onAuxClick (e) {
@@ -288,6 +310,7 @@ class Tab extends ImmutableComponent {
288310
partOfFullPageSet: this.props.partOfFullPageSet || !!this.props.tabWidth
289311
})}
290312
style={this.props.tabWidth ? { flex: `0 0 ${this.props.tabWidth}px` } : {}}
313+
onMouseMove={this.onMouseMove}
291314
onMouseEnter={this.onMouseEnter}
292315
onMouseLeave={this.onMouseLeave}>
293316
{
@@ -369,8 +392,6 @@ class Tab extends ImmutableComponent {
369392
}
370393

371394
const paymentsEnabled = () => {
372-
const getSetting = require('../../../../js/settings').getSetting
373-
const settings = require('../../../../js/constants/settings')
374395
return getSetting(settings.PAYMENTS_ENABLED)
375396
}
376397

app/renderer/components/tabs/tabs.js

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ class Tabs extends ImmutableComponent {
152152
frame={frame}
153153
key={'tab-' + frame.get('key')}
154154
paintTabs={this.props.paintTabs}
155+
previewMode={this.props.previewMode}
155156
previewTabs={this.props.previewTabs}
156157
isActive={this.props.activeFrameKey === frame.get('key')}
157158
onTabClosedWithMouse={this.onTabClosedWithMouse}

app/renderer/components/tabs/tabsToolbar.js

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class TabsToolbar extends ImmutableComponent {
4545
? <PinnedTabs sites={this.props.sites}
4646
activeFrameKey={this.props.activeFrameKey}
4747
paintTabs={this.props.paintTabs}
48+
previewMode={this.props.previewMode}
4849
previewTabs={this.props.previewTabs}
4950
dragData={this.props.dragData}
5051
tabPageIndex={this.props.tabPageIndex}
@@ -59,6 +60,7 @@ class TabsToolbar extends ImmutableComponent {
5960
shouldAllowWindowDrag={this.props.shouldAllowWindowDrag}
6061
dragData={this.props.dragData}
6162
paintTabs={this.props.paintTabs}
63+
previewMode={this.props.previewMode}
6264
previewTabs={this.props.previewTabs}
6365
tabsPerTabPage={this.props.tabsPerTabPage}
6466
activeFrameKey={this.props.activeFrameKey}

docs/state.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ AppStore
232232
'tabs.close-action': string, // one of: parent, lastActive, next
233233
'tabs.paint-tabs': boolean, // true if the page theme color and favicon color should be used for tabs
234234
'tabs.show-tab-previews': boolean, // true to show tab previews
235+
'tabs.preview-timing': boolean, // how much in milliseconds user should wait before tab preview is fired
235236
'tabs.switch-to-new-tabs': boolean, // true if newly opened tabs should be focused immediately
236237
'tabs.tabs-per-page': number // number of tabs per tab page
237238
},
@@ -664,8 +665,9 @@ WindowStore
664665
},
665666
size: array, // last known window size [x, y]
666667
tabs: {
667-
tabPageIndex: number, // index of the current tab page
668+
previewMode: boolean, // whether or not tab preview should be fired based on mouse idle time
668669
previewTabPageIndex: number // index of the tab being previewed
670+
tabPageIndex: number, // index of the current tab page
669671
},
670672
},
671673
widevinePanelDetail: {

docs/windowActions.md

+11
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,17 @@ This is done when hovering over a tab.
212212

213213

214214

215+
### setPreviewMode(shouldEnablePreview)
216+
217+
Dispatches a message to the store to set preview mode.
218+
This will check whether setPreviewFrame should be fired or not.
219+
220+
**Parameters**
221+
222+
**shouldEnablePreview**: `Boolean`, true if user enters in previewMode state
223+
224+
225+
215226
### setTabPageIndex(index)
216227

217228
Dispatches a message to the store to set the tab page index.

js/about/preferences.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ const messages = require('../constants/messages')
3434
const settings = require('../constants/settings')
3535
const {changeSetting} = require('../../app/renderer/lib/settingsUtil')
3636
const {passwordManagers, extensionIds} = require('../constants/passwordManagers')
37-
const {startsWithOption, newTabMode, bookmarksToolbarMode, tabCloseAction, fullscreenOption, autoplayOption} = require('../../app/common/constants/settingsEnums')
37+
const {
38+
startsWithOption,
39+
newTabMode,
40+
bookmarksToolbarMode,
41+
tabCloseAction,
42+
fullscreenOption,
43+
autoplayOption,
44+
tabPreviewTiming
45+
} = require('../../app/common/constants/settingsEnums')
3846

3947
const aboutActions = require('./aboutActions')
4048
const appActions = require('../actions/appActions')
@@ -358,6 +366,19 @@ class TabsTab extends ImmutableComponent {
358366
<SettingCheckbox dataL10nId='switchToNewTabs' prefKey={settings.SWITCH_TO_NEW_TABS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
359367
<SettingCheckbox dataL10nId='paintTabs' prefKey={settings.PAINT_TABS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
360368
<SettingCheckbox dataL10nId='showTabPreviews' prefKey={settings.SHOW_TAB_PREVIEWS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
369+
{
370+
getSetting(settings.SHOW_TAB_PREVIEWS, this.props.settings)
371+
? <SettingItem dataL10nId='tabPreviewTiming'>
372+
<SettingDropdown
373+
value={getSetting(settings.TAB_PREVIEW_TIMING, this.props.settings)}
374+
onChange={changeSetting.bind(null, this.props.onChangeSetting, settings.TAB_PREVIEW_TIMING)}>
375+
<option data-l10n-id='long' value={tabPreviewTiming.LONG} />
376+
<option data-l10n-id='normal' value={tabPreviewTiming.NORMAL} />
377+
<option data-l10n-id='short' value={tabPreviewTiming.SHORT} />
378+
</SettingDropdown>
379+
</SettingItem>
380+
: null
381+
}
361382
<SettingItem dataL10nId='dashboardSettingsTitle'>
362383
<SettingCheckbox dataL10nId='dashboardShowImages' prefKey={settings.SHOW_DASHBOARD_IMAGES} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
363384
</SettingItem>

js/actions/windowActions.js

+13
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,19 @@ const windowActions = {
254254
})
255255
},
256256

257+
/**
258+
* Dispatches a message to the store to set preview mode.
259+
* This will check whether setPreviewFrame should be fired or not.
260+
*
261+
* @param {Boolean} shouldEnablePreview - true if user enters in previewMode state
262+
*/
263+
setPreviewMode: function (shouldEnablePreview) {
264+
dispatch({
265+
actionType: windowConstants.WINDOW_SET_PREVIEW_MODE,
266+
shouldEnablePreview
267+
})
268+
},
269+
257270
/**
258271
* Dispatches a message to the store to set the tab page index.
259272
*

js/constants/appConfig.js

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ module.exports = {
138138
'tabs.tabs-per-page': 20,
139139
'tabs.close-action': 'parent',
140140
'tabs.show-tab-previews': true,
141+
'tabs.preview-timing': 2000,
141142
'tabs.show-dashboard-images': true,
142143
'privacy.history-suggestions': true,
143144
'privacy.bookmark-suggestions': true,

js/constants/settings.js

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const settings = {
2828
PAINT_TABS: 'tabs.paint-tabs',
2929
TABS_PER_PAGE: 'tabs.tabs-per-page',
3030
SHOW_TAB_PREVIEWS: 'tabs.show-tab-previews',
31+
TAB_PREVIEW_TIMING: 'tabs.preview-timing',
3132
SHOW_DASHBOARD_IMAGES: 'tabs.show-dashboard-images',
3233
// Privacy Tab
3334
HISTORY_SUGGESTIONS: 'privacy.history-suggestions',

js/constants/windowConstants.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const windowConstants = {
1111
WINDOW_CLOSE_FRAMES: _,
1212
WINDOW_SET_FOCUSED_FRAME: _,
1313
WINDOW_SET_PREVIEW_FRAME: _,
14+
WINDOW_SET_PREVIEW_MODE: _,
1415
WINDOW_SET_PREVIEW_TAB_PAGE_INDEX: _,
1516
WINDOW_SET_TAB_PAGE_INDEX: _,
1617
WINDOW_SET_TAB_BREAKPOINT: _,

js/stores/windowStore.js

+3
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ const doAction = (action) => {
349349
case windowConstants.WINDOW_CLEAR_CLOSED_FRAMES:
350350
windowState = windowState.set('closedFrames', new Immutable.List())
351351
break
352+
case windowConstants.WINDOW_SET_PREVIEW_MODE:
353+
windowState = windowState.setIn(['ui', 'tabs', 'previewMode'], action.shouldEnablePreview)
354+
break
352355
case windowConstants.WINDOW_SET_PREVIEW_FRAME:
353356
windowState = windowState.merge({
354357
previewFrameKey: action.frameKey != null && action.frameKey !== windowState.get('activeFrameKey')

0 commit comments

Comments
 (0)