From 3bbcce68d7b8b8a7a2164a0f7a280ae9daf70b5c Mon Sep 17 00:00:00 2001 From: Timmy Willison <4timmywil@gmail.com> Date: Mon, 12 Sep 2016 12:32:02 -0400 Subject: [PATCH] Core: rnotwhite -> rhtmlnotwhite and jQuery.trim -> stripAndCollapse - 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 --- src/ajax.js | 8 +++--- src/ajax/load.js | 5 ++-- src/attributes/attr.js | 9 ++++--- src/attributes/classes.js | 29 +++++++++------------ src/attributes/val.js | 8 +++--- src/callbacks.js | 6 ++--- src/core/stripAndCollapse.js | 12 +++++++++ src/data/Data.js | 6 ++--- src/effects.js | 6 ++--- src/event.js | 8 +++--- src/var/rnothtmlwhite.js | 8 ++++++ src/var/rnotwhite.js | 5 ---- test/data/test3.html | 1 + test/unit/ajax.js | 11 ++++++++ test/unit/attributes.js | 50 +++++++++++++++++++++++++++++++++--- 15 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 src/core/stripAndCollapse.js create mode 100644 src/var/rnothtmlwhite.js delete mode 100644 src/var/rnotwhite.js 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 @@
This is a user
This is a teacher
This is a superuser
+
This is a superuser with non-HTML whitespace
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( "
" ); + 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( "