From 08e134548f70f24cb73f8e1b7a6da39b1995d0ac Mon Sep 17 00:00:00 2001 From: carldanley Date: Tue, 16 Oct 2012 11:15:41 -0400 Subject: [PATCH] Create private methods for processing data/removeData requests. Fixes #12519, Closes gh-976 --- src/data.js | 316 ++++++++++++++++++++++++---------------------- src/effects.js | 2 +- src/event.js | 3 +- src/queue.js | 4 +- test/unit/data.js | 18 +-- 5 files changed, 177 insertions(+), 166 deletions(-) diff --git a/src/data.js b/src/data.js index 7d9bef3e1..1545968fe 100644 --- a/src/data.js +++ b/src/data.js @@ -1,209 +1,221 @@ var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } -jQuery.extend({ - cache: {}, + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", - deletedIds: [], + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, - // Remove at next major release (1.9/2.0) - uuid: 0, + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; } + } - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + if ( !cache[ id ] ) { + cache[ id ] = {}; - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; } + } - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } + } - if ( !cache[ id ] ) { - cache[ id ] = {}; + thisCache = cache[ id ]; - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; } - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } + thisCache = thisCache.data; + } - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { - // First Try to find as-is property data - ret = thisCache[ name ]; + // First Try to find as-is property data + ret = thisCache[ name ]; - // Test for null|undefined property data - if ( ret == null ) { + // Test for null|undefined property data + if ( ret == null ) { - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; } + } else { + ret = thisCache; + } - return ret; - }, + return ret; +} - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } +function internalRemoveData( elem, name, pvt /* For internal use only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } - var thisCache, i, l, + var thisCache, i, l, - isNode = elem.nodeType, + isNode = elem.nodeType, - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } - if ( name ) { + if ( name ) { - thisCache = pvt ? cache[ id ] : cache[ id ].data; + thisCache = pvt ? cache[ id ] : cache[ id ].data; - if ( thisCache ) { + if ( thisCache ) { - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { - // try the string as a key before any manipulation + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); if ( name in thisCache ) { name = [ name ]; } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } + name = name.split(" "); } } + } - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; } } + } - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; } + } - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; - // When all else fails, null - } else { - cache[ id ] = null; - } + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + deletedIds: [], + + // Remove at next major release (1.9/2.0) + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data, false ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name, false ); }, // For internal use only. _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); }, // A method for determining if a DOM node can handle the data expando diff --git a/src/effects.js b/src/effects.js index 9965eee78..cff0dc835 100644 --- a/src/effects.js +++ b/src/effects.js @@ -327,7 +327,7 @@ function defaultPrefilter( elem, props, opts ) { } anim.done(function() { var prop; - jQuery.removeData( elem, "fxshow", true ); + jQuery._removeData( elem, "fxshow" ); for ( prop in orig ) { jQuery.style( elem, prop, orig[ prop ] ); } diff --git a/src/event.js b/src/event.js index 91ec7d757..3fea1a1be 100644 --- a/src/event.js +++ b/src/event.js @@ -15,7 +15,6 @@ var rformElems = /^(?:textarea|input|select)$/i, jQuery.event = { add: function( elem, types, handler, data, selector ) { - var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, handlers, special; @@ -196,7 +195,7 @@ jQuery.event = { // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete - jQuery.removeData( elem, "events", true ); + jQuery._removeData( elem, "events" ); } }, diff --git a/src/queue.js b/src/queue.js index d3a1136a6..c5a0cbd7d 100644 --- a/src/queue.js +++ b/src/queue.js @@ -58,8 +58,8 @@ jQuery.extend({ var key = type + "queueHooks"; return jQuery._data( elem, key ) || jQuery._data( elem, key, { empty: jQuery.Callbacks("once memory").add(function() { - jQuery.removeData( elem, type + "queue", true ); - jQuery.removeData( elem, key, true ); + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); }) }); } diff --git a/test/unit/data.js b/test/unit/data.js index 01d042e4e..d1d947f70 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -19,7 +19,7 @@ function dataTests (elem) { } var oldCacheLength, dataObj, internalDataObj, expected, actual; - + equal( jQuery.data(elem, "foo"), undefined, "No data exists initially" ); strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists initially" ); @@ -50,7 +50,7 @@ function dataTests (elem) { strictEqual( jQuery.data(elem, "foo"), "foo1", "Passing an object extends the data object instead of replacing it" ); equal( jQuery.data(elem, "boom"), "bloz", "Extending the data object works" ); - jQuery._data(elem, "foo", "foo2"); + jQuery._data(elem, "foo", "foo2", true); equal( jQuery._data(elem, "foo"), "foo2", "Setting internal data works" ); equal( jQuery.data(elem, "foo"), "foo1", "Setting internal data does not override user data" ); @@ -66,9 +66,9 @@ function dataTests (elem) { jQuery.removeData(elem); strictEqual( jQuery._data(elem), internalDataObj, "jQuery.removeData does not remove internal data if it exists" ); - jQuery.removeData(elem, undefined, true); + jQuery._removeData(elem); - strictEqual( jQuery.data(elem, jQuery.expando), undefined, "jQuery.removeData on internal data works" ); + strictEqual( jQuery._data(elem, jQuery.expando), undefined, "jQuery.removeData on internal data works" ); strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees all data has been removed from object" ); jQuery._data(elem, "foo", "foo2"); @@ -79,7 +79,7 @@ function dataTests (elem) { // delete the last private data key so we can test removing public data // will destroy the cache - jQuery.removeData( elem, "foo", true ); + jQuery._removeData( elem, "foo" ); if (elem.nodeType) { oldCacheLength = getCacheLength(); @@ -109,9 +109,9 @@ function dataTests (elem) { equal( jQuery.data(elem, "foo"), "foo1", "(sanity check) Ensure data is set in user data object" ); equal( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); - jQuery.removeData(elem, "foo", true); + jQuery._removeData(elem, "foo"); - strictEqual( jQuery.data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" ); + strictEqual( jQuery._data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" ); jQuery._data(elem, "foo", "foo2"); equal( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); @@ -121,11 +121,11 @@ function dataTests (elem) { if ( elem.nodeType ) { oldCacheLength = getCacheLength(); - jQuery.removeData(elem, "foo", true); + jQuery._removeData(elem, "foo"); equal( getCacheLength(), oldCacheLength - 1, "Removing the last item in the internal data object also destroys the user data object when it is empty" ); } else { - jQuery.removeData(elem, "foo", true); + jQuery._removeData(elem, "foo"); if (jQuery.support.deleteExpando) { expected = false; -- 2.39.5