From a13d8445ec263cbb817820b45a7ac56b5e7f5648 Mon Sep 17 00:00:00 2001 From: Arooba Shahoor <56495631+Arooba-git@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:17:44 +0900 Subject: [PATCH] Clear timing events and event listeners before component unmounts (buefy/buefy#4000) * Clear timing events and event listeners before component unmounts - The following components clear timeouts before unmounted so that timeout callbacks won't access unmounted component instances: - `Autocomplete` - `ColorpickerHSLRepresentationSquare` - `ColorpickerHSLRepresentationTriangle` - `Dropdown` - `Slider` - `Taginput` - `Tooltip` * feat(lib): revert unnecessary clearTimeout - Reverts unnecessary `clearTimeout` calls in: - `Dialog`: https://github.com/buefy/buefy/pull/4000#discussion_r1470558340 - `Loading`: https://github.com/buefy/buefy/pull/4000#discussion_r1470560013 - `Modal`: https://github.com/buefy/buefy/pull/4000#discussion_r1470561265 - `NotificationNotice`: https://github.com/buefy/buefy/pull/4000#discussion_r1470563177 --------- Co-authored-by: Kikuo Emoto --- src/components/autocomplete/Autocomplete.vue | 6 ++++-- .../ColorpickerHSLRepresentationSquare.vue | 2 ++ .../ColorpickerHSLRepresentationTriangle.vue | 2 ++ src/components/dropdown/Dropdown.vue | 10 +++++++--- src/components/slider/Slider.vue | 9 +++++++-- src/components/taginput/Taginput.vue | 9 +++++++-- src/components/tooltip/Tooltip.vue | 13 ++++++++++--- 7 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/autocomplete/Autocomplete.vue b/src/components/autocomplete/Autocomplete.vue index dd4acd020..131118748 100644 --- a/src/components/autocomplete/Autocomplete.vue +++ b/src/components/autocomplete/Autocomplete.vue @@ -173,7 +173,8 @@ export default { style: {}, _isAutocomplete: true, _elementRef: 'input', - _bodyEl: undefined // Used to append to body + _bodyEl: undefined, // Used to append to body + timeOutID: null } }, computed: { @@ -311,7 +312,7 @@ export default { this.calcDropdownInViewportVertical() } else { // Timeout to wait for the animation to finish before recalculating - setTimeout(() => { + this.timeOutID = setTimeout(() => { this.calcDropdownInViewportVertical() }, 100) } @@ -729,6 +730,7 @@ export default { if (this.appendToBody) { removeElement(this.$data._bodyEl) } + clearTimeout(this.timeOutID) } } diff --git a/src/components/colorpicker/ColorpickerHSLRepresentationSquare.vue b/src/components/colorpicker/ColorpickerHSLRepresentationSquare.vue index a1496ec53..c6bfb859e 100644 --- a/src/components/colorpicker/ColorpickerHSLRepresentationSquare.vue +++ b/src/components/colorpicker/ColorpickerHSLRepresentationSquare.vue @@ -361,6 +361,8 @@ export default { window.removeEventListener('touchmove', this.trackMouse) window.removeEventListener('mouseup', this.stopMouseCapture) window.removeEventListener('touchend', this.stopMouseCapture) + + clearTimeout(this.debounce) } } diff --git a/src/components/colorpicker/ColorpickerHSLRepresentationTriangle.vue b/src/components/colorpicker/ColorpickerHSLRepresentationTriangle.vue index a45b5aab1..a191298b5 100644 --- a/src/components/colorpicker/ColorpickerHSLRepresentationTriangle.vue +++ b/src/components/colorpicker/ColorpickerHSLRepresentationTriangle.vue @@ -439,6 +439,8 @@ export default { window.removeEventListener('touchmove', this.trackMouse) window.removeEventListener('mouseup', this.stopMouseCapture) window.removeEventListener('touchend', this.stopMouseCapture) + + clearTimeout(this.debounce) } } diff --git a/src/components/dropdown/Dropdown.vue b/src/components/dropdown/Dropdown.vue index 38f9c1263..35fe029c7 100644 --- a/src/components/dropdown/Dropdown.vue +++ b/src/components/dropdown/Dropdown.vue @@ -142,7 +142,9 @@ export default { isHoverable: false, maybeTap: false, isTouchEnabled: false, - _bodyEl: undefined // Used to append to body + _bodyEl: undefined, // Used to append to body + timeOutID: null, + timeOutID2: null } }, computed: { @@ -198,7 +200,7 @@ export default { // also takes care of chattering, e.g., repeated quick taps, // otherwise the flag may become inconsistent with the actual // state of the dropdown menu - setTimeout(() => { + this.timeOutID = setTimeout(() => { if (!this.isActive) { this.isTouchEnabled = false } @@ -368,7 +370,7 @@ export default { const value = !this.isActive this.isActive = value // Vue 2.6.x ??? - setTimeout(() => (this.isActive = value)) + this.timeOutID2 = setTimeout(() => (this.isActive = value)) }) } else { this.isActive = !this.isActive @@ -447,6 +449,8 @@ export default { if (this.appendToBody) { removeElement(this.$data._bodyEl) } + clearTimeout(this.timeOutID) + clearTimeout(this.timeOutID2) } } diff --git a/src/components/slider/Slider.vue b/src/components/slider/Slider.vue index 4fec46352..4e4562843 100644 --- a/src/components/slider/Slider.vue +++ b/src/components/slider/Slider.vue @@ -151,7 +151,8 @@ export default { value2: null, dragging: false, isRange: false, - _isSlider: true // Used by Thumb and Tick + _isSlider: true, // Used by Thumb and Tick + timeOutID: null } }, computed: { @@ -288,7 +289,7 @@ export default { }, onDragEnd() { this.isTrackClickDisabled = true - setTimeout(() => { + this.timeOutID = setTimeout(() => { // avoid triggering onSliderClick after dragend this.isTrackClickDisabled = false }, 0) @@ -308,6 +309,10 @@ export default { this.isThumbReversed = false this.isTrackClickDisabled = false this.setValues(this.value) + }, + + beforeDestroy() { + clearTimeout(this.timeOutID) } } diff --git a/src/components/taginput/Taginput.vue b/src/components/taginput/Taginput.vue index 9ee54c6c2..19893a817 100644 --- a/src/components/taginput/Taginput.vue +++ b/src/components/taginput/Taginput.vue @@ -196,7 +196,8 @@ export default { newTag: '', isComposing: false, _elementRef: 'autocomplete', - _isTaginput: true + _isTaginput: true, + requestID: null } }, computed: { @@ -301,7 +302,7 @@ export default { } // after autocomplete events - requestAnimationFrame(() => { + this.requestID = requestAnimationFrame(() => { this.newTag = '' this.$emit('typing', '') }) @@ -372,6 +373,10 @@ export default { emitInfiniteScroll() { this.$emit('infinite-scroll') } + }, + + beforeDestroy() { + cancelAnimationFrame(this.requestID) } } diff --git a/src/components/tooltip/Tooltip.vue b/src/components/tooltip/Tooltip.vue index a4295916c..cffdf2c74 100644 --- a/src/components/tooltip/Tooltip.vue +++ b/src/components/tooltip/Tooltip.vue @@ -96,7 +96,8 @@ export default { timer: null, _bodyEl: undefined, // Used to append to body resizeObserver: undefined, - resizeListener: undefined + resizeListener: undefined, + timeOutID: null } }, computed: { @@ -199,7 +200,7 @@ export default { // if not active, toggle after clickOutside event // this fixes toggling programmatic this.$nextTick(() => { - setTimeout(() => this.open()) + this.timeOutID = setTimeout(() => this.open()) }) }, onHover() { @@ -284,6 +285,7 @@ export default { } }, mounted() { + this.controller = new window.AbortController() if (this.appendToBody && typeof window !== 'undefined') { this.$data._bodyEl = createAbsoluteElement(this.$refs.content) this.updateAppendToBody() @@ -295,7 +297,9 @@ export default { this.updateAppendToBody() animation.removeEventListener('transitionend', listener) } - animation.addEventListener('transitionend', listener) + animation.addEventListener('transitionend', listener, { + signal: this.controller.signal + }) } // observes changes in the window size this.resizeListener = () => this.updateAppendToBody() @@ -327,6 +331,9 @@ export default { if (this.appendToBody) { removeElement(this.$data._bodyEl) } + this.controller.abort() + clearTimeout(this.timer) + clearTimeout(this.timeOutID) } }