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

improve tabs performance and UI with this one simple trick #10691

Merged
merged 28 commits into from
Sep 21, 2017

Conversation

cezaraugusto
Copy link
Contributor

@cezaraugusto cezaraugusto commented Aug 28, 2017

dear reviewer each commit addresses a logical part of the whole fix and most of them have tests included. I took 2 days ☕️☕️☕️☕️☕️ rebasing this against the first trial for better review and bug check, so please consider do not squash.

TL;DR

  • Improved performance for opening tabs
  • Improved performance for resizing the window
  • Tabs do not flicker on hover anymore
  • Audio indicator is can now be clicked again to mute/unmute
  • Observable tabs now resize automatically as tabs get added/removed/resized
  • Going/exiting fullscreen videos gracefully resize tab content
  • Fixed back/forward tabPage action not showing the close icon
  • Tab Title is always visible once title is fetch
  • Tab title can gracefully show emojis 💥 🎉

Click to ignore it all and go to test plan

Performance tests

Performance while resizing window

Test plan was made as the following:

  1. Window is sized as ~1200px
  2. 20 tabs loaded with brave.com as webview
  3. resizing to minimum then maximizing to 1200px again

Metrics used:

  • main resize function cost
  • main call stack cost

current

main resize function cost

We relied on get tabSize for our calculations, which has some cost and we don't use it anymore.

before__resize_function_cost

main call stack cost

Values varied while doing tests. Below shows that previous call stack was lighter than what this PR introduces so still room for improvement. Getting a tab by id seems to have a heavier cost too and other new methods also added some weight.

before__resize_call_stack_size

after this PR

main resize function cost

There's no need for this.tabSize getter anymore.

after__resize_function_cost

main call stack cost

after__resize_call_stack_size

Performance while opening tabs

Test plan was made as the following:

  1. Window is sized as ~1200px
  2. set brave.com as your home page
  3. keep one tab only, which should be loaded as brave.com
  4. open 19 tabs sequentially, wait all tabs to load (no loading icon). Operation took around 22s in local network

current

Again get tabSize taking its space which is no longer required.

before__open_tab_function_cost

summary__tab_open_without_observer

after this pr

summary__tab_open_w_observer

Performance while closing tabs

Test plan was made as the following:

I couldn't see a significant change here, as tab icons are only updated when you mouse out of tabs, but I'm sharing anyway.

  1. Window is sized as ~1200px
  2. set brave.com as your home page
  3. keep one tab only, which should be loaded as brave.com
  4. open 20 tabs
  5. close 20 tabs sequentially, wait for all tabs to load (no loading icon). Operation took around 22s in local network

Tests were made several times, below a comparison between two consecutive tests.
Other than scripting, no gains were found. Given we only update tab size after mouse leave, that makes sense.

current

summary__tab_close_without_observer

after this pr

summary__tab_close_w_observer

Performance while resizing window -- stress test (multiple heavy tabs)

  1. window is sized around 1200px
  2. set https://www.youtube.com/watch?v=A_MjCqQoLLA as your homepage (disable your audio pls)
  3. Open total of 9 tabs. While profiling operation should took 20s
  4. Resize the window to the minimum possible
  5. Resize the window back to 1200px

Tests were made several times, below a comparison between two consecutive tests.

  • saved around ~400ms from scripting
  • saved around ~200ms from rendering
  • paiting is about the same for all, which makes sense given icons didn't change

current

wo_obs_20s_total_real

after this pr

w_obs_20s_total_real

Bugs fixed | usability improved

Total: 16 issues

close #10883
fix #10838
fix #10611
fix #10582
fix #10544
fix #10464
fix #10509
fix #10123
fix #10103
fix #9398
fix #8414
fix #7925
fix #7765
fix #7730
fix #7301
fix #6716

Unrelated tasks covered with this PR

  • Opens the road for theming: added theme.js

Test plan

