]> source.dussan.org Git - jquery.git/commitdiff
Core: rnotwhite -> rhtmlnotwhite and jQuery.trim -> stripAndCollapse
authorTimmy Willison <4timmywil@gmail.com>
Mon, 12 Sep 2016 16:32:02 +0000 (12:32 -0400)
committerTimmy Willison <4timmywil@gmail.com>
Thu, 15 Sep 2016 14:40:27 +0000 (10:40 -0400)
- Renames and changes rnotwhite to focus on HTML whitespace chars
- Change internal use of jQuery.trim to more accurate strip and collapse
- Adds tests to ensure HTML space characters are retained where valid
- Doesn't add tests where the difference is inconsequential and
  existing tests are adequate.

Fixes gh-3003
Fixes gh-3072
Close gh-3316

15 files changed:
src/ajax.js
src/ajax/load.js
src/attributes/attr.js
src/attributes/classes.js
src/attributes/val.js
src/callbacks.js
src/core/stripAndCollapse.js [new file with mode: 0644]
src/data/Data.js
src/effects.js
src/event.js
src/var/rnothtmlwhite.js [new file with mode: 0644]
src/var/rnotwhite.js [deleted file]
test/data/test3.html
test/unit/ajax.js
test/unit/attributes.js

index f8ec218984644a1bd11562e6e61e17328f0cd655..36f707d7d1dbd7bcf6e243523a45391c1dd3fd82 100644 (file)
@@ -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 ) {
index 1058dbf52009d0d35ea8fa317cea310324c61dd4..3ce3a5aae781c05185f272d62537c792c1006532 100644 (file)
@@ -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 );
        }
 
index 5d85f4f19fec0df6827cb92038eec15de96c7c30..2d9c76feb5e983703fd99453b6690b2fa5191d20 100644 (file)
@@ -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++ ] ) ) {
index 2e8a8caba3568840e71eeb6fee802d091e0dc8b9..23b4cd6af4861ef90533f9aa5646e63e5bbb99d9 100644 (file)
@@ -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;
                        }
                }
 
index e23aa9a21dc24c99b173f7afedb2240b1d289ca0..fbf406929e26169f4107f8b5f9a3fba1fe12bc6b 100644 (file)
@@ -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: {
index 4c4f73327750f3c0ba917743c13cf2044a83da2c..a6d4df03fbbcecf6493e588a6ecb6bc4916396b0 100644 (file)
@@ -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 (file)
index 0000000..797d9f5
--- /dev/null
@@ -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;
+} );
index 0901c6bfe6c38b503fef9c57c6632f38185db93e..43ae01623b4ffb9e3d208c7acf9812a61a42feb2 100644 (file)
@@ -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;
index 287ecfde167a4a214dec856109d4578cbec4082e..68af96c61a96e6fabdc4e256b207483e194c7594 100644 (file)
@@ -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,
index 859f99e08ab8bbcfde1e3db11a02b1ed40d073bc..ab2c63cd1989c494e32767b46b990d8cec5354fb 100644 (file)
@@ -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 (file)
index 0000000..30604db
--- /dev/null
@@ -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 (file)
index 91bdec2..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-define( function() {
-       "use strict";
-
-       return ( /\S+/g );
-} );
index a7f862a8695f34cd8c330a02791612c6bb4e6aea..446d5c1859ed89addbb0711d9ca512736a4cd664 100644 (file)
@@ -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>
index 5a3bd32d28c5d23e8d5413563c0e030f449e8ce2..681aa463bd2460f11a5aa971544232a3bfa407b2 100644 (file)
@@ -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" );
index 771d31cb870040d060cca4c1e44502fc4169a8b3..1284ffd180df9491c478dad7d9c6c8cdc5cf4c7c 100644 (file)
@@ -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='&#xA0;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 );