Skip to content

Commit fe13fba

Browse files
committed
Core: Better support for areas in :focusable and :tabbable selectors. Partial fix for #4488 - :focusable and :tabbable are broken with jQuery 1.3.2.
1 parent 4deb824 commit fe13fba

File tree

3 files changed

+53
-25
lines changed

3 files changed

+53
-25
lines changed

tests/unit/core/core.html

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ <h2 id="qunit-userAgent"></h2>
4242

4343
<div id="main" style="position: absolute; top: -10000px; left: -10000px;">
4444

45+
<img src="images/jqueryui_32x32.png" usemap="#mymap" width="10", height="10">
46+
<map name="mymap">
47+
<area shape="rect" coords="0,0,1,1" id="areaCoordsNoHref">
48+
<area shape="rect" coords="1,1,2,2" href="foo.html" id="areaCoordsHref">
49+
<area shape="rect" coords="0,0,0,0" href="foo.html" id="areaCoordsNoSizeHref">
50+
<area href="foo.html" id="areaNoCoordsHref">
51+
</map>
52+
<map name="mymap2">
53+
<area shape="rect" coords="1,1,2,2" href="foo.html" id="areaNoImg">
54+
</map>
55+
4556
<div>
4657
<input id="visibleAncestor-inputTypeNone" />
4758
<input type="text" id="visibleAncestor-inputTypeText" />
@@ -57,10 +68,6 @@ <h2 id="qunit-userAgent"></h2>
5768
<object id="visibleAncestor-object">xxx</object>
5869
<a href="#" id="visibleAncestor-anchorWithHref">anchor</a>
5970
<a id="visibleAncestor-anchorWithoutHref">anchor</a>
60-
<map>
61-
<area href="#" id="visibleAncestor-areaWithHref" alt="" />
62-
<area id="visibleAncestor-areaWithoutHref" alt="" />
63-
</map>
6471
<span id="visibleAncestor-span">x</span>
6572
<div id="visibleAncestor-div">x</div>
6673
<span id="visibleAncestor-spanWithTabindex" tabindex="1">x</span>

tests/unit/core/selector.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ test("data", function() {
7979
});
8080

