0

I have a very strange bug, what appears to be a race condition makes a width(); call give different results.

I have this jQuery after call:

this.after(
    "<div class='menuItemContainer'>" +
        "<div class='menuItemTitle'>" +
            $("option:selected", this).text() + "<span class='menuItemDownArrow'>&#9660;</span>" +
        "</div>" +
        "<div class='menuItemList'>" +
        selectionMarkup +
        "</div>" +
    "</div>"
);

In which I insert html into the DOM.

Next I need to know the combined widths of an element with the class menuItemColums and make their parent div that width.

This markup exists in the variable selectionMarkup.

To do this I do the following:

var w = 0;
$(".menuItemColumn").each(function(){
    w += $(this).width();
    console.log($(this));
    console.log(w);
});
$(".menuItemList").css({
    width: w}
);

This works exactly as expected around 70% of the times, but sometimes I get the wrong measure for some of the elements, or at least for sure the first one sometimes is wrong.

The weird part is that the object seem to be correct, and it's properties are the correct width, but the given widht() which is assigned to w is incorrect:

enter image description here

That image, just to clarify shows the result of logging w and the $(this) which should be the same, since $(this)'s width property seems to be the correct one.

I kept debugging and found out two things that lead be to believe that this is race condition:

If I execute the last piece of code I posted you again, when the bug is reproduced, it fixes the problem.

If I do the following calls, the give me different results, one incorrect and the other one (second one) correct:

console.log($(".menuItemColumn:first").width());
setTimeout(function(){console.log($(".menuItemColumn:first").width());},3000);

I was under the impression that the second part of the code will not execute until the first one is finished, what gives here?

Note: I'm not 100% sure that race condition is the word I was looking for, but still, the idea is that.


UPDATE:

As @WereWolf-TheAlpha the output of the console doesn't seem like jquery objects, I now can't reproduce that and I'm only getting this:

enter image description here

Without changing any code.

Mohammad
  • 21,175
  • 15
  • 55
  • 84
Trufa
  • 39,971
  • 43
  • 126
  • 190
  • It (`this.after`) doesn't look like a `jQuery` object. – The Alpha Apr 24 '14 at 20:52
  • 1
    @WereWolf-TheAlpha good point I hadn't noticed, I'll check out what's happening there. – Trufa Apr 24 '14 at 20:54
  • No no, the output _does_ seem like jQuery objects, which are conceptually sets of DOM elements. Each element in the jQuery selection set is not a jQuery object (like, at position 0) but a DOM element. – Benjamin Gruenbaum Apr 24 '14 at 21:33
  • I'm guessing the browser did not update the width yet, just for the experiment, try putting it in a setTimeout, `.width` needs to be _after_ appending to get the correct value. – Benjamin Gruenbaum Apr 24 '14 at 21:34
  • @BenjaminGruenbaum with timeout it seems to work correctly. – Trufa Apr 24 '14 at 21:40
  • @BenjaminGruenbaum also note that for some strange reason, without changing the code, I'm not getting a different output, that looks like n object, please check the update. – Trufa Apr 24 '14 at 21:41

1 Answers1

1

The issue here is that the Chrome inspector will provide a reference to an object while the object is collapsed in the inspector. When you click that arrow to expand the object, its attributes become real. You can run this to see it occur:

var obj = {};
for (i=0; i<100; i++)
  obj['foo'+i] = i;
console.log(obj);
for (i=0; i<100; i++)
  obj['foo'+i] = 'gotcha';
console.log(obj);

Now, expand the first object in your inspector. Gotcha!

000
  • 26,951
  • 10
  • 71
  • 101
  • It is only partial true. It is not a _full_ reference, at the time the object is expanded the values of the properties are retrieved. If the object is changed another time after that, then those properties don't update. So all unexpanded properties are just references, but become _frozen_ when they are expanded, which will result in some strange effects. – t.niese Apr 24 '14 at 21:04
  • @Joe thanks for the answer but I really don't understand how this can help me :) – Trufa Apr 24 '14 at 21:36
  • @Trufa it partial explains what you see. The `282` shows you the value that was calculated while you where in the script execution. The `73` on the other hand is the `offsetWidth` the object had at the time you expanded the object in the `console` which may be **after** the layout engine did a relayout cause of the added elements. – t.niese Apr 24 '14 at 21:51
  • @t.niese ok, just wanted to be sure, that was the only point, and yes, the problem does seem to be that at the moment of execution the element is still not there. – Trufa Apr 24 '14 at 21:53
  • @Trufa the object is there immediately after you call `elm.after(...)`, but it could be that the relayout did not happen at the time, which could have different reasons (e.g. if you use web fonts that where not fully loaded at that time, that the stylesheet was not fully loaded or other things that could cause the layout engine to postpone its task) Except an old outdated post [DIV width immediately after append is calculating wrong?](http://stackoverflow.com/questions/5017923/div-width-immediately-after-append-is-calculating-wrong) i have no current link right now that proofs my claim. – t.niese Apr 24 '14 at 22:01
  • @t.niese interesting, I tried to solve it with a callback but it doesn't seem to work, is there anything triggered after re-layout? – Trufa Apr 24 '14 at 22:03
  • Have you looked into requestAnimationFrame? – 000 Apr 25 '14 at 02:13