For reviewers (QA please skip)

  • Most new methods were unit tested, you can track them by looking for each commit description.

For everybody (cc QA team)

Overall test plan is to have a lot of tabs, being included pinned, partitioned, private, newtab (which has no icon), about:blank (which has a "default" icon) and other tabs.

As you resize the window, icons should be shown in a graceful state, without bad UI/UX

Running through each issue (from older to newer)

Issue #6716 (fading effect b/w tabs feels like flickering)

  1. Have 3 tabs open. Ensure at least one have tab color (i.e. facebook), and one is private.
  2. Set each tab as active and then hover over other tabs
  3. there should be no flicker background (bad UI)

This is slightly different than #10838 as was reported by not the active tab. Test plan is similar.

Issue #7301 (Tabs display wrong after exiting HTML5 fullscreen)

  1. Have enough tabs so the close tab icon is always visible
  2. Also have some pinned tabs
  3. In one tab, go to a video website (i.e. youtube)
  4. Keep record of what you see in that tab (like favicon, title, close icon and a blue top border)
  5. Open a video in fullscreen mode. Keep an eye on tabs just in case
  6. Exit fullscreen.
  7. Tab content should be in the same size as step 4

Issue #7730 (Title on new tab flickers after setting the language to Russian)

  • Set the lang setting to Russian
  • Restart
  • Open a new tab
  • Change the window size vertically
  • Title shouldn't ficker

Issue #7925 (Throttling between window resize cause tabContent to pop)

  1. Open 100 tabs between video playing, new sessions and private tabs. default tabs too
  2. resize the window
  3. icons shouldn't pop or show/hide with a bad ui feeling

Issue #7765 (Tab title often isn't updated until the cursor is moved over it or tab focus is changed)

  1. Run simulated Brave payments to generate reports (npm run add-simulated-payment-history)
  2. Click on the payment report (the "clock" icon) to open it in a new tab
  3. Save the file/Close the save file prompt
  4. Tab title should show the title and not the url

Issue #8414 (Tabs No Longer Show Full-Color Emoji)

  1. Open getemoji.com
  2. You should see an emoji in the tab title and not a gray/black circle

Issue #9398 (Tab Label Rendering Issue)

Not sure how to track this but issue is unreproducible. See #9398 for comments

issue #10103 (Tab preview does not work properly with DnD)

Note: Needs PR #10937 merged to be tested.

  1. open two tabs
  2. let your mouse stale for 3s in a tab. this enable preview
  3. drag a tab to reorder
  4. preview should be canceled

Issue #10123 (no tab close icon is shown for the active tab on Windows)

(you need Windows)
(Test plan gently borrowed from @bsclifton)

  1. Set tab page to 20
  2. Maximize window
  3. Create enough tabs that you get a second page (21 tabs)
  4. switch back to tab page 1
  5. click one of the tabs to make it the active tab
  6. Hover over the other tabs and notice there is no X button
  7. Close a few tabs- enough that you see the tab breakpoint size change
  8. Hover over tabs and X now shows again

Issue #10464 (Description for Session Tab on Hover)

  1. Have enough tabs so the close tab icon is always visible
  2. Open one partitioned tab (new session)
  3. Hover over the icon
  4. alt title tooltip should render correctly (with a number!)

Issue #10509 (make use of intersectionObserver for tabs)

This is the main base of this PR and there's no test plan needed

Issue #10544 (Tabs cannot be muted via speaker icon)

  1. Open a tab with audio/video playing. See the icon
  2. Toggle the icon. Should be muted/unmuted

Issue #10582 (Tabs do not automatically resize until mousing over them)

  1. Open a lot of tabs up to a point that close icon is always shown
  2. Restart the browser
  3. important: ensure you do not hover over any tab
  4. Resize the window
  5. Icons should follow based on window resize

Issue #10611 (intermittent - audio indicator is not shown)

