From f10e95956bd850e1e59477276b05361178d1018e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 28 May 2021 14:59:14 +0100 Subject: [PATCH 1/6] Use passive option for scroll handler --- src/CountlyAnalytics.ts | 4 +++- src/components/structures/AutoHideScrollbar.js | 15 ++++++++++++++- src/components/structures/IndicatorScrollbar.js | 4 +++- src/components/structures/LeftPanel.tsx | 7 +++++-- src/components/views/rooms/RoomSublist.tsx | 9 +++++++-- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts index aac53b188b..5545ed8483 100644 --- a/src/CountlyAnalytics.ts +++ b/src/CountlyAnalytics.ts @@ -816,7 +816,9 @@ export default class CountlyAnalytics { window.addEventListener("mousemove", this.onUserActivity); window.addEventListener("click", this.onUserActivity); window.addEventListener("keydown", this.onUserActivity); - window.addEventListener("scroll", this.onUserActivity); + // Using the passive option to not block the main thread + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners + window.addEventListener("scroll", this.onUserActivity, { passive: true }); this.activityIntervalId = setInterval(() => { this.inactivityCounter++; diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index 14f7c9ca83..a2dcff2731 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -23,6 +23,20 @@ export default class AutoHideScrollbar extends React.Component { this._collectContainerRef = this._collectContainerRef.bind(this); } + componentDidMount() { + if (this.containerRef && this.props.onScroll) { + // Using the passive option to not block the main thread + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners + this.containerRef.addEventListener("scroll", this.props.onScroll, { passive: true }); + } + } + + componentWillUnmount() { + if (this.containerRef && this.props.onScroll) { + this.containerRef.removeEventListener("scroll", this.props.onScroll); + } + } + _collectContainerRef(ref) { if (ref && !this.containerRef) { this.containerRef = ref; @@ -41,7 +55,6 @@ export default class AutoHideScrollbar extends React.Component { ref={this._collectContainerRef} style={this.props.style} className={["mx_AutoHideScrollbar", this.props.className].join(" ")} - onScroll={this.props.onScroll} onWheel={this.props.onWheel} tabIndex={this.props.tabIndex} > diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 341ab2df71..51a3b287f0 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -59,7 +59,9 @@ export default class IndicatorScrollbar extends React.Component { _collectScroller(scroller) { if (scroller && !this._scrollElement) { this._scrollElement = scroller; - this._scrollElement.addEventListener("scroll", this.checkOverflow); + // Using the passive option to not block the main thread + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners + this._scrollElement.addEventListener("scroll", this.checkOverflow, { passive: true }); this.checkOverflow(); } } diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 7df4bcadf3..a5079c0ed5 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -97,6 +97,9 @@ export default class LeftPanel extends React.Component { public componentDidMount() { UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current); UIStore.instance.on("ListContainer", this.refreshStickyHeaders); + // Using the passive option to not block the main thread + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners + this.listContainerRef.current.addEventListener("scroll", this.onScroll, { passive: true }); } public componentWillUnmount() { @@ -108,6 +111,7 @@ export default class LeftPanel extends React.Component { SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); UIStore.instance.stopTrackingElementDimensions("ListContainer"); UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders); + this.listContainerRef.current.removeEventListener("scroll", this.onScroll); } public componentDidUpdate(prevProps: IProps, prevState: IState): void { @@ -295,7 +299,7 @@ export default class LeftPanel extends React.Component { } } - private onScroll = (ev: React.MouseEvent) => { + private onScroll = (ev: Event) => { const list = ev.target as HTMLDivElement; this.handleStickyHeaders(list); }; @@ -459,7 +463,6 @@ export default class LeftPanel extends React.Component {
{ private headerButton = createRef(); private sublistRef = createRef(); + private tilesRef = createRef(); private dispatcherRef: string; private layout: ListLayout; private heightAtStart: number; @@ -246,11 +247,15 @@ export default class RoomSublist extends React.Component { public componentDidMount() { this.dispatcherRef = defaultDispatcher.register(this.onAction); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onListsUpdated); + // Using the passive option to not block the main thread + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners + this.tilesRef.current.addEventListener("scroll", this.onScrollPrevent, { passive: true }); } public componentWillUnmount() { defaultDispatcher.unregister(this.dispatcherRef); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated); + this.tilesRef.current.removeEventListener("scroll", this.onScrollPrevent); } private onListsUpdated = () => { @@ -755,7 +760,7 @@ export default class RoomSublist extends React.Component { ); } - private onScrollPrevent(e: React.UIEvent) { + private onScrollPrevent(e: Event) { // the RoomTile calls scrollIntoView and the browser may scroll a div we do not wish to be scrollable // this fixes https://github.com/vector-im/element-web/issues/14413 (e.target as HTMLDivElement).scrollTop = 0; @@ -884,7 +889,7 @@ export default class RoomSublist extends React.Component { className="mx_RoomSublist_resizeBox" enable={handles} > -
+
{visibleTiles}
{showNButton} From 18188538f6ca656d1388855faeaed0fbbb3afc2b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 28 May 2021 14:59:54 +0100 Subject: [PATCH 2/6] Move the read receipt animation to the compositing layer --- res/css/views/rooms/_EventTile.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 5d1dd04383..5d477d63c9 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -280,6 +280,7 @@ $left-gutter: 64px; height: $font-14px; width: $font-14px; + will-change: left, top; transition: left var(--transition-short) ease-out, top var(--transition-standard) ease-out; From fd69fce1bac58911a9829a610385dbeeea469281 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 28 May 2021 17:37:29 +0100 Subject: [PATCH 3/6] guard event listener from null values --- src/components/structures/LeftPanel.tsx | 4 ++-- src/components/views/rooms/RoomSublist.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index a5079c0ed5..5b6b9c3717 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -99,7 +99,7 @@ export default class LeftPanel extends React.Component { UIStore.instance.on("ListContainer", this.refreshStickyHeaders); // Using the passive option to not block the main thread // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - this.listContainerRef.current.addEventListener("scroll", this.onScroll, { passive: true }); + this.listContainerRef.current?.addEventListener("scroll", this.onScroll, { passive: true }); } public componentWillUnmount() { @@ -111,7 +111,7 @@ export default class LeftPanel extends React.Component { SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); UIStore.instance.stopTrackingElementDimensions("ListContainer"); UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders); - this.listContainerRef.current.removeEventListener("scroll", this.onScroll); + this.listContainerRef.current?.removeEventListener("scroll", this.onScroll); } public componentDidUpdate(prevProps: IProps, prevState: IState): void { diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index cd3d9bc73a..0bb7381dbc 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -249,13 +249,13 @@ export default class RoomSublist extends React.Component { RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onListsUpdated); // Using the passive option to not block the main thread // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - this.tilesRef.current.addEventListener("scroll", this.onScrollPrevent, { passive: true }); + this.tilesRef.current?.addEventListener("scroll", this.onScrollPrevent, { passive: true }); } public componentWillUnmount() { defaultDispatcher.unregister(this.dispatcherRef); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated); - this.tilesRef.current.removeEventListener("scroll", this.onScrollPrevent); + this.tilesRef.current?.removeEventListener("scroll", this.onScrollPrevent); } private onListsUpdated = () => { From 308ac505a8be7046c2b58649fb9f6b6a103771ca Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 1 Jun 2021 14:13:46 +0100 Subject: [PATCH 4/6] Migrate AutoHideScrollbar to TypeScript Also changed the way the React.RefObject is collected --- ...HideScrollbar.js => AutoHideScrollbar.tsx} | 41 ++++++++++--------- .../dialogs/AddExistingToSpaceDialog.tsx | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) rename src/components/structures/{AutoHideScrollbar.js => AutoHideScrollbar.tsx} (61%) diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.tsx similarity index 61% rename from src/components/structures/AutoHideScrollbar.js rename to src/components/structures/AutoHideScrollbar.tsx index a2dcff2731..4a577a8dbd 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.tsx @@ -17,42 +17,43 @@ limitations under the License. import React from "react"; -export default class AutoHideScrollbar extends React.Component { - constructor(props) { - super(props); - this._collectContainerRef = this._collectContainerRef.bind(this); - } +interface IProps { + className?: string; + onScroll?: () => void; + onWheel?: () => void; + style?: React.CSSProperties + tabIndex?: number, + wrappedRef?: (ref: HTMLDivElement) => void; +} + +export default class AutoHideScrollbar extends React.Component { + private containerRef: React.RefObject = React.createRef(); componentDidMount() { - if (this.containerRef && this.props.onScroll) { + if (this.containerRef.current && this.props.onScroll) { // Using the passive option to not block the main thread // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - this.containerRef.addEventListener("scroll", this.props.onScroll, { passive: true }); + this.containerRef.current.addEventListener("scroll", this.props.onScroll, { passive: true }); + } + + if (this.props.wrappedRef) { + this.props.wrappedRef(this.containerRef.current); } } componentWillUnmount() { - if (this.containerRef && this.props.onScroll) { - this.containerRef.removeEventListener("scroll", this.props.onScroll); - } - } - - _collectContainerRef(ref) { - if (ref && !this.containerRef) { - this.containerRef = ref; - } - if (this.props.wrappedRef) { - this.props.wrappedRef(ref); + if (this.containerRef.current && this.props.onScroll) { + this.containerRef.current.removeEventListener("scroll", this.props.onScroll); } } getScrollTop() { - return this.containerRef.scrollTop; + return this.containerRef.current.scrollTop; } render() { return (
= ({ autoComplete={true} autoFocus={true} /> - + { rooms.length > 0 ? (

{ _t("Rooms") }

From 73ca6b2ad044523b073f2339cb8d584af45efc78 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 1 Jun 2021 14:14:02 +0100 Subject: [PATCH 5/6] Add passive flag to Tooltip scroll event listener --- src/components/views/elements/Tooltip.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/Tooltip.tsx b/src/components/views/elements/Tooltip.tsx index 7e9ce9745c..0202c6b02f 100644 --- a/src/components/views/elements/Tooltip.tsx +++ b/src/components/views/elements/Tooltip.tsx @@ -70,7 +70,10 @@ export default class Tooltip extends React.Component { this.tooltipContainer = document.createElement("div"); this.tooltipContainer.className = "mx_Tooltip_wrapper"; document.body.appendChild(this.tooltipContainer); - window.addEventListener('scroll', this.renderTooltip, true); + window.addEventListener('scroll', this.renderTooltip, { + passive: true, + capture: true, + }); this.parent = ReactDOM.findDOMNode(this).parentNode as Element; @@ -85,7 +88,9 @@ export default class Tooltip extends React.Component { public componentWillUnmount() { ReactDOM.unmountComponentAtNode(this.tooltipContainer); document.body.removeChild(this.tooltipContainer); - window.removeEventListener('scroll', this.renderTooltip, true); + window.removeEventListener('scroll', this.renderTooltip, { + capture: true, + }); } private updatePosition(style: CSSProperties) { From 591314141bb10d19028c3379357587571abe43bd Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 1 Jun 2021 14:15:42 +0100 Subject: [PATCH 6/6] Add methods visibility for AutoHideScrollbar --- src/components/structures/AutoHideScrollbar.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx index 4a577a8dbd..66f998b616 100644 --- a/src/components/structures/AutoHideScrollbar.tsx +++ b/src/components/structures/AutoHideScrollbar.tsx @@ -29,7 +29,7 @@ interface IProps { export default class AutoHideScrollbar extends React.Component { private containerRef: React.RefObject = React.createRef(); - componentDidMount() { + public componentDidMount() { if (this.containerRef.current && this.props.onScroll) { // Using the passive option to not block the main thread // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners @@ -41,17 +41,17 @@ export default class AutoHideScrollbar extends React.Component { } } - componentWillUnmount() { + public componentWillUnmount() { if (this.containerRef.current && this.props.onScroll) { this.containerRef.current.removeEventListener("scroll", this.props.onScroll); } } - getScrollTop() { + public getScrollTop(): number { return this.containerRef.current.scrollTop; } - render() { + public render() { return (