#4512 closed bug (fixed)
jquery-1.3.2.min.js "visible" selector doesn't run properly
Reported by: | qiuzj | Owned by: | snover |
---|---|---|---|
Priority: | high | Milestone: | 1.4.3 |
Component: | css | Version: | 1.4.2 |
Keywords: | Cc: | dmethvin, snover | |
Blocked by: | Blocking: |
Description
HTML Code: <table>
<tr style="display:none"><td>Value 1</td></tr> <tr><td>Value 2</td></tr>
</table>
jQuery Code: $("tr:visible")
Results: [ <tr style="display:none"><td>Value 1</td></tr>,<tr><td>Value 2</td></tr> ]
Attachments (3)
Change History (38)
comment:1 Changed 14 years ago by
comment:2 Changed 14 years ago by
Component: | ajax → selector |
---|---|
Milestone: | 1.3.2 → 1.3.3 |
Owner: | set to john |
comment:3 Changed 14 years ago by
Owner: | changed from john to brandon |
---|
comment:4 Changed 14 years ago by
Resolution: | → invalid |
---|---|
Status: | new → closed |
I'm unable to reproduce this. Please validate your HTML. If you are able to create a complete and clean test case, please feel free to reopen this ticket.
Changed 14 years ago by
Attachment: | test2.html added |
---|
comment:5 Changed 14 years ago by
Resolution: | invalid |
---|---|
Status: | closed → reopened |
Found a complete test-case that is failing.
comment:6 Changed 14 years ago by
And realized my own test-case was flawed. I was looking at the td, not the tr.
The tr, although hidden with display: none, still has a width > 0. The height of the hidden tr is 0. The logic for the :visible/:hidden selectors was changed so that if an element has a width or a height, it is considered visible.
The reason it can have a height of 0 and width > 0 and still be visible is because a parent with uncleared floated elements is still visible but will have a height of 0.
comment:7 follow-up: 8 Changed 14 years ago by
I just have a look on the source and I think the following code :
return elem.offsetWidth > 0 || elem.offsetHeight > 0;should be replaced by :
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
- (jQuery/src/selector.js at line 984)
- This works on firefox and ie6 event if the element is a TR
comment:8 Changed 14 years ago by
Replying to benoit@gardentechno.:
I just have a look on the source and I think the following code :
return elem.offsetWidth > 0 || elem.offsetHeight > 0;should be replaced by :
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
Unfortunately an element can be "visible" even if one of its dimensions is 0. For example, a div that has un-cleared floated children will cause the parent's height to be 0 but the width will be > 0. Just because the height is 0 doesn't mean the element is hidden.
comment:9 Changed 14 years ago by
My thinking right now is to use the width/height checks for speed and if the result is ambiguous (one dimension is 0 and the other is > 0) then check the display property.
Changed 14 years ago by
comment:11 Changed 14 years ago by
Resolution: | fixed |
---|---|
Status: | closed → reopened |
I just checked out r6357 and it does not work in IE8 if I turn on compatibility mode it works fine.
comment:12 follow-up: 13 Changed 14 years ago by
Can you expand on "does not work" by providing a test case? Thanks.
comment:13 Changed 14 years ago by
Replying to dmethvin:
Can you expand on "does not work" by providing a test case? Thanks.
I can verify that the patch does not work with the supplied test case in IE8. There is a thread in the dev mailing-list where I've posted another possible solution.
comment:15 Changed 14 years ago by
The other possible solution I posted was this.
Index: src/selector.js =================================================================== --- src/selector.js (revision 6357) +++ src/selector.js (working copy) @@ -977,19 +977,21 @@ jQuery.expr[":"] = jQuery.expr.filters; Sizzle.selectors.filters.hidden = function(elem){ - var width = elem.offsetWidth, height = elem.offsetHeight; - return ( width === 0 && height === 0 ) ? + var width = elem.offsetWidth, height = elem.offsetHeight, + force = /tr/i.test( elem.tagName ); + return ( width === 0 && height === 0 && !force ) ? true : - ( width !== 0 && height !== 0 ) ? + ( width !== 0 && height !== 0 && !force ) ? false : !!( jQuery.curCSS(elem, "display") === "none" ); }; Sizzle.selectors.filters.visible = function(elem){ - var width = elem.offsetWidth, height = elem.offsetHeight; - return ( width === 0 && height === 0 ) ? + var width = elem.offsetWidth, height = elem.offsetHeight, + force = /^tr$/i.test( elem.tagName ); + return ( width === 0 && height === 0 && !force ) ? false : - ( width > 0 && height > 0 ) ? + ( width > 0 && height > 0 && !force ) ? true : !!( jQuery.curCSS(elem, "display") !== "none" ); };
comment:17 Changed 14 years ago by
Resolution: | fixed |
---|---|
Status: | closed → reopened |
The same problem occurs for divs etc too (no only TRs), you can see it here:
using ie8. Temp01 from irc channel suggested me this solution, replace:
force = /tr$/i.test( elem.nodeName ); ticket #4512
with:
force = true;
in nightly build rev. 6586, and now it's working fine.
comment:21 follow-up: 23 Changed 14 years ago by
Resolution: | → fixed |
---|---|
Status: | reopened → closed |
Looks like this fix is still working, as far as I can tell.
comment:23 Changed 14 years ago by
Resolution: | fixed |
---|---|
Status: | closed → reopened |
Replying to john:
Looks like this fix is still working, as far as I can tell.
It is not working in IE8 as can be seen when going to http://jsbin.com/ijini and when loading the file here: http://dev.jquery.com/attachment/ticket/4512/test2.html
comment:24 Changed 14 years ago by
The only way I can get this to work is using the code below
$("#clicktoshoworhide").click(function() { var elem = $('#elementtoshoworhide')[0]; if(elem.style.display == 'none') $('#elementtoshoworhide').show(); else { $('#elementtoshoworhide').hide(); }
comment:25 Changed 14 years ago by
Resolution: | → fixed |
---|---|
Status: | reopened → closed |
janusz, you're using jQuery 1.3.2 for your test and the fix is in the nightlies.
comment:26 Changed 14 years ago by
Resolution: | fixed |
---|---|
Status: | closed → reopened |
This is not fixed. In IE8 offsetWidth
and offsetHeight
are not zero for UL's as well.
I have tested the nightly jQuery file and is(':visible')
does not work correctly (along with .toggle()
) with UL's.
changing the code to:
force = /^(?:tr|ul)$/i.test(...);
has fixed the problem; I still don't like selectively testing certain elements for display:none
. I think that if the elements height and width are 0, it's hidden; if not, it might be hidden but display:none
should still be checked.
comment:27 follow-up: 48 Changed 14 years ago by
Resolution: | → fixed |
---|---|
Status: | reopened → closed |
@cipher. I created a test case but I got the result that I expected:
<script src="dist/jquery.js"></script> <script> $(window).load(function(){ alert( $("ul:first").is(":hidden") ); alert( $("ul:last").is(":hidden") ); }); </script> <ul style="width:0;height:0;"></ul> <ul style="display:none;"></ul>
The first one was false (height/width of zero, but still visible - correct) the second one was true (display: none, not visible).
Changed 13 years ago by
Attachment: | test-all-elements.html added |
---|
Test page for IE8 visibility problem
comment:48 Changed 13 years ago by
Resolution: | fixed |
---|---|
Status: | closed → reopened |
Replying to john:
I have encountered the same problem using jQuery 1.4.2 running on IE8 in standards mode but for the TD and TH tags.
I wrote the following test code to try and detect all the elements that were causing problems. It is by no means complete, but it does show that the problem occurs on more elements than just TR's (specifically it also occurs on BUTTON, TD, TH, CODE, and Q) http://dev.jquery.com/attachment/ticket/4512/test-all-elements.html
I also discovered that the problem only occurs on IE8 running in "standards mode", meaning that pages that do not specify a doctype or include a <meta http-equiv="X-UA-Compatible" content="IE=7" /> do work correctly.
The problem is, as stated earlier, related to the offsetWidth and offsetHeight of the elements in question not being equal to 0 when hidden.
For my own purposes I modified the jQuery file to read: skip = /(tr|td|th)$/.test(elem.nodeName.toLowerCase()); instead of: skip = elem.nodeName.toLowerCase() === "tr";
comment:49 Changed 13 years ago by
Cc: | dmethvin snover added |
---|---|
Milestone: | 1.4 → 1.4.3 |
need: | Review → Patch |
Priority: | major → high |
Version: | 1.3.2 → 1.4.2 |
So, just to clarify, the *principal* (but not only) issue here is that IE gives table elements a width and height whenever there is an additional table cell in the row that is not hidden but which also has a height.
We can work around that. This bug is far more nefarious, however, and I feel like laughing and crying at the same time.
In doing research for this issue, I have discovered that are some inexplicable rules dictating when offsetWidth/offsetHeight are non-zero:
button
insidelabel
(but notinput
)q
insideq
(but notcode
insidecode
,div
insidediv
, orspan
insidespan
)- any table cell as long as there is at least one table cell in the row that is not hidden and is not empty
- any element followed by an inline element as long as the first element in the parent is a text node
That last one is particularly interesting. As far as I can tell, none of these rules are related to hasLayout
, either.
In conclusion, I think we can safely patch the issue with :visible selector and tables, but unless someone can figure out the voodoo that is going on behind the scenes in IE’s warped little brain, someone will eventually be haunted by this shortcut again in another permutation.
comment:50 Changed 13 years ago by
OK, so, modification to the rules, based off this plainly ridiculous test case.
- Innermost nested
q
breaks if any of the parentq
is followed by another node (other elements likespan
orcode
are OK)<br> - Any
td
orth
ortr
as long as there is at least one table cell in the row that is not hidden and not empty<br> button
preceeded by abutton
or non-block node<br>- Some voodoo combinations of inline elements, which also sometimes affect block-level elements but not usually
If someone can figure out that voodoo, I will be super-duper impressed.
FML Trident is a mess! Interestingly, IE9b1 in IE8 and IE9 modes pass, but IE9b1 in IE9 mode required the addition of a check to avoid testing html/body/head/etc..
comment:51 Changed 13 years ago by
OK.
So, the reason that q
appears on the surface to be special is because IE8 generates content. This is not immediately obvious in the above test-case because jsfiddle sets the content to "". That is not the same as setting the content to none
, however, so the generated text nodes are still created. This makes q
appear special but it is apparently not so much.
danheberden and I worked on this for a few hours, and the pattern seems to most closely match this description:
IE8 fails to return offsetWidth/Height properly for hidden elements whenever the element has inline siblings on both sides, or whenever the element is an only child and its parent has an inline next sibling. (Text nodes are apparently considered to be inline elements for the purposes of these decisions within Trident.)
I know what you are thinking—this sounds like a bug that one would expect to see from hasLayout—but forcing layout does not solve the problem, and the buggy elements are a mix of elements with and without layout. So, no luck there.
I think that the most important takeaway from this analysis is not the actual criteria under which IE8 screws up; rather, it is that the behaviour changes depending upon the existence or non-existence of generated content, which means that it appears there is no way of 100% reliably detecting the conditions under which a specific hidden element may not correctly return offsetWidth/Height.
Testing for this condition is very easy, and I have written a patch to detect it. Unfortunately, once we know that offsetWidth/Height is unreliable, it looks like we’ll be forced to walk up the DOM every time someone wants to determine if an element is hidden in IE8. :(
comment:52 Changed 13 years ago by
Owner: | changed from brandon to snover |
---|---|
Status: | reopened → new |
comment:53 Changed 13 years ago by
Status: | new → assigned |
---|
comment:54 Changed 13 years ago by
need: | Patch → Commit |
---|
I would just like to mention that the final statement in my last comment was wrong; offsetWidth/Height are only wrong when the element itself has display: none
set. If an ascendant is display: none
, then offsetWidth/Height are still zero. Yay!
comment:56 Changed 13 years ago by
Resolution: | → fixed |
---|---|
Status: | assigned → closed |
comment:57 Changed 13 years ago by
Component: | selector → css |
---|
Firefox: [ <tr><td>Value 2</td></tr> ]
IE: [ <tr style="display:none"><td>Value 1</td></tr>,<tr><td>Value 2</td></tr> ]