This issue happened because when breakpoints was a thing, "dynamic"
breakpoint wasn't set for the icon to be visible (see https://github.com/brave/browser-laptop/blob/master/app/renderer/components/tabs/tab.js#L263). We do not rely on breakpoints anymore, so:

  1. Open one tab
  2. Go to youtube.com, play a video
  3. Audio icon should be visible

Issue #10838 (Active tab flicker on hover)

  • Have 3 tabs open. Ensure at least one have custom background (i.e. facebook), and one is private.
  • Set each tab as active and then hover it
  • there should be no flicker background (bad UI)

Issue #10883 (create smooth transitions for tab components)

  • close icon, favicon, private, new session icons will have a smooth transition between resizes
  • audio icon will slightly scroll to the right when enabled

less/main.less Outdated
@@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */

@bodyBG: #000;
@windowContainerFG: #f00;
@windowContainerFG: #000;
Copy link
Contributor

@luixxiul luixxiul Sep 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main.less was removed on #10861, not being sure what those specifications have been required for. See: https://github.com/brave/browser-laptop/pull/10861/files#diff-eb0db025dc73e282655968f53c9bfbdfR171. We could discuss it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I saw that just after pushing. Should be removed here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed main.less was removed here too 👍

const frame = getFrameByKey(state, frameKey)

if (frame == null) {
if (process.env.NODE_ENV !== 'test') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you use this pattern a lot. I am curious why we wouldn't like to see this error in test as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it always falls to that condition for unit tests even when the test pass for some async reason

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok will check out unit tests and try to fix it, so that we can remove this check

Copy link
Contributor

@NejcZdovc NejcZdovc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all state helper functions should be converted from format (currentWindow, frameKey) to (currentWindow, tabId), because we are in the process of converting frameKey into tabId

return false
}

const isNewTabPage = frame.get('location') === 'about:newtab'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that we use this check in different places, can you please create function in utils for it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function should receive location as param, so that we can reuse this function in other places. We can create followup where we would fix other places, that are not included in this PR. This way we would have 'about:newtab' defined only in one place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

// until we know what we're loading. We should probably do this for
// all about: pages that we already know the title for so we don't have
// to wait for the title to be parsed.
if (frame.get('location') === 'about:blank') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for 'about:newtab' we should do the same with about:blank

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

// state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/state/State/g

const tabCloseState = require('../../common/state/tabContentState/tabCloseState')
const partitionState = require('../../common/state/tabContentState/partitionState')

// Utis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Utis/Utils/g

const {getTextColorForBackground} = require('../../../js/lib/color')
const {isEntryIntersected} = require('../../../app/renderer/lib/observerUtil')

// settings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/settings/Settings/g

? <TabTitle frameKey={this.props.frameKey} />
: null
}
<Favicon frameKey={this.props.frameKey} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this component and all bellow we should be passing tabId into them

docs/state.md Outdated
@@ -697,7 +696,8 @@ WindowStore
hoverTabIndex: number, // index of the current hovered tab
previewMode: boolean, // whether or not tab preview should be fired based on mouse idle time
previewTabPageIndex: number, // index of the tab being previewed
tabPageIndex: number // index of the current tab page
tabPageIndex: number, // index of the current tab page
intersectionRatio: number // at which tab size position the tab sentinel is being intersected
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please sort this alphabetically

const tabPageIndex = state.getIn(['ui', 'tabs', 'tabPageIndex'], 0)
const previewTabPageIndex = state.getIn(['ui', 'tabs', 'previewTabPageIndex'])

return previewTabPageIndex != null ? previewTabPageIndex : tabPageIndex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we simplify this to return state.getIn(['ui', 'tabs', 'previewTabPageIndex']) || tabPageIndex


