🌐 AI搜索 & 代理 主页
Skip to content

Commit c71352d

Browse files
authored
fix(tooltips, popovers): fix memory leak (closes #4400) (#4401)
* fix(v-b-tooltip): memory leaks * Update bv-tooltip.js * Update bv-tooltip.js * Update bv-tooltip.js * Update bv-tooltip.js * Update bv-tooltip.js * Update bv-tooltip.js
1 parent 0518691 commit c71352d

File tree

1 file changed

+52
-74
lines changed

1 file changed

+52
-74
lines changed

src/components/tooltip/helpers/bv-tooltip.js

Lines changed: 52 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import Vue from '../../../utils/vue'
77
import getScopId from '../../../utils/get-scope-id'
88
import looseEqual from '../../../utils/loose-equal'
9+
import noop from '../../../utils/noop'
910
import { arrayIncludes, concat, from as arrayFrom } from '../../../utils/array'
1011
import {
1112
isElement,
@@ -34,7 +35,6 @@ import {
3435
import { keys } from '../../../utils/object'
3536
import { warn } from '../../../utils/warn'
3637
import { BvEvent } from '../../../utils/bv-event.class'
37-
3838
import { BVTooltipTemplate } from './bv-tooltip-template'
3939

4040
const NAME = 'BVTooltip'
@@ -203,7 +203,7 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
203203
this.$_hoverState = ''
204204
this.$_visibleInterval = null
205205
this.$_enabled = !this.disabled
206-
this.$_noop = () => {}
206+
this.$_noop = noop.bind(this)
207207

208208
// Destroy ourselves when the parent is destroyed
209209
if (this.$parent) {
@@ -236,18 +236,14 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
236236
// Remove all handler/listeners
237237
this.unListen()
238238
this.setWhileOpenListeners(false)
239-
240-
// Clear any timeouts/Timers
241-
clearTimeout(this.$_hoverTimeout)
242-
this.$_hoverTimeout = null
243-
239+
// Clear any timeouts/intervals
240+
this.clearHoverTimeout()
241+
this.clearVisibilityInterval()
242+
// Destroy the template
244243
this.destroyTemplate()
245-
this.restoreTitle()
246244
},
247245
methods: {
248-
//
249-
// Methods for creating and destroying the template
250-
//
246+
// --- Methods for creating and destroying the template ---
251247
getTemplate() {
252248
// Overridden by BVPopover
253249
return BVTooltipTemplate
@@ -273,7 +269,6 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
273269
},
274270
createTemplateAndShow() {
275271
// Creates the template instance and show it
276-
// this.destroyTemplate()
277272
const container = this.getContainer()
278273
const Template = this.getTemplate()
279274
const $tip = (this.$_tip = new Template({
@@ -323,19 +318,24 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
323318
// then emit the `hidden` event once it is fully hidden
324319
// The `hook:destroyed` will also be called (safety measure)
325320
this.$_tip && this.$_tip.hide()
321+
// Clear out any stragging active triggers
322+
this.clearActiveTriggers()
323+
// Reset the hover state
324+
this.$_hoverState = ''
326325
},
326+
// Destroy the template instance and reset state
327327
destroyTemplate() {
328-
// Destroy the template instance and reset state
329328
this.setWhileOpenListeners(false)
330-
clearTimeout(this.$_hoverTimeout)
331-
this.$_hoverTimout = null
329+
this.clearHoverTimeout()
332330
this.$_hoverState = ''
333331
this.clearActiveTriggers()
334332
this.localPlacementTarget = null
335333
try {
336334
this.$_tip && this.$_tip.$destroy()
337335
} catch {}
338336
this.$_tip = null
337+
this.removeAriaDescribedby()
338+
this.restoreTitle()
339339
this.localShow = false
340340
},
341341
getTemplateElement() {
@@ -355,13 +355,10 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
355355
})
356356
}
357357
},
358-
//
359-
// Show and Hide handlers
360-
//
358+
// --- Show/Hide handlers ---
359+
// Show the tooltip
361360
show() {
362-
// Show the tooltip
363361
const target = this.getTarget()
364-
365362
if (
366363
!target ||
367364
!contains(document.body, target) ||
@@ -375,38 +372,29 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
375372
// we exit without showing
376373
return
377374
}
378-
375+
// If tip already exists, exit early
379376
if (this.$_tip || this.localShow) {
380-
// If tip already exists, exit early
381377
/* istanbul ignore next */
382378
return
383379
}
384-
385380
// In the process of showing
386381
this.localShow = true
387-
388382
// Create a cancelable BvEvent
389383
const showEvt = this.buildEvent('show', { cancelable: true })
390384
this.emitEvent(showEvt)
385+
// Don't show if event cancelled
391386
/* istanbul ignore next: ignore for now */
392387
if (showEvt.defaultPrevented) {
393-
// Don't show if event cancelled
394388
// Destroy the template (if for some reason it was created)
395389
/* istanbul ignore next */
396390
this.destroyTemplate()
397-
// Clear the localShow flag
398-
/* istanbul ignore next */
399-
this.localShow = false
400391
/* istanbul ignore next */
401392
return
402393
}
403-
404394
// Fix the title attribute on target
405395
this.fixTitle()
406-
407396
// Set aria-describedby on target
408397
this.addAriaDescribedby()
409-
410398
// Create and show the tooltip
411399
this.createTemplateAndShow()
412400
},
@@ -433,11 +421,6 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
433421

434422
// Tell the template to hide
435423
this.hideTemplate()
436-
// TODO: The following could be added to `hideTemplate()`
437-
// Clear out any stragging active triggers
438-
this.clearActiveTriggers()
439-
// Reset the hover state
440-
this.$_hoverState = ''
441424
},
442425
forceHide() {
443426
// Forcefully hides/destroys the template, regardless of any active triggers
@@ -450,8 +433,7 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
450433
// This is also done in the template `hide` evt handler
451434
this.setWhileOpenListeners(false)
452435
// Clear any hover enter/leave event
453-
clearTimeout(this.hoverTimeout)
454-
this.$_hoverTimeout = null
436+
this.clearHoverTimeout()
455437
this.$_hoverState = ''
456438
this.clearActiveTriggers()
457439
// Disable the fade animation on the template
@@ -464,49 +446,42 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
464446
enable() {
465447
this.$_enabled = true
466448
// Create a non-cancelable BvEvent
467-
this.emitEvent(this.buildEvent('enabled', {}))
449+
this.emitEvent(this.buildEvent('enabled'))
468450
},
469451
disable() {
470452
this.$_enabled = false
471453
// Create a non-cancelable BvEvent
472-
this.emitEvent(this.buildEvent('disabled', {}))
454+
this.emitEvent(this.buildEvent('disabled'))
473455
},
474-
//
475-
// Handlers for template events
476-
//
456+
// --- Handlers for template events ---
457+
// When template is inserted into DOM, but not yet shown
477458
onTemplateShow() {
478-
// When template is inserted into DOM, but not yet shown
479459
// Enable while open listeners/watchers
480460
this.setWhileOpenListeners(true)
481461
},
462+
// When template show transition completes
482463
onTemplateShown() {
483-
// When template show transition completes
484464
const prevHoverState = this.$_hoverState
485465
this.$_hoverState = ''
486466
if (prevHoverState === 'out') {
487467
this.leave(null)
488468
}
489469
// Emit a non-cancelable BvEvent 'shown'
490-
this.emitEvent(this.buildEvent('shown', {}))
470+
this.emitEvent(this.buildEvent('shown'))
491471
},
472+
// When template is starting to hide
492473
onTemplateHide() {
493-
// When template is starting to hide
494474
// Disable while open listeners/watchers
495475
this.setWhileOpenListeners(false)
496476
},
477+
// When template has completed closing (just before it self destructs)
497478
onTemplateHidden() {
498-
// When template has completed closing (just before it self destructs)
499-
// TODO:
500-
// The next two lines could be moved into `destroyTemplate()`
501-
this.removeAriaDescribedby()
502-
this.restoreTitle()
479+
// Destroy the template
503480
this.destroyTemplate()
504481
// Emit a non-cancelable BvEvent 'shown'
505482
this.emitEvent(this.buildEvent('hidden', {}))
506483
},
507-
//
508-
// Utility methods
509-
//
484+
// --- Utility methods ---
510485
getTarget() {
511486
// Handle case where target may be a component ref
512487
let target = this.target ? this.target.$el || this.target : null
@@ -566,6 +541,18 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
566541
const target = this.getTarget()
567542
return this.isDropdown() && target && select(DROPDOWN_OPEN_SELECTOR, target)
568543
},
544+
clearHoverTimeout() {
545+
if (this.$_hoverTimeout) {
546+
clearTimeout(this.$_hoverTimeout)
547+
this.$_hoverTimeout = null
548+
}
549+
},
550+
clearVisibilityInterval() {
551+
if (this.$_visibleInterval) {
552+
clearInterval(this.$_visibleInterval)
553+
this.$_visibleInterval = null
554+
}
555+
},
569556
clearActiveTriggers() {
570557
for (const trigger in this.activeTrigger) {
571558
this.activeTrigger[trigger] = false
@@ -619,9 +606,7 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
619606
removeAttr(target, 'data-original-title')
620607
}
621608
},
622-
//
623-
// BvEvent helpers
624-
//
609+
// --- BvEvent helpers ---
625610
buildEvent(type, opts = {}) {
626611
// Defaults to a non-cancellable event
627612
return new BvEvent(type, {
@@ -644,20 +629,16 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
644629
}
645630
this.$emit(evtName, bvEvt)
646631
},
647-
//
648-
// Event handler setup methods
649-
//
632+
// --- Event handler setup methods ---
650633
listen() {
651634
// Enable trigger event handlers
652635
const el = this.getTarget()
653636
if (!el) {
654637
/* istanbul ignore next */
655638
return
656639
}
657-
658640
// Listen for global show/hide events
659641
this.setRootListener(true)
660-
661642
// Set up our listeners on the target trigger element
662643
this.computedTriggers.forEach(trigger => {
663644
if (trigger === 'click') {
@@ -712,14 +693,13 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
712693
// On-touch start listeners
713694
this.setOnTouchStartListener(on)
714695
},
696+
// Handler for periodic visibility check
715697
visibleCheck(on) {
716-
// Handler for periodic visibility check
717-
clearInterval(this.$_visibleInterval)
718-
this.$_visibleInterval = null
698+
this.clearVisibilityInterval()
719699
const target = this.getTarget()
720700
const tip = this.getTemplateElement()
721701
if (on) {
722-
this.visibleInterval = setInterval(() => {
702+
this.$_visibleInterval = setInterval(() => {
723703
if (tip && this.localShow && (!target.parentNode || !isVisible(target))) {
724704
// Target element is no longer visible or not in DOM, so force-hide the tooltip
725705
this.forceHide()
@@ -762,9 +742,7 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
762742
target.__vue__[on ? '$on' : '$off']('shown', this.forceHide)
763743
}
764744
},
765-
//
766-
// Event handlers
767-
//
745+
// --- Event handlers ---
768746
handleEvent(evt) {
769747
// General trigger event handler
770748
// target is the trigger element
@@ -884,14 +862,14 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
884862
this.$_hoverState = 'in'
885863
return
886864
}
887-
clearTimeout(this.hoverTimeout)
865+
this.clearHoverTimeout()
888866
this.$_hoverState = 'in'
889867
if (!this.computedDelay.show) {
890868
this.show()
891869
} else {
892870
// Hide any title attribute while enter delay is active
893871
this.fixTitle()
894-
this.hoverTimeout = setTimeout(() => {
872+
this.$_hoverTimeout = setTimeout(() => {
895873
/* istanbul ignore else */
896874
if (this.$_hoverState === 'in') {
897875
this.show()
@@ -917,12 +895,12 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
917895
if (this.isWithActiveTrigger) {
918896
return
919897
}
920-
clearTimeout(this.hoverTimeout)
898+
this.clearHoverTimeout()
921899
this.$_hoverState = 'out'
922900
if (!this.computedDelay.hide) {
923901
this.hide()
924902
} else {
925-
this.$hoverTimeout = setTimeout(() => {
903+
this.$_hoverTimeout = setTimeout(() => {
926904
if (this.$_hoverState === 'out') {
927905
this.hide()
928906
}

0 commit comments

Comments
 (0)