Change the way jQuery.data works so that there is no longer a chance of collision between user data and internal data. Fixes #6968.

This commit is contained in:
Colin Snover 2011-01-09 15:52:33 -06:00
parent 1d1d4fe112
commit 8e59a99e0a
10 changed files with 352 additions and 203 deletions

View File

@ -133,11 +133,11 @@ jQuery.fn.extend({
} else if ( type === "undefined" || type === "boolean" ) { } else if ( type === "undefined" || type === "boolean" ) {
if ( this.className ) { if ( this.className ) {
// store className if set // store className if set
jQuery.data( this, "__className__", this.className ); jQuery._data( this, "__className__", this.className );
} }
// toggle whole className // toggle whole className
this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
} }
}); });
}, },

View File

@ -1,7 +1,6 @@
(function( jQuery ) { (function( jQuery ) {
var windowData = {}, var rbrace = /^(?:\{.*\}|\[.*\])$/;
rbrace = /^(?:\{.*\}|\[.*\])$/;
jQuery.extend({ jQuery.extend({
cache: {}, cache: {},
@ -23,110 +22,163 @@ jQuery.extend({
}, },
hasData: function( elem ) { hasData: function( elem ) {
if ( elem.nodeType ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
elem = jQuery.cache[ elem[jQuery.expando] ];
}
return !!elem && !jQuery.isEmptyObject(elem); return !!elem && !jQuery.isEmptyObject(elem);
}, },
data: function( elem, name, data ) { data: function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) { if ( !jQuery.acceptData( elem ) ) {
return; return;
} }
elem = elem == window ? var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
windowData :
elem;
var isNode = elem.nodeType, // We have to handle DOM nodes and JS objects differently because IE6-7
id = isNode ? elem[ jQuery.expando ] : null, // can't GC object references properly across the DOM-JS boundary
cache = jQuery.cache, thisCache; isNode = elem.nodeType,
if ( isNode && !id && typeof name === "string" && data === undefined ) { // 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[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
// 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 || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
return; return;
} }
// Get the data from the object directly if ( !id ) {
if ( !isNode ) { // Only DOM nodes need a new unique ID for each element since their data
cache = elem; // ends up in the global cache
// Compute a unique ID for the element
} else if ( !id ) {
elem[ jQuery.expando ] = id = ++jQuery.uuid;
}
// Avoid generating a new cache unless none exists and we
// want to manipulate it.
if ( typeof name === "object" ) {
if ( isNode ) { if ( isNode ) {
cache[ id ] = jQuery.extend(cache[ id ], name); elem[ jQuery.expando ] = id = ++jQuery.uuid;
} else { } else {
jQuery.extend( cache, name ); id = jQuery.expando;
}
} }
} else if ( isNode && !cache[ id ] ) { if ( !cache[ id ] ) {
cache[ id ] = {}; cache[ id ] = {};
} }
thisCache = isNode ? cache[ id ] : cache; // 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" ) {
if ( pvt ) {
cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
} else {
cache[ id ] = jQuery.extend(cache[ id ], name);
}
}
thisCache = cache[ id ];
// Internal jQuery data is stored in a separate object inside the object's data
// cache in order to avoid key collisions between internal data and user-defined
// data
if ( pvt ) {
if ( !thisCache[ internalKey ] ) {
thisCache[ internalKey ] = {};
}
thisCache = thisCache[ internalKey ];
}
// Prevent overriding the named cache with undefined values
if ( data !== undefined ) { if ( data !== undefined ) {
thisCache[ name ] = data; thisCache[ name ] = data;
} }
return typeof name === "string" ? thisCache[ name ] : thisCache; return getByName ? thisCache[ name ] : thisCache;
}, },
removeData: function( elem, name ) { removeData: function( elem, name, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) { if ( !jQuery.acceptData( elem ) ) {
return; return;
} }
elem = elem == window ? var internalKey = jQuery.expando, isNode = elem.nodeType,
windowData :
elem;
var isNode = elem.nodeType, // See jQuery.data for more information
id = isNode ? elem[ jQuery.expando ] : elem, cache = isNode ? jQuery.cache : elem,
cache = jQuery.cache,
thisCache = isNode ? cache[ id ] : id; // See jQuery.data for more information
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 we want to remove a specific section of the element's data
if ( name ) { if ( name ) {
var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
if ( thisCache ) { if ( thisCache ) {
// Remove the section of cache data
delete thisCache[ name ]; delete thisCache[ name ];
// If we've removed all the data, remove the element's cache // If there is no data left in the cache, we want to continue
if ( isNode && jQuery.isEmptyObject(thisCache) ) { // and let the cache object itself get destroyed
jQuery.removeData( elem ); if ( !jQuery.isEmptyObject(thisCache) ) {
return;
}
} }
} }
// Otherwise, we want to remove all of the element's data // See jQuery.data for more information
if ( pvt ) {
delete cache[ id ][ internalKey ];
// Don't destroy the parent cache unless the internal data object
// had been the only thing left in it
if ( !jQuery.isEmptyObject(cache[ id ]) ) {
return;
}
}
var internalCache = cache[ id ][ internalKey ];
// Browsers that fail expando deletion also refuse to delete expandos on
// the window, but it will allow it on all other JS objects; other browsers
// don't care
if ( jQuery.support.deleteExpando || cache != window ) {
delete cache[ id ];
} else { } else {
if ( isNode && jQuery.support.deleteExpando ) { cache[ id ] = null;
delete elem[ jQuery.expando ]; }
// We destroyed the entire user cache at once because it's faster than
// iterating through each key, but we need to continue to persist internal
// data if it existed
if ( internalCache ) {
cache[ id ] = {};
cache[ id ][ internalKey ] = internalCache;
// Otherwise, we need to eliminate the expando on the node to avoid
// false lookups in the cache for entries that no longer exist
} else if ( isNode ) {
// IE does not allow us to delete expando properties from nodes,
// nor does it have a removeAttribute function on Document nodes;
// we must handle all of these cases
if ( jQuery.support.deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) { } else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando ); elem.removeAttribute( jQuery.expando );
// Completely remove the data cache
} else if ( isNode ) {
delete cache[ id ];
// Remove all fields from the object
} else { } else {
for ( var n in elem ) { elem[ jQuery.expando ] = null;
delete elem[ n ];
}
} }
} }
}, },
// For internal use only.
_data: function( elem, name, data ) {
return jQuery.data( elem, name, data, true );
},
// A method for determining if a DOM node can handle the data expando // A method for determining if a DOM node can handle the data expando
acceptData: function( elem ) { acceptData: function( elem ) {
if ( elem.nodeName ) { if ( elem.nodeName ) {

10
src/effects.js vendored
View File

@ -27,7 +27,7 @@ jQuery.fn.extend({
// Reset the inline display of this element to learn if it is // Reset the inline display of this element to learn if it is
// being hidden by cascaded rules or not // being hidden by cascaded rules or not
if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
display = elem.style.display = ""; display = elem.style.display = "";
} }
@ -35,7 +35,7 @@ jQuery.fn.extend({
// in a stylesheet to whatever the default browser style is // in a stylesheet to whatever the default browser style is
// for such an element // for such an element
if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
} }
} }
@ -46,7 +46,7 @@ jQuery.fn.extend({
display = elem.style.display; display = elem.style.display;
if ( display === "" || display === "none" ) { if ( display === "" || display === "none" ) {
elem.style.display = jQuery.data(elem, "olddisplay") || ""; elem.style.display = jQuery._data(elem, "olddisplay") || "";
} }
} }
@ -62,8 +62,8 @@ jQuery.fn.extend({
for ( var i = 0, j = this.length; i < j; i++ ) { for ( var i = 0, j = this.length; i < j; i++ ) {
var display = jQuery.css( this[i], "display" ); var display = jQuery.css( this[i], "display" );
if ( display !== "none" && !jQuery.data( this[i], "olddisplay" ) ) { if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
jQuery.data( this[i], "olddisplay", display ); jQuery._data( this[i], "olddisplay", display );
} }
} }

View File

@ -8,7 +8,8 @@ var rnamespaces = /\.(.*)$/,
fcleanup = function( nm ) { fcleanup = function( nm ) {
return nm.replace(rescape, "\\$&"); return nm.replace(rescape, "\\$&");
}, },
focusCounts = { focusin: 0, focusout: 0 }; focusCounts = { focusin: 0, focusout: 0 },
eventKey = "events";
/* /*
* A number of helper functions used for managing events. * A number of helper functions used for managing events.
@ -50,7 +51,7 @@ jQuery.event = {
} }
// Init the element's event structure // Init the element's event structure
var elemData = jQuery.data( elem ); var elemData = jQuery._data( elem );
// If no elemData is found then we must be trying to bind to one of the // If no elemData is found then we must be trying to bind to one of the
// banned noData elements // banned noData elements
@ -58,10 +59,7 @@ jQuery.event = {
return; return;
} }
// Use a key less likely to result in collisions for plain JS objects. var events = elemData[ eventKey ],
// Fixes bug #7150.
var eventKey = elem.nodeType ? "events" : "__events__",
events = elemData[ eventKey ],
eventHandle = elemData.handle; eventHandle = elemData.handle;
if ( typeof events === "function" ) { if ( typeof events === "function" ) {
@ -177,8 +175,7 @@ jQuery.event = {
} }
var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
eventKey = elem.nodeType ? "events" : "__events__", elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
elemData = jQuery.data( elem ),
events = elemData && elemData[ eventKey ]; events = elemData && elemData[ eventKey ];
if ( !elemData || !events ) { if ( !elemData || !events ) {
@ -290,10 +287,10 @@ jQuery.event = {
delete elemData.handle; delete elemData.handle;
if ( typeof elemData === "function" ) { if ( typeof elemData === "function" ) {
jQuery.removeData( elem, eventKey ); jQuery.removeData( elem, eventKey, true );
} else if ( jQuery.isEmptyObject( elemData ) ) { } else if ( jQuery.isEmptyObject( elemData ) ) {
jQuery.removeData( elem ); jQuery.removeData( elem, undefined, true );
} }
} }
}, },
@ -325,9 +322,16 @@ jQuery.event = {
// Only trigger if we've ever bound an event for it // Only trigger if we've ever bound an event for it
if ( jQuery.event.global[ type ] ) { if ( jQuery.event.global[ type ] ) {
// XXX This code smells terrible. event.js should not be directly
// inspecting the data cache
jQuery.each( jQuery.cache, function() { jQuery.each( jQuery.cache, function() {
if ( this.events && this.events[type] ) { // internalKey variable is just used to make it easier to find
jQuery.event.trigger( event, data, this.handle.elem ); // and potentially change this stuff later; currently it just
// points to jQuery.expando
var internalKey = jQuery.expando,
internalCache = this[ internalKey ];
if ( internalCache && internalCache.events && internalCache.events[type] ) {
jQuery.event.trigger( event, data, internalCache.handle.elem );
} }
}); });
} }
@ -353,8 +357,8 @@ jQuery.event = {
// Trigger the event, it is assumed that "handle" is a function // Trigger the event, it is assumed that "handle" is a function
var handle = elem.nodeType ? var handle = elem.nodeType ?
jQuery.data( elem, "handle" ) : jQuery._data( elem, "handle" ) :
(jQuery.data( elem, "__events__" ) || {}).handle; (jQuery._data( elem, eventKey ) || {}).handle;
if ( handle ) { if ( handle ) {
handle.apply( elem, data ); handle.apply( elem, data );
@ -432,7 +436,7 @@ jQuery.event = {
event.namespace = event.namespace || namespace_sort.join("."); event.namespace = event.namespace || namespace_sort.join(".");
events = jQuery.data(this, this.nodeType ? "events" : "__events__"); events = jQuery._data(this, eventKey);
if ( typeof events === "function" ) { if ( typeof events === "function" ) {
events = events.events; events = events.events;
@ -787,12 +791,12 @@ if ( !jQuery.support.changeBubbles ) {
return; return;
} }
data = jQuery.data( elem, "_change_data" ); data = jQuery._data( elem, "_change_data" );
val = getVal(elem); val = getVal(elem);
// the current data will be also retrieved by beforeactivate // the current data will be also retrieved by beforeactivate
if ( e.type !== "focusout" || elem.type !== "radio" ) { if ( e.type !== "focusout" || elem.type !== "radio" ) {
jQuery.data( elem, "_change_data", val ); jQuery._data( elem, "_change_data", val );
} }
if ( data === undefined || val === data ) { if ( data === undefined || val === data ) {
@ -837,7 +841,7 @@ if ( !jQuery.support.changeBubbles ) {
// information // information
beforeactivate: function( e ) { beforeactivate: function( e ) {
var elem = e.target; var elem = e.target;
jQuery.data( elem, "_change_data", getVal(elem) ); jQuery._data( elem, "_change_data", getVal(elem) );
} }
}, },
@ -986,8 +990,8 @@ jQuery.fn.extend({
return this.click( jQuery.proxy( fn, function( event ) { return this.click( jQuery.proxy( fn, function( event ) {
// Figure out which function to execute // Figure out which function to execute
var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
// Make sure that clicks stop // Make sure that clicks stop
event.preventDefault(); event.preventDefault();
@ -1075,7 +1079,7 @@ function liveHandler( event ) {
var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
elems = [], elems = [],
selectors = [], selectors = [],
events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); events = jQuery._data( this, eventKey );
if ( typeof events === "function" ) { if ( typeof events === "function" ) {
events = events.events; events = events.events;

View File

@ -381,9 +381,15 @@ function cloneCopyEvent(orig, ret) {
throw "Cloned data mismatch"; throw "Cloned data mismatch";
} }
var oldData = jQuery.data( orig[nodeIndex] ), var internalKey = jQuery.expando,
curData = jQuery.data( this, oldData ), oldData = jQuery.data( orig[nodeIndex] ),
events = oldData && oldData.events; curData = jQuery.data( this, oldData );
// Switch to use the internal data object, if it exists, for the next
// stage of data copying
if ( (oldData = oldData[ internalKey ]) ) {
var events = oldData.events;
curData = curData[ internalKey ] = jQuery.extend({}, oldData);
if ( events ) { if ( events ) {
delete curData.handle; delete curData.handle;
@ -395,6 +401,7 @@ function cloneCopyEvent(orig, ret) {
} }
} }
} }
}
}); });
} }
@ -594,8 +601,7 @@ jQuery.extend({
}, },
cleanData: function( elems ) { cleanData: function( elems ) {
var data, id, cache = jQuery.cache, var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
special = jQuery.event.special,
deleteExpando = jQuery.support.deleteExpando; deleteExpando = jQuery.support.deleteExpando;
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
@ -606,13 +612,14 @@ jQuery.extend({
id = elem[ jQuery.expando ]; id = elem[ jQuery.expando ];
if ( id ) { if ( id ) {
data = cache[ id ]; data = cache[ id ] && cache[ id ][ internalKey ];
if ( data && data.events ) { if ( data && data.events ) {
for ( var type in data.events ) { for ( var type in data.events ) {
if ( special[ type ] ) { if ( special[ type ] ) {
jQuery.event.remove( elem, type ); jQuery.event.remove( elem, type );
// This is a shortcut to avoid jQuery.event.remove's overhead
} else { } else {
jQuery.removeEvent( elem, type, data.handle ); jQuery.removeEvent( elem, type, data.handle );
} }

View File

@ -7,7 +7,7 @@ jQuery.extend({
} }
type = (type || "fx") + "queue"; type = (type || "fx") + "queue";
var q = jQuery.data( elem, type ); var q = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup // Speed up dequeue by getting out quickly if this is just a lookup
if ( !data ) { if ( !data ) {
@ -15,7 +15,7 @@ jQuery.extend({
} }
if ( !q || jQuery.isArray(data) ) { if ( !q || jQuery.isArray(data) ) {
q = jQuery.data( elem, type, jQuery.makeArray(data) ); q = jQuery._data( elem, type, jQuery.makeArray(data) );
} else { } else {
q.push( data ); q.push( data );

View File

@ -703,12 +703,12 @@ var testToggleClass = function(valueObj) {
// toggleClass storage // toggleClass storage
e.toggleClass(true); e.toggleClass(true);
ok( e.get(0).className === "", "Assert class is empty (data was empty)" ); ok( e[0].className === "", "Assert class is empty (data was empty)" );
e.addClass("testD testE"); e.addClass("testD testE");
ok( e.is(".testD.testE"), "Assert class present" ); ok( e.is(".testD.testE"), "Assert class present" );
e.toggleClass(); e.toggleClass();
ok( !e.is(".testD.testE"), "Assert class not present" ); ok( !e.is(".testD.testE"), "Assert class not present" );
ok( e.data('__className__') === 'testD testE', "Assert data was stored" ); ok( jQuery._data(e[0], '__className__') === 'testD testE', "Assert data was stored" );
e.toggleClass(); e.toggleClass();
ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); ok( e.is(".testD.testE"), "Assert class present (restored from data)" );
e.toggleClass(false); e.toggleClass(false);
@ -720,11 +720,9 @@ var testToggleClass = function(valueObj) {
e.toggleClass(); e.toggleClass();
ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); ok( e.is(".testD.testE"), "Assert class present (restored from data)" );
// Cleanup // Cleanup
e.removeClass("testD"); e.removeClass("testD");
e.removeData('__className__'); jQuery.removeData(e[0], '__className__', true);
}; };
test("toggleClass(String|boolean|undefined[, boolean])", function() { test("toggleClass(String|boolean|undefined[, boolean])", function() {
@ -785,7 +783,7 @@ test("toggleClass(Fucntion[, boolean]) with incoming value", function() {
// Cleanup // Cleanup
e.removeClass("test"); e.removeClass("test");
e.removeData('__className__'); jQuery.removeData(e[0], '__className__', true);
}); });
test("addClass, removeClass, hasClass", function() { test("addClass, removeClass, hasClass", function() {

View File

@ -1,22 +1,159 @@
module("data"); module("data", { teardown: moduleTeardown });
test("expando", function(){ test("expando", function(){
expect(6); expect(1);
equals("expando" in jQuery, true, "jQuery is exposing the expando"); equals("expando" in jQuery, true, "jQuery is exposing the expando");
});
var obj = {}; function dataTests (elem) {
equals( jQuery.data(obj), obj, "jQuery.data(obj) returns the object"); // expect(32)
equals( jQuery.expando in obj, false, "jQuery.data(obj) did not add an expando to the object" );
obj = {}; function getCacheLength() {
jQuery.data(obj, 'test'); var cacheLength = 0;
equals( jQuery.expando in obj, false, "jQuery.data(obj,key) did not add an expando to the object" ); for (var i in jQuery.cache) {
++cacheLength;
}
obj = {}; return cacheLength;
jQuery.data(obj, "foo", "bar"); }
equals( jQuery.expando in obj, false, "jQuery.data(obj,key,value) did not add an expando to the object" );
equals( obj.foo, "bar", "jQuery.data(obj,key,value) sets fields directly on the object." ); equals( jQuery.data(elem, "foo"), undefined, "No data exists initially" );
strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists initially" );
var dataObj = jQuery.data(elem);
equals( typeof dataObj, "object", "Calling data with no args gives us a data object reference" );
strictEqual( jQuery.data(elem), dataObj, "Calling jQuery.data returns the same data object when called multiple times" );
strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists even when an empty data obj exists" );
dataObj.foo = "bar";
equals( jQuery.data(elem, "foo"), "bar", "Data is readable by jQuery.data when set directly on a returned data object" );
strictEqual( jQuery.hasData(elem), true, "jQuery.hasData agrees data exists when data exists" );
jQuery.data(elem, "foo", "baz");
equals( jQuery.data(elem, "foo"), "baz", "Data can be changed by jQuery.data" );
equals( dataObj.foo, "baz", "Changes made through jQuery.data propagate to referenced data object" );
jQuery.data(elem, "foo", undefined);
equals( jQuery.data(elem, "foo"), "baz", "Data is not unset by passing undefined to jQuery.data" );
jQuery.data(elem, "foo", null);
strictEqual( jQuery.data(elem, "foo"), null, "Setting null using jQuery.data works OK" );
jQuery.data(elem, "foo", "foo1");
jQuery.data(elem, { "bar" : "baz", "boom" : "bloz" });
strictEqual( jQuery.data(elem, "foo"), "foo1", "Passing an object extends the data object instead of replacing it" );
equals( jQuery.data(elem, "boom"), "bloz", "Extending the data object works" );
jQuery._data(elem, "foo", "foo2");
equals( jQuery._data(elem, "foo"), "foo2", "Setting internal data works" );
equals( jQuery.data(elem, "foo"), "foo1", "Setting internal data does not override user data" );
var internalDataObj = jQuery.data(elem, jQuery.expando);
strictEqual( jQuery._data(elem), internalDataObj, "Internal data object is accessible via jQuery.expando property" );
notStrictEqual( dataObj, internalDataObj, "Internal data object is not the same as user data object" );
strictEqual( elem.boom, undefined, "Data is never stored directly on the object" );
jQuery.removeData(elem, "foo");
strictEqual( jQuery.data(elem, "foo"), undefined, "jQuery.removeData removes single properties" );
jQuery.removeData(elem);
strictEqual( jQuery.data(elem, jQuery.expando), internalDataObj, "jQuery.removeData does not remove internal data if it exists" );
jQuery.removeData(elem, undefined, true);
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");
strictEqual( jQuery.hasData(elem), true, "jQuery.hasData shows data exists even if it is only internal data" );
jQuery.data(elem, "foo", "foo1");
equals( jQuery._data(elem, "foo"), "foo2", "Setting user data does not override internal data" );
jQuery.removeData(elem, undefined, true);
equals( jQuery.data(elem, "foo"), "foo1", "jQuery.removeData for internal data does not remove user data" );
if (elem.nodeType) {
var oldCacheLength = getCacheLength();
jQuery.removeData(elem, "foo");
equals( getCacheLength(), oldCacheLength - 1, "Removing the last item in the data object destroys it" );
}
else {
jQuery.removeData(elem, "foo");
var expected, actual;
if (jQuery.support.deleteExpando) {
expected = false;
actual = jQuery.expando in elem;
}
else {
expected = null;
actual = elem[ jQuery.expando ];
}
equals( actual, expected, "Removing the last item in the data object destroys it" );
}
jQuery.data(elem, "foo", "foo1");
jQuery._data(elem, "foo", "foo2");
equals( jQuery.data(elem, "foo"), "foo1", "(sanity check) Ensure data is set in user data object" );
equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" );
jQuery.removeData(elem, "foo", true);
strictEqual( jQuery.data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" );
jQuery._data(elem, "foo", "foo2");
equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" );
jQuery.removeData(elem, "foo");
equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) jQuery.removeData for user data does not remove internal data" );
if (elem.nodeType) {
oldCacheLength = getCacheLength();
jQuery.removeData(elem, "foo", true);
equals( 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);
if (jQuery.support.deleteExpando) {
expected = false;
actual = jQuery.expando in elem;
}
else {
expected = null;
actual = elem[ jQuery.expando ];
}
equals( actual, expected, "Removing the last item in the internal data object also destroys the user data object when it is empty" );
}
}
test("jQuery.data", function() {
expect(128);
var div = document.createElement("div");
dataTests(div);
dataTests({});
// remove bound handlers from window object to stop potential false positives caused by fix for #5280 in
// transports/xhr.js
jQuery(window).unbind("unload");
dataTests(window);
dataTests(document);
// clean up unattached element
jQuery(div).remove();
}); });
test("jQuery.acceptData", function() { test("jQuery.acceptData", function() {
@ -37,68 +174,11 @@ test("jQuery.acceptData", function() {
ok( !jQuery.acceptData( applet ), "applet" ); ok( !jQuery.acceptData( applet ), "applet" );
}); });
test("jQuery.data", function() {
expect(15);
var div = document.createElement("div");
ok( jQuery.data(div, "test") === undefined, "Check for no data exists" );
jQuery.data(div, "test", "success");
equals( jQuery.data(div, "test"), "success", "Check for added data" );
ok( jQuery.data(div, "notexist") === undefined, "Check for no data exists" );
var data = jQuery.data(div);
same( data, { "test": "success" }, "Return complete data set" );
jQuery.data(div, "test", "overwritten");
equals( jQuery.data(div, "test"), "overwritten", "Check for overwritten data" );
jQuery.data(div, "test", undefined);
equals( jQuery.data(div, "test"), "overwritten", "Check that data wasn't removed");
jQuery.data(div, "test", null);
ok( jQuery.data(div, "test") === null, "Check for null data");
jQuery.data(div, "test3", "orig");
jQuery.data(div, { "test": "in", "test2": "in2" });
equals( jQuery.data(div, "test"), "in", "Verify setting an object in data" );
equals( jQuery.data(div, "test2"), "in2", "Verify setting an object in data" );
equals( jQuery.data(div, "test3"), "orig", "Verify original not overwritten" );
var obj = {};
jQuery.data( obj, "prop", true );
ok( obj.prop, "Data is being stored on the object" );
equals( jQuery.data( obj, "prop" ), true, "Make sure the right value is retrieved" );
jQuery.data( window, "BAD", true );
ok( !window[ jQuery.expando ], "Make sure there is no expando on the window object." );
ok( !window.BAD, "And make sure that the property wasn't set directly on the window." );
ok( jQuery.data( window, "BAD" ), "Make sure that the value was set." );
});
test("jQuery.hasData", function() {
expect(6);
function testData(obj) {
equals( jQuery.hasData(obj), false, "No data exists" );
jQuery.data( obj, "foo", "bar" );
equals( jQuery.hasData(obj), true, "Data exists" );
jQuery.removeData( obj, "foo" );
equals( jQuery.hasData(obj), false, "Data was removed" );
}
testData(document.createElement('div'));
testData({});
});
test(".data()", function() { test(".data()", function() {
expect(5); expect(5);
var div = jQuery("#foo"); var div = jQuery("#foo");
strictEqual( div.data("foo"), undefined, "Make sure that missing result is undefined" ); strictEqual( div.data("foo"), undefined, "Make sure that missing result is undefined" );
div.data("test", "success"); div.data("test", "success");
same( div.data(), {test: "success"}, "data() get the entire data object" ); same( div.data(), {test: "success"}, "data() get the entire data object" );
strictEqual( div.data("foo"), undefined, "Make sure that missing result is still undefined" ); strictEqual( div.data("foo"), undefined, "Make sure that missing result is still undefined" );
@ -107,7 +187,7 @@ test(".data()", function() {
equals( nodiv.data(), null, "data() on empty set returns null" ); equals( nodiv.data(), null, "data() on empty set returns null" );
var obj = { foo: "bar" }; var obj = { foo: "bar" };
equals( jQuery(obj).data(), obj, "Retrieve data object from a wrapped JS object (#7524)" ); deepEqual( jQuery(obj).data(), {}, "Retrieve data object from a wrapped JS object (#7524)" );
}) })
test(".data(String) and .data(String, Object)", function() { test(".data(String) and .data(String, Object)", function() {
@ -194,11 +274,14 @@ test(".data(String) and .data(String, Object)", function() {
equals( $elem.data('null',null).data('null'), null, "null's are preserved"); equals( $elem.data('null',null).data('null'), null, "null's are preserved");
equals( $elem.data('emptyString','').data('emptyString'), '', "Empty strings are preserved"); equals( $elem.data('emptyString','').data('emptyString'), '', "Empty strings are preserved");
equals( $elem.data('false',false).data('false'), false, "false's are preserved"); equals( $elem.data('false',false).data('false'), false, "false's are preserved");
equals( $elem.data('exists'), true, "Existing data is returned" ); equals( $elem.data('exists'), undefined, "Existing data is not returned" );
// Clean up // Clean up
$elem.removeData(); $elem.removeData();
ok( jQuery.isEmptyObject( $elem[0] ), "removeData clears the object" ); deepEqual( $elem[0], {exists:true}, "removeData does not clear the object" );
// manually clean up detached elements
parent.remove();
}); });
test("data-* attributes", function() { test("data-* attributes", function() {
@ -323,13 +406,17 @@ test(".data(Object)", function() {
var obj = {test:"unset"}, var obj = {test:"unset"},
jqobj = jQuery(obj); jqobj = jQuery(obj);
jqobj.data("test", "unset");
jqobj.data({ "test": "in", "test2": "in2" }); jqobj.data({ "test": "in", "test2": "in2" });
equals( obj.test, "in", "Verify setting an object on an object extends the object" ); equals( jQuery.data(obj).test, "in", "Verify setting an object on an object extends the data object" );
equals( obj.test2, "in2", "Verify setting an object on an object extends the object" ); equals( obj.test2, undefined, "Verify setting an object on an object does not extend the object" );
// manually clean up detached elements
div.remove();
}); });
test("jQuery.removeData", function() { test("jQuery.removeData", function() {
expect(7); expect(6);
var div = jQuery("#foo")[0]; var div = jQuery("#foo")[0];
jQuery.data(div, "test", "testing"); jQuery.data(div, "test", "testing");
jQuery.removeData(div, "test"); jQuery.removeData(div, "test");
@ -342,10 +429,9 @@ test("jQuery.removeData", function() {
var obj = {}; var obj = {};
jQuery.data(obj, "test", "testing"); jQuery.data(obj, "test", "testing");
equals( obj.test, "testing", "verify data on plain object"); equals( jQuery(obj).data("test"), "testing", "verify data on plain object");
jQuery.removeData(obj, "test"); jQuery.removeData(obj, "test");
equals( jQuery.data(obj, "test"), undefined, "Check removal of data on plain object" ); equals( jQuery.data(obj, "test"), undefined, "Check removal of data on plain object" );
equals( obj.test, undefined, "Check removal of data directly from plain object" );
jQuery.data( window, "BAD", true ); jQuery.data( window, "BAD", true );
jQuery.removeData( window, "BAD" ); jQuery.removeData( window, "BAD" );

View File

@ -895,7 +895,7 @@ test("hide hidden elements (bug #7141)", function() {
var div = jQuery("<div style='display:none'></div>").appendTo("#main"); var div = jQuery("<div style='display:none'></div>").appendTo("#main");
equals( div.css("display"), "none", "Element is hidden by default" ); equals( div.css("display"), "none", "Element is hidden by default" );
div.hide(); div.hide();
ok( !div.data("olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" );
div.show(); div.show();
equals( div.css("display"), "block", "Show a double-hidden element" ); equals( div.css("display"), "block", "Show a double-hidden element" );
@ -910,7 +910,7 @@ test("hide hidden elements, with animation (bug #7141)", function() {
var div = jQuery("<div style='display:none'></div>").appendTo("#main"); var div = jQuery("<div style='display:none'></div>").appendTo("#main");
equals( div.css("display"), "none", "Element is hidden by default" ); equals( div.css("display"), "none", "Element is hidden by default" );
div.hide(1, function () { div.hide(1, function () {
ok( !div.data("olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" );
div.show(1, function () { div.show(1, function () {
equals( div.css("display"), "block", "Show a double-hidden element" ); equals( div.css("display"), "block", "Show a double-hidden element" );
start(); start();

View File

@ -28,7 +28,7 @@ test("bind(), with data", function() {
}; };
jQuery("#firstp").bind("click", {foo: "bar"}, handler).click().unbind("click", handler); jQuery("#firstp").bind("click", {foo: "bar"}, handler).click().unbind("click", handler);
ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." );
}); });
test("click(), with data", function() { test("click(), with data", function() {
@ -39,7 +39,7 @@ test("click(), with data", function() {
}; };
jQuery("#firstp").click({foo: "bar"}, handler).click().unbind("click", handler); jQuery("#firstp").click({foo: "bar"}, handler).click().unbind("click", handler);
ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." );
}); });
test("bind(), with data, trigger with data", function() { test("bind(), with data, trigger with data", function() {
@ -505,7 +505,7 @@ test("bind(), with different this object", function() {
.bind("click", jQuery.proxy(handler1, thisObject)).click().unbind("click", handler1) .bind("click", jQuery.proxy(handler1, thisObject)).click().unbind("click", handler1)
.bind("click", data, jQuery.proxy(handler2, thisObject)).click().unbind("click", handler2); .bind("click", data, jQuery.proxy(handler2, thisObject)).click().unbind("click", handler2);
ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." ); ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." );
}); });
test("bind(name, false), unbind(name, false)", function() { test("bind(name, false), unbind(name, false)", function() {
@ -547,7 +547,7 @@ test("bind()/trigger()/unbind() on plain object", function() {
} }
}); });
var events = jQuery(obj).data("__events__"); var events = jQuery._data(obj, "events");
ok( events, "Object has events bound." ); ok( events, "Object has events bound." );
equals( obj.events, undefined, "Events object on plain objects is not events" ); equals( obj.events, undefined, "Events object on plain objects is not events" );
equals( typeof events, "function", "'events' expando is a function on plain objects." ); equals( typeof events, "function", "'events' expando is a function on plain objects." );
@ -567,7 +567,9 @@ test("bind()/trigger()/unbind() on plain object", function() {
// Make sure it doesn't complain when no events are found // Make sure it doesn't complain when no events are found
jQuery(obj).unbind("test"); jQuery(obj).unbind("test");
equals( obj.__events__, undefined, "Make sure events object is removed" ); equals( obj && obj[ jQuery.expando ] &&
obj[ jQuery.expando ][ jQuery.expando ] &&
obj[ jQuery.expando ][ jQuery.expando ].events, undefined, "Make sure events object is removed" );
}); });
test("unbind(type)", function() { test("unbind(type)", function() {
@ -947,7 +949,7 @@ test("toggle(Function, Function, ...)", function() {
equals( turn, 2, "Trying toggle with 3 functions, attempt 5 yields 2"); equals( turn, 2, "Trying toggle with 3 functions, attempt 5 yields 2");
$div.unbind('click',fns[0]); $div.unbind('click',fns[0]);
var data = jQuery.data( $div[0], 'events' ); var data = jQuery._data( $div[0], 'events' );
ok( !data, "Unbinding one function from toggle unbinds them all"); ok( !data, "Unbinding one function from toggle unbinds them all");
// Test Multi-Toggles // Test Multi-Toggles
@ -1065,7 +1067,7 @@ test(".live()/.die()", function() {
equals( clicked, 2, "live with a context" ); equals( clicked, 2, "live with a context" );
// Make sure the event is actually stored on the context // Make sure the event is actually stored on the context
ok( jQuery.data(container, "events").live, "live with a context" ); ok( jQuery._data(container, "events").live, "live with a context" );
// Test unbinding with a different context // Test unbinding with a different context
jQuery("#foo", container).die("click"); jQuery("#foo", container).die("click");
@ -1578,7 +1580,7 @@ test(".delegate()/.undelegate()", function() {
equals( clicked, 2, "delegate with a context" ); equals( clicked, 2, "delegate with a context" );
// Make sure the event is actually stored on the context // Make sure the event is actually stored on the context
ok( jQuery.data(container, "events").live, "delegate with a context" ); ok( jQuery._data(container, "events").live, "delegate with a context" );
// Test unbinding with a different context // Test unbinding with a different context
jQuery("#main").undelegate("#foo", "click"); jQuery("#main").undelegate("#foo", "click");
@ -1907,7 +1909,7 @@ test("window resize", function() {
ok( true, "Resize event fired." ); ok( true, "Resize event fired." );
}).resize().unbind("resize"); }).resize().unbind("resize");
ok( !jQuery(window).data("__events__"), "Make sure all the events are gone." ); ok( !jQuery._data(window, "__events__"), "Make sure all the events are gone." );
}); });
test("focusin bubbles", function() { test("focusin bubbles", function() {