const hasFrame = (state, frameKey) => {
const frame = getFrameByKey(state, frameKey)
return !frame.isEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getFrameByKey can return null as well so we need to add check here as well frame && !frame.isEmpty()


const getTabIdByFrameKey = (state, frameKey) => {
const frame = getFrameByKey(state, frameKey)
return frame.get('tabId', tabState.TAB_ID_NONE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to add null check here as well

}
// we only have one entry
const entry = entries[0]
windowActions.setTabIntersectionState(this.props.frameKey, entry.intersectionRatio)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called quite a lot of time when resizing, can we doubnce it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throttling tabs as we do now leads to #7925. I tried minimizing perf cost by observing only the first tab in a tab set. I'll share perf wins once I finish this monster

@ghost ghost added the sprint/1 label Sep 13, 2017
@cezaraugusto cezaraugusto changed the title improve performance for tabs, include intersectionObserver improve tabs performance and UI with this one simple trick Sep 14, 2017
@luixxiul
Copy link
Contributor

@cezaraugusto just in case let me remind you that detaching and reattaching tabs do not work for now. Also you cannot drag tabs.

@NejcZdovc
Copy link
Contributor

@luixxiul @cezaraugusto should be fixed with #10937, so I will review it and if it's ok I will merge it very soon

},

enforceFontVisibility: {
fontWeight: '600'
tab__title_bold: {
Copy link
Contributor

@luixxiul luixxiul Sep 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this would be a proper name as we have tab__title_windows below which has bolder font-weight. How about renaming it to tab__title_isDarwin?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!this.props.showIconAtReducedSize &&
css(styles.icon__loading, iconStyles.icon__loading_color)
)
: !this.props.favicon && css(styles.icon__default, iconStyles.icon__default_props)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have icon__default* for both styles and iconStyles. I'm wondering if it would be confusing a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed icon__default_props to icon__default_sizeAndColor. Not sure if best but more declarative ya

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

background: '#ddd',
borderColor: '#bbb',
color: '#000',
hover: {
Copy link
Contributor

@luixxiul luixxiul Sep 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind adding blank lines for readability?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

className={css(this.props.showCloseIcon && styles.closeTab)}
className={css(
styles.closeTab__icon,
this.props.centralizeTabIcons && styles.closeTab__icon__centered
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be closeTab__icon_centered?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return <TabIcon
className={css(styles.icon, styles.icon_audio)}
data-test-id={this.audioIcon}
className={css(styles.audio__tabIcon)}
Copy link
Contributor

@luixxiul luixxiul Sep 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind renaming this to styles.audioTab__icon following d69c3f7#diff-08a3077ca08d52dfaad29de7ae4d904aR75 or renaming the other?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const newSessionProps = StyleSheet.create({
newSession__indicator: {
filter: this.props.isActive && this.props.textIsWhite ? 'invert(100%)' : 'none'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind formatting like this? f672622#diff-bf09ff10e2191a87584e58393f99b728R44

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -73,21 +64,17 @@ class NewSessionIcon extends React.Component {
module.exports = ReduxComponent.connect(NewSessionIcon)

const styles = StyleSheet.create({
icon: {
newSession__icon: {
zIndex: 99,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind converting this into const and adding it to global.js so that we could see the relationship among z-index values?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -214,6 +214,7 @@ const globalStyles = {
zindexWindowIsPreview: '1100',
zindexDownloadsBar: '1000',
zindexTabs: '1000',
zindexTabsAudioTopBorder: '10001',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not 1001 instead of 10001?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@luixxiul luixxiul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ 👍

@luixxiul
Copy link
Contributor

@cezaraugusto please do not forget to merge this to 0.20.x too, thanks!

@cezaraugusto
Copy link
Contributor Author

master 9e6642f
0.20.x 472f152

@luixxiul
Copy link
Contributor

luixxiul commented Nov 6, 2017

By doing QA I must say the tabs area has been massively improved 🦁 Kudos to @cezaraugusto 🎖

syuan100 pushed a commit to syuan100/browser-laptop that referenced this pull request Nov 9, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.