8181
test("focusable - visible, enabled elements", function() {
82-
expect(18);
82+
expect(16);
8383

8484
isFocusable('#visibleAncestor-inputTypeNone', 'input, no type');
8585
isFocusable('#visibleAncestor-inputTypeText', 'input, type text');
@@ -93,9 +93,6 @@ test("focusable - visible, enabled elements", function() {
9393
isFocusable('#visibleAncestor-object', 'object');
9494
isFocusable('#visibleAncestor-anchorWithHref', 'anchor with href');
9595
isNotFocusable('#visibleAncestor-anchorWithoutHref', 'anchor without href');
96-
// fails: $("map").is(":visible") and $("map").is(":hidden") both return true
97-
isFocusable('#visibleAncestor-areaWithHref', 'area with href');
98-
isNotFocusable('#visibleAncestor-areaWithoutHref', 'area without href');
9996
isNotFocusable('#visibleAncestor-span', 'span');
10097
isNotFocusable('#visibleAncestor-div', 'div');
10198
isFocusable("#visibleAncestor-spanWithTabindex", 'span with tabindex');
@@ -159,8 +156,16 @@ test("focusable - invalid tabindex", function() {
159156
isNotFocusable('#spanTabindex3foo', 'span, tabindex 3foo');
160157
});
161158

159+
test("focusable - area elements", function() {
160+
isNotFocusable('#areaCoordsNoHref', 'coords but no href');
161+
isFocusable('#areaCoordsHref', 'coords and href');
162+
isFocusable('#areaCoordsNoSizeHref', 'coords of zero px and href');
163+
isFocusable('#areaNoCoordsHref', 'href but no coords');
164+
isNotFocusable('#areaNoImg', 'not associated with an image');
165+
});
166+
162167
test("tabbable - visible, enabled elements", function() {
163-
expect(18);
168+
expect(16);
164169

165170
isTabbable('#visibleAncestor-inputTypeNone', 'input, no type');
166171
isTabbable('#visibleAncestor-inputTypeText', 'input, type text');
@@ -174,16 +179,13 @@ test("tabbable - visible, enabled elements", function() {
174179
isTabbable('#visibleAncestor-object', 'object');
175180
isTabbable('#visibleAncestor-anchorWithHref', 'anchor with href');
176181
isNotTabbable('#visibleAncestor-anchorWithoutHref', 'anchor without href');
177-
// fails: $("map").is(":visible") and $("map").is(":hidden") both return true
178-
isTabbable('#visibleAncestor-areaWithHref', 'area with href');
179-
isNotTabbable('#visibleAncestor-areaWithoutHref', 'area without href');
180182
isNotTabbable('#visibleAncestor-span', 'span');
181183
isNotTabbable('#visibleAncestor-div', 'div');
182184
isTabbable("#visibleAncestor-spanWithTabindex", 'span with tabindex');
183185
isNotTabbable("#visibleAncestor-divWithNegativeTabindex", 'div with tabindex');
184186
});
185187

186-
test("Tabbable - disabled elements", function() {
188+
test("tabbable - disabled elements", function() {
187189
expect(9);
188190

189191
isNotTabbable('#disabledElement-inputTypeNone', 'input, no type');
@@ -197,25 +199,23 @@ test("Tabbable - disabled elements", function() {
197199
isNotTabbable('#disabledElement-textarea', 'textarea');
198200
});
199201

200-
test("Tabbable - hidden styles", function() {
202+
test("tabbable - hidden styles", function() {
201203
expect(8);
202204

203205
isNotTabbable('#displayNoneAncestor-input', 'input, display: none parent');
204206
isNotTabbable('#displayNoneAncestor-span', 'span with tabindex, display: none parent');
205207

206-
// fails: element hidden by parent-visibility-hidden is still visible according to :visible
207208
isNotTabbable('#visibilityHiddenAncestor-input', 'input, visibility: hidden parent');
208209
isNotTabbable('#visibilityHiddenAncestor-span', 'span with tabindex, visibility: hidden parent');
209210

210211
isNotTabbable('#displayNone-input', 'input, display: none');
211-
// fails: element hidden by parent-visibility-hidden is still visible according to :visible
212212
isNotTabbable('#visibilityHidden-input', 'input, visibility: hidden');
213213

214214
isNotTabbable('#displayNone-span', 'span with tabindex, display: none');
215215
isNotTabbable('#visibilityHidden-span', 'span with tabindex, visibility: hidden');
216216
});
217217

218-
test("Tabbable - natively tabbable with various tabindex", function() {
218+
test("tabbable - natively tabbable with various tabindex", function() {
219219
expect(4);
220220

221221
isTabbable('#inputTabindex0', 'input, tabindex 0');
@@ -224,7 +224,7 @@ test("Tabbable - natively tabbable with various tabindex", function() {
224224
isNotTabbable('#inputTabindex-50', 'input, tabindex -50');
225225
});
226226

227-
test("Tabbable - not natively tabbable with various tabindex", function() {
227+
test("tabbable - not natively tabbable with various tabindex", function() {
228228
expect(4);
229229

230230
isTabbable('#spanTabindex0', 'span, tabindex 0');
@@ -233,7 +233,7 @@ test("Tabbable - not natively tabbable with various tabindex", function() {
233233
isNotTabbable('#spanTabindex-50', 'span, tabindex -50');
234234
});
235235

236-
test("Tabbable - invalid tabindex", function() {
236+
test("tabbable - invalid tabindex", function() {
237237
expect(4);
238238

239239
isTabbable('#inputTabindexfoo', 'input, tabindex foo');
@@ -242,4 +242,12 @@ test("Tabbable - invalid tabindex", function() {
242242
isNotTabbable('#spanTabindex3foo', 'span, tabindex 3foo');
243243
});
244244

245+
test("tabbable - area elements", function() {
246+
isNotTabbable('#areaCoordsNoHref', 'coords but no href');
247+
isTabbable('#areaCoordsHref', 'coords and href');
248+
isTabbable('#areaCoordsNoSizeHref', 'coords of zero px and href');
249+
isTabbable('#areaNoCoordsHref', 'href but no coords');
250+
isNotTabbable('#areaNoImg', 'not associated with an image');
251+
});
252+
245253
})(jQuery);

ui/jquery.ui.core.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ $.each( [ "Width", "Height" ], function( i, name ) {
231231
});
232232

233233
//Additional selectors
234+
function visible( element ) {
235+
return !$(element).parents().andSelf().filter(function() {
236+
return $.curCSS( this, "visibility" ) === "hidden" ||
237+
$.expr.filters.hidden( this );
238+
}).length;
239+
}
240+
234241
$.extend($.expr[':'], {
235242
data: function(elem, i, match) {
236243
return !!$.data(elem, match[3]);
@@ -239,17 +246,23 @@ $.extend($.expr[':'], {
239246
focusable: function(element) {
240247
var nodeName = element.nodeName.toLowerCase(),
241248
tabIndex = $.attr(element, 'tabindex');
249+
if ( "area" === nodeName ) {
250+
var map = element.parentNode,
251+
mapName = map.name,
252+
img;
253+
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
254+
return false;
255+
}
256+
img = $( "img[usemap=#" + mapName + "]" )[0];
257+
return !!img && visible( img );
258+
}
242259
return (/input|select|textarea|button|object/.test(nodeName)
243260
? !element.disabled
244-
: 'a' == nodeName || 'area' == nodeName
261+
: 'a' == nodeName
245262
? element.href || !isNaN(tabIndex)
246263
: !isNaN(tabIndex))
247264
// the element and all of its ancestors must be visible
248-
// the browser may report that the area is hidden
249-
&& !$(element).parents().andSelf().filter(function() {
250-
return $.curCSS( this, "visibility" ) === "hidden" ||
251-
$.expr.filters.hidden( this );
252-
}).length;
265+
&& visible( element );
253266
},
254267

255268
tabbable: function(element) {

0 commit comments

Comments
 (0)