aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorColin Snover <github.com@zetafleet.com>2011-01-17 15:31:43 -0600
committerColin Snover <github.com@zetafleet.com>2011-01-17 15:31:43 -0600
commite78d3a7e2d47e9796f87c18b76f8178b0bee42c5 (patch)
tree1fced65554ceddbe4a2f30ba7102696c219c7815 /src
parentf01ef93aab1d2e90efbcacc71b01180f7f33656b (diff)
parent57cc182a40e909868d41f9b1bb405b06138f6cae (diff)
downloadjquery-e78d3a7e2d47e9796f87c18b76f8178b0bee42c5.tar.gz
jquery-e78d3a7e2d47e9796f87c18b76f8178b0bee42c5.zip
Merge in data_nocollide branch. Fixes #6968, improves unit testing framework checks for leaky stuff.
Diffstat (limited to 'src')
-rw-r--r--src/attributes.js4
-rw-r--r--src/data.js169
-rw-r--r--src/effects.js10
-rw-r--r--src/event.js46
-rw-r--r--src/manipulation.js33
-rw-r--r--src/queue.js8
6 files changed, 172 insertions, 98 deletions
diff --git a/src/attributes.js b/src/attributes.js
index fec132340..d37400a68 100644
--- a/src/attributes.js
+++ b/src/attributes.js
@@ -133,11 +133,11 @@ jQuery.fn.extend({
} else if ( type === "undefined" || type === "boolean" ) {
if ( this.className ) {
// store className if set
- jQuery.data( this, "__className__", this.className );
+ jQuery._data( this, "__className__", this.className );
}
// toggle whole className
- this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || "";
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
}
});
},
diff --git a/src/data.js b/src/data.js
index 4d1d1bd55..21f0e3a55 100644
--- a/src/data.js
+++ b/src/data.js
@@ -1,7 +1,6 @@
(function( jQuery ) {
-var windowData = {},
- rbrace = /^(?:\{.*\}|\[.*\])$/;
+var rbrace = /^(?:\{.*\}|\[.*\])$/;
jQuery.extend({
cache: {},
@@ -23,110 +22,170 @@ jQuery.extend({
},
hasData: function( elem ) {
- if ( elem.nodeType ) {
- elem = jQuery.cache[ elem[jQuery.expando] ];
- }
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !jQuery.isEmptyObject(elem);
},
- data: function( elem, name, data ) {
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
- elem = elem == window ?
- windowData :
- elem;
+ var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
+
+ // 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,
- var isNode = elem.nodeType,
- id = isNode ? elem[ jQuery.expando ] : null,
- cache = jQuery.cache, thisCache;
+ // 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;
- if ( isNode && !id && typeof name === "string" && data === undefined ) {
+ // 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;
}
- // Get the data from the object directly
- if ( !isNode ) {
- cache = elem;
+ 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[ jQuery.expando ] = id = ++jQuery.uuid;
+ } else {
+ id = jQuery.expando;
+ }
+ }
- // Compute a unique ID for the element
- } else if ( !id ) {
- elem[ jQuery.expando ] = id = ++jQuery.uuid;
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
}
- // Avoid generating a new cache unless none exists and we
- // want to manipulate it.
+ // 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 ( isNode ) {
+ if ( pvt ) {
+ cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
+ } else {
cache[ id ] = jQuery.extend(cache[ id ], name);
+ }
+ }
- } else {
- jQuery.extend( cache, 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 ] = {};
}
- } else if ( isNode && !cache[ id ] ) {
- cache[ id ] = {};
+ thisCache = thisCache[ internalKey ];
}
- thisCache = isNode ? cache[ id ] : cache;
-
- // Prevent overriding the named cache with undefined values
if ( data !== undefined ) {
thisCache[ name ] = data;
}
- return typeof name === "string" ? thisCache[ name ] : thisCache;
+ // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
+ // not attempt to inspect the internal events object using jQuery.data, as this
+ // internal data object is undocumented and subject to change.
+ if ( name === "events" && !thisCache[name] ) {
+ return thisCache[ internalKey ] && thisCache[ internalKey ].events;
+ }
+
+ return getByName ? thisCache[ name ] : thisCache;
},
- removeData: function( elem, name ) {
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
- elem = elem == window ?
- windowData :
- elem;
+ var internalKey = jQuery.expando, isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
- var isNode = elem.nodeType,
- id = isNode ? elem[ jQuery.expando ] : 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 ) {
+ var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
+
if ( thisCache ) {
- // Remove the section of cache data
delete thisCache[ name ];
- // If we've removed all the data, remove the element's cache
- if ( isNode && jQuery.isEmptyObject(thisCache) ) {
- jQuery.removeData( elem );
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !jQuery.isEmptyObject(thisCache) ) {
+ return;
}
}
+ }
+
+ // 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 ];
- // Otherwise, we want to remove all of the element's data
+ // 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 {
- if ( isNode && jQuery.support.deleteExpando ) {
- delete elem[ jQuery.expando ];
+ cache[ id ] = null;
+ }
+ // 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 ) {
elem.removeAttribute( jQuery.expando );
-
- // Completely remove the data cache
- } else if ( isNode ) {
- delete cache[ id ];
-
- // Remove all fields from the object
} else {
- for ( var n in elem ) {
- delete elem[ n ];
- }
+ elem[ jQuery.expando ] = null;
}
}
},
+ // 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
acceptData: function( elem ) {
if ( elem.nodeName ) {
diff --git a/src/effects.js b/src/effects.js
index bd57ffc3d..b0675395f 100644
--- a/src/effects.js
+++ b/src/effects.js
@@ -27,7 +27,7 @@ jQuery.fn.extend({
// Reset the inline display of this element to learn if it is
// 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 = "";
}
@@ -35,7 +35,7 @@ jQuery.fn.extend({
// in a stylesheet to whatever the default browser style is
// for such an element
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;
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++ ) {
var display = jQuery.css( this[i], "display" );
- if ( display !== "none" && !jQuery.data( this[i], "olddisplay" ) ) {
- jQuery.data( this[i], "olddisplay", display );
+ if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
+ jQuery._data( this[i], "olddisplay", display );
}
}
diff --git a/src/event.js b/src/event.js
index 34cd91b85..2ddf28812 100644
--- a/src/event.js
+++ b/src/event.js
@@ -8,7 +8,8 @@ var rnamespaces = /\.(.*)$/,
fcleanup = function( nm ) {
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.
@@ -50,7 +51,7 @@ jQuery.event = {
}
// 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
// banned noData elements
@@ -58,10 +59,7 @@ jQuery.event = {
return;
}
- // Use a key less likely to result in collisions for plain JS objects.
- // Fixes bug #7150.
- var eventKey = elem.nodeType ? "events" : "__events__",
- events = elemData[ eventKey ],
+ var events = elemData[ eventKey ],
eventHandle = elemData.handle;
if ( typeof events === "function" ) {
@@ -177,8 +175,7 @@ jQuery.event = {
}
var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
- eventKey = elem.nodeType ? "events" : "__events__",
- elemData = jQuery.data( elem ),
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
events = elemData && elemData[ eventKey ];
if ( !elemData || !events ) {
@@ -290,10 +287,10 @@ jQuery.event = {
delete elemData.handle;
if ( typeof elemData === "function" ) {
- jQuery.removeData( elem, eventKey );
+ jQuery.removeData( elem, eventKey, true );
} 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
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() {
- if ( this.events && this.events[type] ) {
- jQuery.event.trigger( event, data, this.handle.elem );
+ // internalKey variable is just used to make it easier to find
+ // 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
var handle = elem.nodeType ?
- jQuery.data( elem, "handle" ) :
- (jQuery.data( elem, "__events__" ) || {}).handle;
+ jQuery._data( elem, "handle" ) :
+ (jQuery._data( elem, eventKey ) || {}).handle;
if ( handle ) {
handle.apply( elem, data );
@@ -432,7 +436,7 @@ jQuery.event = {
event.namespace = event.namespace || namespace_sort.join(".");
- events = jQuery.data(this, this.nodeType ? "events" : "__events__");
+ events = jQuery._data(this, eventKey);
if ( typeof events === "function" ) {
events = events.events;
@@ -787,12 +791,12 @@ if ( !jQuery.support.changeBubbles ) {
return;
}
- data = jQuery.data( elem, "_change_data" );
+ data = jQuery._data( elem, "_change_data" );
val = getVal(elem);
// the current data will be also retrieved by beforeactivate
if ( e.type !== "focusout" || elem.type !== "radio" ) {
- jQuery.data( elem, "_change_data", val );
+ jQuery._data( elem, "_change_data", val );
}
if ( data === undefined || val === data ) {
@@ -837,7 +841,7 @@ if ( !jQuery.support.changeBubbles ) {
// information
beforeactivate: function( e ) {
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 ) {
// Figure out which function to execute
- var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
- jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
// Make sure that clicks stop
event.preventDefault();
@@ -1075,7 +1079,7 @@ function liveHandler( event ) {
var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
elems = [],
selectors = [],
- events = jQuery.data( this, this.nodeType ? "events" : "__events__" );
+ events = jQuery._data( this, eventKey );
if ( typeof events === "function" ) {
events = events.events;
diff --git a/src/manipulation.js b/src/manipulation.js
index cf533c818..596a45736 100644
--- a/src/manipulation.js
+++ b/src/manipulation.js
@@ -346,7 +346,7 @@ jQuery.fn.extend({
table ?
root(this[i], first) :
this[i],
- i > 0 || results.cacheable || this.length > 1 ?
+ i > 0 || results.cacheable || (this.length > 1 && i > 0) ?
jQuery(fragment).clone(true)[0] :
fragment
);
@@ -381,17 +381,24 @@ function cloneCopyEvent(orig, ret) {
throw "Cloned data mismatch";
}
- var oldData = jQuery.data( orig[nodeIndex] ),
- curData = jQuery.data( this, oldData ),
- events = oldData && oldData.events;
+ var internalKey = jQuery.expando,
+ oldData = jQuery.data( orig[nodeIndex] ),
+ curData = jQuery.data( this, oldData );
- if ( events ) {
- delete curData.handle;
- curData.events = {};
+ // 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);
- for ( var type in events ) {
- for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
- jQuery.event.add( this, type, events[ type ][ i ], events[ type ][ i ].data );
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( var type in events ) {
+ for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( this, type, events[ type ][ i ], events[ type ][ i ].data );
+ }
}
}
}
@@ -608,8 +615,7 @@ jQuery.extend({
},
cleanData: function( elems ) {
- var data, id, cache = jQuery.cache,
- special = jQuery.event.special,
+ var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
deleteExpando = jQuery.support.deleteExpando;
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
@@ -620,13 +626,14 @@ jQuery.extend({
id = elem[ jQuery.expando ];
if ( id ) {
- data = cache[ id ];
+ data = cache[ id ] && cache[ id ][ internalKey ];
if ( data && data.events ) {
for ( var type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
+ // This is a shortcut to avoid jQuery.event.remove's overhead
} else {
jQuery.removeEvent( elem, type, data.handle );
}
diff --git a/src/queue.js b/src/queue.js
index 735b0e189..9e3e2fb52 100644
--- a/src/queue.js
+++ b/src/queue.js
@@ -7,7 +7,7 @@ jQuery.extend({
}
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
if ( !data ) {
@@ -15,7 +15,7 @@ jQuery.extend({
}
if ( !q || jQuery.isArray(data) ) {
- q = jQuery.data( elem, type, jQuery.makeArray(data) );
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
q.push( data );
@@ -46,6 +46,10 @@ jQuery.extend({
jQuery.dequeue(elem, type);
});
}
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue", true );
+ }
}
});