Bootstrap: Race condition when destroying and re-initializing tooltip

Created on 27 Apr 2015  路  5Comments  路  Source: twbs/bootstrap

A recent change (https://github.com/twbs/bootstrap/commit/76762169c359b381f70fa518daef09e76b0aefe3) creates a situation where it is impossible to ensure that the destroying of a tooltip has been properly finished before re-initializing it. This creates a race condition where the newly initialized tooltip may (in my testing, in all cases) get destroyed by the previously called destroy call.

Observe:

$('.my-element').tooltip();
$('.my-element').tooltip('destroy');
$('.my-element').tooltip();

We will never see the tooltip because the destroy method actually removes data, etc only after the tooltip has been hidden, which happens asynchronously. This means that the second tooltip() call finishes before destroy, and this means that destroy will remove the newly initialized tooltip data form the element.

I'm not sure what the best solution is, but an option would be to allow passing in a callback function to the destroy method, but I don't really like the idea of having to dive into async world when destroying a tooltip.

js

Most helpful comment

@escobar5 here's how the mentioned work around should look like:

destroyTooltip: function ($targetElement) {
    var bsData = $targetElement.data()["bs.tooltip"];
    if (bsData && bsData.$tip) {
        bsData.$tip.removeClass("fade");
    }

    $targetElement.tooltip("destroy");
}

When fade class is removed, this condition will not pass and as a result destroy will run synchronously. Tested in 3.3.7.

All 5 comments

Yep I agree this is a problem, but don't really think it's worth addressing in 3.+.

One work around would be to call hide yourself first and then remove the fade class from the tooltip - this would force destroy to be synchronous.

Not 100% the circumstances around why you are trying to destroy the tip and reintialize it. My hunch because they're coming sequentially like that is that you should probably just be using show/hide alone (maybe with manual triggers), but again it's hard to say without knowing more

@fat can you explain me further the work around? I'm having the same problem and I'm not being able to open a new popover without it being closed

@escobar5 here's how the mentioned work around should look like:

destroyTooltip: function ($targetElement) {
    var bsData = $targetElement.data()["bs.tooltip"];
    if (bsData && bsData.$tip) {
        bsData.$tip.removeClass("fade");
    }

    $targetElement.tooltip("destroy");
}

When fade class is removed, this condition will not pass and as a result destroy will run synchronously. Tested in 3.3.7.

Unfortunately there's a corner-case with this approach I recently encountered in my project - I started getting "Uncaught TypeError: Cannot read property 'trigger' of null".
This is due to the fact that complete-callback in enter method can fire after tooltip is destroyed:

      var complete = function () {
        var prevHoverState = that.hoverState
        that.$element.trigger('shown.bs.' + that.type)
        that.hoverState = null

        if (prevHoverState == 'out') that.leave(that)
      }

that.$element is null.

Here's full stack:

bootstrap.js:1491 Uncaught TypeError: Cannot read property 'trigger' of null
    at HTMLDivElement.complete (bootstrap.js:1491)
    at HTMLDivElement.fn (jquery.js:4825)
    at HTMLDivElement.handle (bootstrap.js:72)
    at HTMLDivElement.dispatch (jquery.js:5226)
    at HTMLDivElement.$event.dispatch (jquery.event.drag.js:374)
    at HTMLDivElement.elemData.handle (jquery.js:4878)
    at Object.trigger (jquery.js:5130)
    at HTMLDivElement.<anonymous> (jquery.js:5860)
    at Function.each (jquery.js:370)
    at jQuery.fn.init.each (jquery.js:137)

So it's a check needed in complete callback:

if (!that.$element) return
Was this page helpful?
0 / 5 - 0 ratings

Related issues

IamManchanda picture IamManchanda  路  3Comments

cvrebert picture cvrebert  路  3Comments

devfrey picture devfrey  路  3Comments

steve-32a picture steve-32a  路  3Comments

leomao10 picture leomao10  路  3Comments