Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Override Sizzle attribute retrieval with jQuery.attr. Fixes #5637, #7…
…128, #9261, #9570, #10178.

Bug fixed on the side: $(window).is('a') was throwing an exception. Fixes #10178.
  • Loading branch information
timmywil committed Sep 19, 2011
1 parent 269899a commit 92405d4
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 17 deletions.
12 changes: 8 additions & 4 deletions src/attributes.js
Expand Up @@ -7,7 +7,7 @@ var rclass = /[\n\t\r]/g,
rfocusable = /^(?:button|input|object|select|textarea)$/i,
rclickable = /^a(?:rea)?$/i,
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
nodeHook, boolHook;
nodeHook, boolHook, fixSpecified;

jQuery.fn.extend({
attr: function( name, value ) {
Expand Down Expand Up @@ -519,15 +519,19 @@ boolHook = {

// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !jQuery.support.getSetAttribute ) {


fixSpecified = {
name: true,
id: true
};

// Use this for any attribute in IE6/7
// This fixes almost every IE6/7 issue
nodeHook = jQuery.valHooks.button = {
get: function( elem, name ) {
var ret;
ret = elem.getAttributeNode( name );
// Return undefined if nodeValue is empty string
return ret && ret.nodeValue !== "" ?
return ret && (fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified) ?
ret.nodeValue :
undefined;
},
Expand Down
2 changes: 1 addition & 1 deletion src/sizzle
Submodule sizzle updated from 93cc46 to 3375f9
3 changes: 3 additions & 0 deletions src/sizzle-jquery.js
@@ -1,3 +1,6 @@
// Override sizzle attribute retrieval
Sizzle.attr = jQuery.attr;
Sizzle.selectors.attrMap = {};
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;
Expand Down
132 changes: 132 additions & 0 deletions test/data/selector.html
@@ -0,0 +1,132 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>jQuery selector</title>

<script src="../../src/core.js"></script>
<script src="../../src/deferred.js"></script>
<script src="../../src/support.js"></script>
<script src="../../src/data.js"></script>
<script src="../../src/queue.js"></script>
<script src="../../src/attributes.js"></script>
<script src="../../src/event.js"></script>
<script src="../../src/sizzle/sizzle.js"></script>
<script src="../../src/sizzle-jquery.js"></script>
<script src="../../src/traversing.js"></script>
<script src="../../src/manipulation.js"></script>
<script src="../../src/css.js"></script>
<script src="../../src/ajax.js"></script>
<script src="../../src/ajax/jsonp.js"></script>
<script src="../../src/ajax/script.js"></script>
<script src="../../src/ajax/xhr.js"></script>
<script src="../../src/effects.js"></script>
<script src="../../src/offset.js"></script>
<script src="../../src/dimensions.js"></script>

<script id="script1"
defer
async></script>

<script type="text/javascript">
document.createElement('video');
document.createElement('audio');
document.createElement('article');
document.createElement('details');
</script>
</head>
<body>
<img id="img1"
ismap>

<hr id="hr1"
noshade>

<form id="form1"
name="formName"
novalidate
formnovalidate>
<input type="text" id="text1"
tabindex="1"
name="name"
required
autofocus
readonly>
<textarea id="textarea1"
noresize></textarea>
</form>

<table>
<tr><td id="td1"
nowrap></td></tr>
</table>

<iframe id="iframe1"
src="iframe.html"
seamless></iframe>

<style id="style1"
scoped></style>

<ol id="ol1"
reversed></ol>

<article id="article1"
pubdate></article>

<details id="details1"
open></details>

<div id="div1"
nowrap
hidden
itemscope
draggable="true"
contenteditable="true"
aria-disabled="true">
<p>My name is <span id="span1"
spellcheck="true"
itemprop="name">Elizabeth</span>.</p>
</div>

<audio id="audio1"
muted></audio>

<video id="video1"
loop
controls
autoplay
autobuffer></video>

<map id="map1">
<area id="area1"
nohref
shape="default">
</map>

<input id="check1"
type="checkbox"
disabled
checked>

<select id="select1"
multiple>
<option id="option1"
selected
value="blar">blar</option>
</select>

<dl id="dl"
compact>
<dt>Term</dt><dd>This is the first definition in compact format.</dd>
<dt>Term</dt><dd>This is the second definition in compact format.</dd>
</dl>

<object id="object1"
declare></object>

<marquee id="marquee1"
direction="up"
truespeed>Scrolling text (non-standard)</marquee>
</body>
</html>
3 changes: 1 addition & 2 deletions test/data/testinit.js
Expand Up @@ -20,8 +20,7 @@ function q() {

/**
* Asserts that a select matches the given IDs * @example t("Check for something", "//[a]", ["foo", "baar"]);
* @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baa
r'
* @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
*/
function t(a,b,c) {
var f = jQuery(b).get(), s = "";
Expand Down
3 changes: 2 additions & 1 deletion test/index.html
Expand Up @@ -41,6 +41,7 @@
<script src="unit/attributes.js"></script>
<script src="unit/event.js"></script>
<script src="../src/sizzle/test/unit/selector.js"></script>
<script src="unit/selector.js"></script>
<script src="unit/traversing.js"></script>
<script src="unit/manipulation.js"></script>
<script src="unit/css.js"></script>
Expand Down Expand Up @@ -136,7 +137,7 @@ <h2 id="qunit-userAgent"></h2>
<select name="select5" id="select5">
<option id="option5a" value="3">1</option>
<option id="option5b" value="2">2</option>
<option id="option5c" value="1">3</option>
<option id="option5c" value="1" data-attr="">3</option>
</select>

<object id="object1" codebase="stupid">
Expand Down
15 changes: 8 additions & 7 deletions test/unit/attributes.js
Expand Up @@ -55,10 +55,10 @@ test("attr(String)", function() {

// [7472] & [3113] (form contains an input with name="action" or name="id")
var extras = jQuery("<input name='id' name='name' /><input id='target' name='target' />").appendTo("#testForm");
equals( jQuery("#form").attr("action","newformaction").attr("action"), "newformaction", "Check that action attribute was changed" );
equals( jQuery("#testForm").attr("target"), undefined, "Retrieving target does not equal the input with name=target" );
equals( jQuery("#testForm").attr("target", "newTarget").attr("target"), "newTarget", "Set target successfully on a form" );
equals( jQuery("#testForm").removeAttr("id").attr("id"), undefined, "Retrieving id does not equal the input with name=id after id is removed [#7472]" );
equal( jQuery("#form").attr("action","newformaction").attr("action"), "newformaction", "Check that action attribute was changed" );
equal( jQuery("#testForm").attr("target"), undefined, "Retrieving target does not equal the input with name=target" );
equal( jQuery("#testForm").attr("target", "newTarget").attr("target"), "newTarget", "Set target successfully on a form" );
equal( jQuery("#testForm").removeAttr("id").attr("id"), undefined, "Retrieving id does not equal the input with name=id after id is removed [#7472]" );
// Bug #3685 (form contains input with name="name")
equals( jQuery("#testForm").attr("name"), undefined, "Retrieving name does not retrieve input with name=name" );
extras.remove();
Expand Down Expand Up @@ -157,7 +157,7 @@ test("attr(Hash)", function() {
});

test("attr(String, Object)", function() {
expect(75);
expect(76);

var div = jQuery("div").attr("foo", "bar"),
fail = false;
Expand All @@ -177,8 +177,9 @@ test("attr(String, Object)", function() {
equals( jQuery("#name").attr("name"), "something", "Set name attribute" );
jQuery("#name").attr("name", null);
equals( jQuery("#name").attr("name"), undefined, "Remove name attribute" );
var $input = jQuery("<input>", { name: "something" });
equals( $input.attr("name"), "something", "Check element creation gets/sets the name attribute." );
var $input = jQuery("<input>", { name: "something", id: "specified" });
equal( $input.attr("name"), "something", "Check element creation gets/sets the name attribute." );
equal( $input.attr("id"), "specified", "Check element creation gets/sets the id attribute." );

jQuery("#check2").prop("checked", true).prop("checked", false).attr("checked", true);
equals( document.getElementById("check2").checked, true, "Set checked attribute" );
Expand Down
133 changes: 133 additions & 0 deletions test/unit/selector.js
@@ -0,0 +1,133 @@
/**
* This test page is for selector tests that address selector issues that have already been addressed in jQuery functions
* and which only work because jQuery has hooked into Sizzle.
* These tests may or may not fail in an independent Sizzle.
*/

module("selector - jQuery only", { teardown: moduleTeardown });

/**
* Loads an iframe for the selector context
* @param {String} fileName - Name of the html file to load
* @param {String} name - Test name
* @param {Function} fn - Test callback containing the tests to run
*/
var testIframe = function( fileName, name, fn ) {

var loadFixture = function() {

// Creates iframe with cache disabled
var src = "./data/" + fileName + ".html?" + parseInt( Math.random()*1000, 10 ),
iframe = jQuery("<iframe />").css({
width: 500, height: 500, position: "absolute", top: -600, left: -600, visibility: "hidden"
}).appendTo("body")[0];
iframe.contentWindow.location = src;
return iframe;
};

test(name, function() {
// pause execution for now
stop();

// load fixture in iframe
var iframe = loadFixture(),
win = iframe.contentWindow,
interval = setInterval( function() {
if ( win && win.jQuery && win.jQuery.isReady ) {
clearInterval( interval );
// continue
start();
// call actual tests passing the correct jQuery instance to use
fn.call( this, win.jQuery, win );
document.body.removeChild( iframe );
iframe = null;
}
}, 15 );
});
};

testIframe("selector", "attributes - jQuery.attr", function( jQuery, window ) {
expect(34);

var document = window.document;

/**
* Returns an array of elements with the given IDs, eg.
*/
var q = function() {
var r = [];

for ( var i = 0; i < arguments.length; i++ ) {
r.push( document.getElementById( arguments[i] ) );
}

return r;
};

/**
* Asserts that a select matches the given IDs * @example t("Check for something", "//[a]", ["foo", "baar"]);
* @param {String} a - Assertion name
* @param {String} b - Sizzle selector
* @param {String} c - Array of ids to construct what is expected
*/
var t = function( a, b, c ) {
var f = jQuery(b).get(), s = "";

for ( var i = 0; i < f.length; i++ ) {
s += (s && ",") + '"' + f[i].id + '"';
}

deepEqual(f, q.apply( q, c ), a + " (" + b + ")");
};

// ====== All known boolean attributes, including html5 booleans ======
// autobuffer, autofocus, autoplay, async, checked,
// compact, controls, declare, defer, disabled,
// formnovalidate, hidden, indeterminate (property only),
// ismap, itemscope, loop, multiple, muted, nohref, noresize,
// noshade, nowrap, novalidate, open, pubdate, readonly, required,
// reversed, scoped, seamless, selected, truespeed, visible (skipping visible attribute, which is on a barprop object)

t( "Attribute Exists", "[autobuffer]", ["video1"]);
t( "Attribute Exists", "[autofocus]", ["text1"]);
t( "Attribute Exists", "[autoplay]", ["video1"]);
t( "Attribute Exists", "[async]", ["script1"]);
t( "Attribute Exists", "[checked]", ["check1"]);
t( "Attribute Exists", "[compact]", ["dl"]);
t( "Attribute Exists", "[controls]", ["video1"]);
t( "Attribute Exists", "[declare]", ["object1"]);
t( "Attribute Exists", "[defer]", ["script1"]);
t( "Attribute Exists", "[disabled]", ["check1"]);
t( "Attribute Exists", "[formnovalidate]", ["form1"]);
t( "Attribute Exists", "[hidden]", ["div1"]);
t( "Attribute Exists", "[indeterminate]", []);
t( "Attribute Exists", "[ismap]", ["img1"]);
t( "Attribute Exists", "[itemscope]", ["div1"]);
// t( "Attribute Exists", "[loop]", ["video1"]); // IE 6/7 cannot differentiate here. loop is also used on img, input, and marquee tags as well as video/audio. getAttributeNode unfortunately only retrieves the property value.
t( "Attribute Exists", "[multiple]", ["select1"]);
t( "Attribute Exists", "[muted]", ["audio1"]);
// t( "Attribute Exists", "[nohref]", ["area1"]); // IE 6/7 keep this set to false regardless of presence. The attribute node is not retrievable.
t( "Attribute Exists", "[noresize]", ["textarea1"]);
t( "Attribute Exists", "[noshade]", ["hr1"]);
t( "Attribute Exists", "[nowrap]", ["td1", "div1"]);
t( "Attribute Exists", "[novalidate]", ["form1"]);
t( "Attribute Exists", "[open]", ["details1"]);
t( "Attribute Exists", "[pubdate]", ["article1"]);
t( "Attribute Exists", "[readonly]", ["text1"]);
t( "Attribute Exists", "[required]", ["text1"]);
t( "Attribute Exists", "[reversed]", ["ol1"]);
t( "Attribute Exists", "[scoped]", ["style1"]);
t( "Attribute Exists", "[seamless]", ["iframe1"]);
// t( "Attribute Exists", "[selected]", ["option1"]); // IE8's querySelectorAll fails here. Redirecting to oldSizzle would work, but it would require an additional support test as well as a check for the selected attribute within the qsa logic
t( "Attribute Exists", "[truespeed]", ["marquee1"]);

// Enumerated attributes (these are not boolean content attributes)
jQuery.each([ "draggable", "contenteditable", "aria-disabled" ], function( i, val ) {
t( "Enumerated attribute", "[" + val + "]", ["div1"]);
});
t( "Enumerated attribute", "[spellcheck]", ["span1"]);

// t( "tabindex selector does not retrieve all elements in IE6/7(#8473)", "form, [tabindex]", ["form1", "text1"]); // Uncomment this when the tabindex attrHook is deprecated

t( "Improperly named form elements do not interfere with form selections (#9570)", "form[name='formName']", ["form1"]);
});

0 comments on commit 92405d4

Please sign in to comment.