diff options
-rw-r--r-- | src/ajax.js | 8 | ||||
-rw-r--r-- | src/ajax/load.js | 5 | ||||
-rw-r--r-- | src/attributes/attr.js | 9 | ||||
-rw-r--r-- | src/attributes/classes.js | 29 | ||||
-rw-r--r-- | src/attributes/val.js | 8 | ||||
-rw-r--r-- | src/callbacks.js | 6 | ||||
-rw-r--r-- | src/core/stripAndCollapse.js | 12 | ||||
-rw-r--r-- | src/data/Data.js | 6 | ||||
-rw-r--r-- | src/effects.js | 6 | ||||
-rw-r--r-- | src/event.js | 8 | ||||
-rw-r--r-- | src/var/rnothtmlwhite.js | 8 | ||||
-rw-r--r-- | src/var/rnotwhite.js | 5 | ||||
-rw-r--r-- | test/data/test3.html | 1 | ||||
-rw-r--r-- | test/unit/ajax.js | 11 | ||||
-rw-r--r-- | test/unit/attributes.js | 50 |
15 files changed, 121 insertions, 51 deletions
diff --git a/src/ajax.js b/src/ajax.js index f8ec21898..36f707d7d 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,7 +1,7 @@ define( [ "./core", "./var/document", - "./var/rnotwhite", + "./var/rnothtmlwhite", "./ajax/var/location", "./ajax/var/nonce", "./ajax/var/rquery", @@ -11,7 +11,7 @@ define( [ "./event/trigger", "./deferred", "./serialize" // jQuery.param -], function( jQuery, document, rnotwhite, location, nonce, rquery ) { +], function( jQuery, document, rnothtmlwhite, location, nonce, rquery ) { "use strict"; @@ -64,7 +64,7 @@ function addToPrefiltersOrTransports( structure ) { var dataType, i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || []; + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; if ( jQuery.isFunction( func ) ) { @@ -532,7 +532,7 @@ jQuery.extend( { s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ]; + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; // A cross-domain request is in order when the origin doesn't match the current origin. if ( s.crossDomain == null ) { diff --git a/src/ajax/load.js b/src/ajax/load.js index 1058dbf52..3ce3a5aae 100644 --- a/src/ajax/load.js +++ b/src/ajax/load.js @@ -1,11 +1,12 @@ define( [ "../core", + "../core/stripAndCollapse", "../core/parseHTML", "../ajax", "../traversing", "../manipulation", "../selector" -], function( jQuery ) { +], function( jQuery, stripAndCollapse ) { "use strict"; @@ -18,7 +19,7 @@ jQuery.fn.load = function( url, params, callback ) { off = url.indexOf( " " ); if ( off > -1 ) { - selector = jQuery.trim( url.slice( off ) ); + selector = stripAndCollapse( url.slice( off ) ); url = url.slice( 0, off ); } diff --git a/src/attributes/attr.js b/src/attributes/attr.js index 5d85f4f19..2d9c76feb 100644 --- a/src/attributes/attr.js +++ b/src/attributes/attr.js @@ -2,9 +2,9 @@ define( [ "../core", "../core/access", "./support", - "../var/rnotwhite", + "../var/rnothtmlwhite", "../selector" -], function( jQuery, access, support, rnotwhite ) { +], function( jQuery, access, support, rnothtmlwhite ) { "use strict"; @@ -89,7 +89,10 @@ jQuery.extend( { removeAttr: function( elem, value ) { var name, i = 0, - attrNames = value && value.match( rnotwhite ); + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( ( name = attrNames[ i++ ] ) ) { diff --git a/src/attributes/classes.js b/src/attributes/classes.js index 2e8a8caba..23b4cd6af 100644 --- a/src/attributes/classes.js +++ b/src/attributes/classes.js @@ -1,14 +1,13 @@ define( [ "../core", - "../var/rnotwhite", + "../core/stripAndCollapse", + "../var/rnothtmlwhite", "../data/var/dataPriv", "../core/init" -], function( jQuery, rnotwhite, dataPriv ) { +], function( jQuery, stripAndCollapse, rnothtmlwhite, dataPriv ) { "use strict"; -var rclass = /[\t\r\n\f]/g; - function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } @@ -25,12 +24,11 @@ jQuery.fn.extend( { } if ( typeof value === "string" && value ) { - classes = value.match( rnotwhite ) || []; + classes = value.match( rnothtmlwhite ) || []; while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); - cur = elem.nodeType === 1 && - ( " " + curValue + " " ).replace( rclass, " " ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; @@ -41,7 +39,7 @@ jQuery.fn.extend( { } // Only assign if different to avoid unneeded rendering. - finalValue = jQuery.trim( cur ); + finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } @@ -67,14 +65,13 @@ jQuery.fn.extend( { } if ( typeof value === "string" && value ) { - classes = value.match( rnotwhite ) || []; + classes = value.match( rnothtmlwhite ) || []; while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && - ( " " + curValue + " " ).replace( rclass, " " ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; @@ -87,7 +84,7 @@ jQuery.fn.extend( { } // Only assign if different to avoid unneeded rendering. - finalValue = jQuery.trim( cur ); + finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } @@ -122,7 +119,7 @@ jQuery.fn.extend( { // Toggle individual class names i = 0; self = jQuery( this ); - classNames = value.match( rnotwhite ) || []; + classNames = value.match( rnothtmlwhite ) || []; while ( ( className = classNames[ i++ ] ) ) { @@ -165,10 +162,8 @@ jQuery.fn.extend( { className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && - ( " " + getClass( elem ) + " " ).replace( rclass, " " ) - .indexOf( className ) > -1 - ) { - return true; + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; } } diff --git a/src/attributes/val.js b/src/attributes/val.js index e23aa9a21..fbf406929 100644 --- a/src/attributes/val.js +++ b/src/attributes/val.js @@ -1,13 +1,13 @@ define( [ "../core", + "../core/stripAndCollapse", "./support", "../core/init" -], function( jQuery, support ) { +], function( jQuery, stripAndCollapse, support ) { "use strict"; -var rreturn = /\r/g, - rspaces = /[\x20\t\r\n\f]+/g; +var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { @@ -91,7 +91,7 @@ jQuery.extend( { // option.text throws exceptions (#14686, #14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " ); + stripAndCollapse( jQuery.text( elem ) ); } }, select: { diff --git a/src/callbacks.js b/src/callbacks.js index 4c4f73327..a6d4df03f 100644 --- a/src/callbacks.js +++ b/src/callbacks.js @@ -1,14 +1,14 @@ define( [ "./core", - "./var/rnotwhite" -], function( jQuery, rnotwhite ) { + "./var/rnothtmlwhite" +], function( jQuery, rnothtmlwhite ) { "use strict"; // Convert String-formatted options into Object-formatted ones function createOptions( options ) { var object = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; diff --git a/src/core/stripAndCollapse.js b/src/core/stripAndCollapse.js new file mode 100644 index 000000000..797d9f531 --- /dev/null +++ b/src/core/stripAndCollapse.js @@ -0,0 +1,12 @@ +define( function() { + "use strict"; + + // Strip and collapse whitespace according to HTML spec + // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + var rhtmlSpace = /[\x20\t\r\n\f]+/g, + stripAndCollapse = function( value ) { + return ( " " + value + " " ).replace( rhtmlSpace, " " ).slice( 1, -1 ); + }; + + return stripAndCollapse; +} ); diff --git a/src/data/Data.js b/src/data/Data.js index 0901c6bfe..43ae01623 100644 --- a/src/data/Data.js +++ b/src/data/Data.js @@ -1,8 +1,8 @@ define( [ "../core", - "../var/rnotwhite", + "../var/rnothtmlwhite", "./var/acceptData" -], function( jQuery, rnotwhite, acceptData ) { +], function( jQuery, rnothtmlwhite, acceptData ) { "use strict"; @@ -127,7 +127,7 @@ Data.prototype = { // Otherwise, create an array by matching non-whitespace key = key in cache ? [ key ] : - ( key.match( rnotwhite ) || [] ); + ( key.match( rnothtmlwhite ) || [] ); } i = key.length; diff --git a/src/effects.js b/src/effects.js index 287ecfde1..68af96c61 100644 --- a/src/effects.js +++ b/src/effects.js @@ -2,7 +2,7 @@ define( [ "./core", "./var/document", "./var/rcssNum", - "./var/rnotwhite", + "./var/rnothtmlwhite", "./css/var/cssExpand", "./css/var/isHiddenWithinTree", "./css/var/swap", @@ -17,7 +17,7 @@ define( [ "./manipulation", "./css", "./effects/Tween" -], function( jQuery, document, rcssNum, rnotwhite, cssExpand, isHiddenWithinTree, swap, +], function( jQuery, document, rcssNum, rnothtmlwhite, cssExpand, isHiddenWithinTree, swap, adjustCSS, dataPriv, showHide ) { "use strict"; @@ -415,7 +415,7 @@ jQuery.Animation = jQuery.extend( Animation, { callback = props; props = [ "*" ]; } else { - props = props.match( rnotwhite ); + props = props.match( rnothtmlwhite ); } var prop, diff --git a/src/event.js b/src/event.js index 859f99e08..ab2c63cd1 100644 --- a/src/event.js +++ b/src/event.js @@ -2,13 +2,13 @@ define( [ "./core", "./var/document", "./var/documentElement", - "./var/rnotwhite", + "./var/rnothtmlwhite", "./var/slice", "./data/var/dataPriv", "./core/init", "./selector" -], function( jQuery, document, documentElement, rnotwhite, slice, dataPriv ) { +], function( jQuery, document, documentElement, rnothtmlwhite, slice, dataPriv ) { "use strict"; @@ -147,7 +147,7 @@ jQuery.event = { } // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; @@ -229,7 +229,7 @@ jQuery.event = { } // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; diff --git a/src/var/rnothtmlwhite.js b/src/var/rnothtmlwhite.js new file mode 100644 index 000000000..30604db4f --- /dev/null +++ b/src/var/rnothtmlwhite.js @@ -0,0 +1,8 @@ +define( function() { + "use strict"; + + // Only count HTML whitespace + // Other whitespace should count in values + // https://html.spec.whatwg.org/multipage/infrastructure.html#space-character + return ( /[^\x20\t\r\n\f]+/g ); +} ); diff --git a/src/var/rnotwhite.js b/src/var/rnotwhite.js deleted file mode 100644 index 91bdec22f..000000000 --- a/src/var/rnotwhite.js +++ /dev/null @@ -1,5 +0,0 @@ -define( function() { - "use strict"; - - return ( /\S+/g ); -} ); diff --git a/test/data/test3.html b/test/data/test3.html index a7f862a86..446d5c185 100644 --- a/test/data/test3.html +++ b/test/data/test3.html @@ -2,3 +2,4 @@ <div class="user">This is a user</div> <div class="teacher">This is a teacher</div> <div id="superuser">This is a superuser</div> +<div id="whitespace\xA0">This is a superuser with non-HTML whitespace</div> diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 5a3bd32d2..681aa463b 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -2327,6 +2327,17 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ); } ); + // Selector should be trimmed to avoid leading spaces (#14773) + // Selector should include any valid non-HTML whitespace (#3003) + QUnit.test( "jQuery.fn.load( URL_SELECTOR with non-HTML whitespace(#3003) )", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery( "#first" ).load( "data/test3.html #whitespace\\\\xA0 ", function() { + assert.strictEqual( jQuery( this ).children( "div" ).length, 1, "Verify that specific elements were injected" ); + done(); + } ); + } ); + QUnit.asyncTest( "jQuery.fn.load( String, Function ) - simple: inject text into DOM", 2, function( assert ) { jQuery( "#first" ).load( url( "data/name.html" ), function() { assert.ok( /^ERROR/.test( jQuery( "#first" ).text() ), "Check if content was injected into the DOM" ); diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 771d31cb8..1284ffd18 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -657,6 +657,28 @@ QUnit.test( "removeAttr(Multi String, variable space width)", function( assert ) } ); } ); +QUnit.test( "removeAttr(Multi String, non-HTML whitespace is valid in attribute names (gh-3003)", function( assert ) { + assert.expect( 8 ); + + var div = jQuery( "<div id='a' data-\xA0='b' title='c' rel='d'></div>" ); + var tests = { + id: "a", + "data-\xA0": "b", + title: "c", + rel: "d" + }; + + jQuery.each( tests, function( key, val ) { + assert.equal( div.attr( key ), val, "Attribute \"" + key + "\" exists, and has a value of \"" + val + "\"" ); + } ); + + div.removeAttr( "id data-\xA0 title rel " ); + + jQuery.each( tests, function( key ) { + assert.equal( div.attr( key ), undefined, "Attribute \"" + key + "\" was removed" ); + } ); +} ); + QUnit.test( "prop(String, Object)", function( assert ) { assert.expect( 17 ); @@ -1117,7 +1139,7 @@ QUnit.test( "val(select) after form.reset() (Bug #2551)", function( assert ) { } ); QUnit.test( "select.val(space characters) (gh-2978)", function( assert ) { - assert.expect( 35 ); + assert.expect( 37 ); var $select = jQuery( "<select/>" ).appendTo( "#qunit-fixture" ), spaces = { @@ -1168,13 +1190,17 @@ QUnit.test( "select.val(space characters) (gh-2978)", function( assert ) { html += "<option>" + value + "text</option>"; $select.html( html ); - $select.val( "text" ); - assert.equal( $select.val(), "text", "Value with space character at beginning or end is stripped (" + key + ") selected (text)" ); if ( /^\\u/.test( key ) ) { + $select.val( val + "text" ); + assert.equal( $select.val(), val + "text", "Value with non-HTML space character at beginning is not stripped (" + key + ") selected (" + key + "text)" ); $select.val( "te" + val + "xt" ); assert.equal( $select.val(), "te" + val + "xt", "Value with non-space whitespace character (" + key + ") in the middle selected (text)" ); + $select.val( "text" + val ); + assert.equal( $select.val(), "text" + val, "Value with non-HTML space character at end is not stripped (" + key + ") selected (text" + key + ")" ); } else { + $select.val( "text" ); + assert.equal( $select.val(), "text", "Value with HTML space character at beginning or end is stripped (" + key + ") selected (text)" ); $select.val( "te xt" ); assert.equal( $select.val(), "te xt", "Value with space character (" + key + ") in the middle selected (text)" ); } @@ -1541,6 +1567,24 @@ QUnit.test( "addClass, removeClass, hasClass on many elements", function( assert "Did not find a class when not present" ); } ); +QUnit.test( "addClass, removeClass, hasClass on elements with classes with non-HTML whitespace (gh-3072, gh-3003)", function( assert ) { + assert.expect( 9 ); + + var $elem = jQuery( "<div class=' test'></div>" ); + + function testMatches() { + assert.ok( $elem.is( ".\\A0 test" ), "Element matches with collapsed space" ); + assert.ok( $elem.is( ".\\A0test" ), "Element matches with non-breaking space" ); + assert.ok( $elem.hasClass( "\xA0test" ), "Element has class with non-breaking space" ); + } + + testMatches(); + $elem.addClass( "foo" ); + testMatches(); + $elem.removeClass( "foo" ); + testMatches(); +} ); + QUnit.test( "contents().hasClass() returns correct values", function( assert ) { assert.expect( 2 ); |