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

Commit 3d87b1e

Browse files
committed
make tab preview based on idle mouse time
- Fix #8860 - Auditors: @bsclifton
1 parent 1a2dcf3 commit 3d87b1e

File tree

11 files changed

+102
-14
lines changed

11 files changed

+102
-14
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
@@ -193,6 +193,10 @@ tabsPerTabPage=Number of tabs per tab set:
193193
tabCloseAction=When closing an active tab:
194194
showTabPreviews=Show tab previews on hover
195195
showOpenedTabMatches=Show tab matches
196+
tabPreviewTiming=Time to wait before previewing a tab
197+
long=Long
198+
normal=Normal
199+
short=Short
196200
showHistoryMatches=Show history matches
197201
showBookmarkMatches=Show bookmark matches
198202
showTopsiteSuggestions=Show top site suggestions

app/renderer/components/tabs/tab.js

+35-12
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const {hasBreakpoint} = require('../../lib/tabUtil')
5050
class Tab extends React.Component {
5151
constructor (props) {
5252
super(props)
53+
this.onMouseMove = this.onMouseMove.bind(this)
5354
this.onMouseEnter = this.onMouseEnter.bind(this)
5455
this.onMouseLeave = this.onMouseLeave.bind(this)
5556
this.onUpdateTabSize = this.onUpdateTabSize.bind(this)
@@ -129,27 +130,47 @@ class Tab extends React.Component {
129130
dnd.onDragOver(dragTypes.TAB, this.tabNode.getBoundingClientRect(), this.props.frameKey, this.draggingOverData, e)
130131
}
131132

132-
onMouseLeave () {
133+
onMouseLeave (e) {
134+
windowActions.setTabHoverState(this.props.frameKey, false)
135+
133136
if (this.props.previewTabs) {
134-
window.clearTimeout(this.hoverTimeout)
135-
windowActions.setPreviewFrame(null)
137+
clearTimeout(this.mouseTimeout)
138+
// Matches tab_, tabArea, tabTitle, tabId, closeTab and
139+
// all tab icons such as favicon, private and new session
140+
const tabComponents = /tab(?=_|area|title|id)|closetab|icon/i
141+
const tabAsRelatedTarget = tabComponents.test(e.relatedTarget.classList)
142+
143+
// We are taking for granted that user hovering over another tab
144+
// means that he wants to sequentially preview a set of tabs,
145+
// so if previewMode was set by defined mouse idle time,
146+
// only cancel previewMode if the next event doesn't happen in another tab.
147+
if (!tabAsRelatedTarget) {
148+
windowActions.setPreviewFrame(null)
149+
windowActions.setPreviewMode(false)
150+
}
136151
}
137152
windowActions.setTabHoverState(this.props.frameKey, false)
138153
}
139154

140155
onMouseEnter (e) {
141-
// relatedTarget inside mouseenter checks which element before this event was the pointer on
142-
// if this element has a tab-like class, then it's likely that the user was previewing
143-
// a sequency of tabs. Called here as previewMode.
144-
const previewMode = /tab(?!pages)/i.test(e.relatedTarget.classList)
156+
windowActions.setTabHoverState(this.props.frameKey, true)
145157

146-
// If user isn't in previewMode, we add a bit of delay to avoid tab from flashing out
147-
// as reported here: https://github.com/brave/browser-laptop/issues/1434
158+
this.mouseTimeout = null
159+
if (this.props.previewTabs && this.props.previewMode) {
160+
windowActions.setPreviewFrame(this.props.frameKey)
161+
}
162+
}
163+
164+
onMouseMove (e) {
165+
// previewMode is only triggered if mouse is idle over a tab
166+
// for a given amount of time based on timing defined in prefs->tabs
148167
if (this.props.previewTabs) {
149-
this.hoverTimeout =
150-
window.setTimeout(windowActions.setPreviewFrame.bind(null, this.props.frameKey), previewMode ? 0 : 200)
168+
clearTimeout(this.mouseTimeout)
169+
this.mouseTimeout = setTimeout(() => {
170+
windowActions.setPreviewFrame(this.props.frameKey)
171+
windowActions.setPreviewMode(true)
172+
}, getSetting(settings.TAB_PREVIEW_TIMING))
151173
}
152-
windowActions.setTabHoverState(this.props.frameKey, true)
153174
}
154175

155176
onAuxClick (e) {
@@ -272,6 +293,7 @@ class Tab extends React.Component {
272293
props.dragData = state.getIn(['dragData', 'type']) === dragTypes.TAB && state.get('dragData')
273294
props.hasTabInFullScreen = tabContentState.hasTabInFullScreen(currentWindow)
274295
props.tabId = frame.get('tabId')
296+
props.previewMode = currentWindow.getIn(['ui', 'tabs', 'previewMode'])
275297

276298
return props
277299
}
@@ -298,6 +320,7 @@ class Tab extends React.Component {
298320
partOfFullPageSet: this.props.partOfFullPageSet || !!this.props.tabWidth
299321
})}
300322
style={this.props.tabWidth ? { flex: `0 0 ${this.props.tabWidth}px` } : {}}
323+
onMouseMove={this.onMouseMove}
301324
onMouseEnter={this.onMouseEnter}
302325
onMouseLeave={this.onMouseLeave}>
303326
{

docs/state.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ AppStore
233233
'tabs.close-action': string, // one of: parent, lastActive, next
234234
'tabs.paint-tabs': boolean, // true if the page theme color and favicon color should be used for tabs
235235
'tabs.show-tab-previews': boolean, // true to show tab previews
236+
'tabs.preview-timing': boolean, // how much in milliseconds user should wait before tab preview is fired
236237
'tabs.switch-to-new-tabs': boolean, // true if newly opened tabs should be focused immediately
237238
'tabs.tabs-per-page': number // number of tabs per tab page
238239
},
@@ -665,8 +666,9 @@ WindowStore
665666
},
666667
size: array, // last known window size [x, y]
667668
tabs: {
668-
tabPageIndex: number, // index of the current tab page
669+
previewMode: boolean, // whether or not tab preview should be fired based on mouse idle time
669670
previewTabPageIndex: number // index of the tab being previewed
671+
tabPageIndex: number, // index of the current tab page
670672
},
671673
},
672674
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')
@@ -359,6 +367,19 @@ class TabsTab extends ImmutableComponent {
359367
<SettingCheckbox dataL10nId='switchToNewTabs' prefKey={settings.SWITCH_TO_NEW_TABS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
360368
<SettingCheckbox dataL10nId='paintTabs' prefKey={settings.PAINT_TABS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
361369
<SettingCheckbox dataL10nId='showTabPreviews' prefKey={settings.SHOW_TAB_PREVIEWS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
370+
{
371+
getSetting(settings.SHOW_TAB_PREVIEWS, this.props.settings)
372+
? <SettingItem dataL10nId='tabPreviewTiming'>
373+
<SettingDropdown
374+
value={getSetting(settings.TAB_PREVIEW_TIMING, this.props.settings)}
375+
onChange={changeSetting.bind(null, this.props.onChangeSetting, settings.TAB_PREVIEW_TIMING)}>
376+
<option data-l10n-id='long' value={tabPreviewTiming.LONG} />
377+
<option data-l10n-id='normal' value={tabPreviewTiming.NORMAL} />
378+
<option data-l10n-id='short' value={tabPreviewTiming.SHORT} />
379+
</SettingDropdown>
380+
</SettingItem>
381+
: null
382+
}
362383
<SettingItem dataL10nId='dashboardSettingsTitle'>
363384
<SettingCheckbox dataL10nId='dashboardShowImages' prefKey={settings.SHOW_DASHBOARD_IMAGES} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
364385
</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
@@ -346,6 +346,9 @@ const doAction = (action) => {
346346
case windowConstants.WINDOW_CLEAR_CLOSED_FRAMES:
347347
windowState = windowState.set('closedFrames', new Immutable.List())
348348
break
349+
case windowConstants.WINDOW_SET_PREVIEW_MODE:
350+
windowState = windowState.setIn(['ui', 'tabs', 'previewMode'], action.shouldEnablePreview)
351+
break
349352
case windowConstants.WINDOW_SET_PREVIEW_FRAME:
350353
windowState = windowState.merge({
351354
previewFrameKey:

0 commit comments

Comments
 (0)