]> source.dussan.org Git - jquery.git/commitdiff
2.0: Rewrite data.js (Incl. event, manipulation, tests)
authorRick Waldron <waldron.rick@gmail.com>
Sun, 3 Feb 2013 20:27:55 +0000 (15:27 -0500)
committerRick Waldron <waldron.rick@gmail.com>
Sun, 3 Feb 2013 20:27:55 +0000 (15:27 -0500)
src/data.js
src/event.js
src/manipulation.js
test/data/onbeforeunload.html [new file with mode: 0644]
test/data/testrunner.js
test/unit/data.js
test/unit/event.js

index 596341c6aa9cc19755fb2ac289d96aa1ff964a14..448decd00b18744ba5d24ac4e6c98728005892de 100644 (file)
-var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+var data_user, data_priv,
+       rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
        rmultiDash = /([A-Z])/g;
 
-function internalData( elem, name, data, pvt ) {
-       if ( !jQuery.acceptData( elem ) ) {
-               return;
-       }
-
-       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;
-
-       // 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;
-       }
+function Data() {
+       // Nodes|Objects
+       this.owners = [];
+       // Data objects
+       this.cache = [];
+}
 
-       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 = core_deletedIds.pop() || jQuery.guid++;
-               } else {
-                       id = internalKey;
-               }
-       }
+Data.index = function( array, node ) {
+       return array.indexOf( node );
+};
 
-       if ( !cache[ id ] ) {
-               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;
+Data.prototype = {
+       add: function( owner ) {
+               this.owners.push( owner );
+               return (this.cache[ this.owners.length - 1 ] = {});
+       },
+       set: function( owner, data, value ) {
+               var prop,
+                               index = Data.index( this.owners, owner );
+
+               // If there is no entry for this "owner", create one inline
+               // and set the index as though an owner entry had always existed
+               if ( index === -1 ) {
+                       this.add( owner );
+                       index = this.owners.length - 1;
                }
-       }
+               // Handle: [ owner, key, value ] args
+               if ( typeof data === "string" ) {
+                       this.cache[ index ][ data ] = value;
 
-       // 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 );
+               // Handle: [ owner, { properties } ] args
                } else {
-                       cache[ id ].data = jQuery.extend( cache[ id ].data, name );
-               }
-       }
-
-       thisCache = cache[ id ];
+                       // In the case where there was actually no "owner" entry and
+                       // this.add( owner ) was called to create one, there will be
+                       // a corresponding empty plain object in the cache.
+                       if ( jQuery.isEmptyObject( this.cache[ index ] ) ) {
+                               this.cache[ index ] = data;
 
-       // 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 = {};
+                       // Otherwise, copy the properties one-by-one to the cache object
+                       } else {
+                               for ( prop in data ) {
+                                       this.cache[ index ][ prop ] = data[ prop ];
+                               }
+                       }
                }
+               return this;
+       },
+       get: function( owner, key ) {
+               var cache,
+                               index = Data.index( this.owners, owner );
+
+               // A valid cache is found, or needs to be created.
+               // New entries will be added and return the current
+               // empty data object to be used as a return reference
+               // return this.add( owner );
+               // This logic was required by expectations made of the
+               // old data system.
+               cache = index === -1 ?
+                       this.add( owner ) : this.cache[ index ];
+
+               return key === undefined ?
+                       cache : cache[ key ];
+       },
+       access: function( owner, key, value ) {
+               if ( value === undefined && (key && typeof key !== "object") ) {
+                       // Assume this is a request to read the cached data
+                       return this.get( owner, key );
+               } else {
 
-               thisCache = thisCache.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 ) {
-
-               // First Try to find as-is property data
-               ret = thisCache[ name ];
-
-               // Test for null|undefined property data
-               if ( ret == null ) {
+                       // If only an owner was specified, return the entire
+                       // cache object.
+                       if ( key === undefined ) {
+                               return this.get( owner );
+                       }
 
-                       // Try to find the camelCased property
-                       ret = thisCache[ jQuery.camelCase( name ) ];
+                       // Allow setting or extending (existing objects) with an
+                       // object of properties, or a key and val
+                       this.set( owner, key, value );
+                       return value !== undefined ? value : key;
                }
-       } else {
-               ret = thisCache;
-       }
-
-       return ret;
-}
-
-function internalRemoveData( elem, name, pvt ) {
-       if ( !jQuery.acceptData( elem ) ) {
-               return;
-       }
-
-       var thisCache, i, l,
-
-               isNode = elem.nodeType,
-
-               // 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 ( name ) {
-
-               thisCache = pvt ? cache[ id ] : cache[ id ].data;
-
-               if ( thisCache ) {
-
-                       // Support array or space separated string names for data keys
-                       if ( !jQuery.isArray( name ) ) {
-
-                               // try the string as a key before any manipulation
-                               if ( name in thisCache ) {
-                                       name = [ name ];
-                               } else {
+               // Otherwise, this is a read request.
+               return this.get( owner, key );
+       },
+       remove: function( owner, key ) {
+               var i, l, name,
+                               camel = jQuery.camelCase,
+                               index = Data.index( this.owners, owner ),
+                               cache = this.cache[ index ];
 
-                                       // split the camel cased version by spaces unless a key with the spaces exists
-                                       name = jQuery.camelCase( name );
-                                       if ( name in thisCache ) {
-                                               name = [ name ];
+               if ( key === undefined ) {
+                       cache = {};
+               } else {
+                       if ( cache ) {
+                               // Support array or space separated string of keys
+                               if ( !Array.isArray( key ) ) {
+                                       // Try the string as a key before any manipulation
+                                       //
+
+                                       if ( key in cache ) {
+                                               name = [ key ];
                                        } else {
-                                               name = name.split(" ");
+                                               // Split the camel cased version by spaces unless a key with the spaces exists
+                                               name = camel( key );
+                                               name = name in cache ?
+                                                       [ name ] : name.split(" ");
                                        }
+                               } else {
+                                       // If "name" is an array of keys...
+                                       // When data is initially created, via ("key", "val") signature,
+                                       // keys will be converted to camelCase.
+                                       // Since there is no way to tell _how_ a key was added, remove
+                                       // both plain key and camelCase key. #12786
+                                       // This will only penalize the array argument path.
+                                       name = key.concat( key.map( camel ) );
                                }
-                       } else {
-                               // If "name" is an array of keys...
-                               // When data is initially created, via ("key", "val") signature,
-                               // keys will be converted to camelCase.
-                               // Since there is no way to tell _how_ a key was added, remove
-                               // both plain key and camelCase key. #12786
-                               // This will only penalize the array argument path.
-                               name = name.concat( jQuery.map( name, jQuery.camelCase ) );
-                       }
-
-                       for ( i = 0, l = name.length; i < l; i++ ) {
-                               delete thisCache[ name[i] ];
-                       }
+                               i = 0;
+                               l = name.length;
 
-                       // 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;
+                               for ( ; i < l; i++ ) {
+                                       delete cache[ name[i] ];
+                               }
                        }
                }
-       }
+               this.cache[ index ] = cache;
+       },
+       hasData: function( owner ) {
+               var index = Data.index( this.owners, owner );
 
-       // See jQuery.data for more information
-       if ( !pvt ) {
-               delete cache[ id ].data;
+               if ( index > -1 ) {
+                       return !jQuery.isEmptyObject( this.cache[ index ] );
+               }
+               return false;
+       },
+       discard: function( owner ) {
+               var index = Data.index( this.owners, owner );
 
-               // Don't destroy the parent cache unless the internal data object
-               // had been the only thing left in it
-               if ( !isEmptyDataObject( cache[ id ] ) ) {
-                       return;
+               if ( index >= 0 ) {
+                       this.owners.splice( index, 1 );
+                       this.cache.splice( index, 1 );
                }
+               return this;
        }
+};
+
+// This will be used by remove()/cleanData() in manipulation to sever
+// remaining references to node objects. One day we'll replace the dual
+// arrays with a WeakMap and this won't be an issue.
+// (Splices the data objects out of the internal cache arrays)
+function data_discard( owner ) {
+       data_user.discard( owner );
+       data_priv.discard( owner );
+}
 
-       // 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 ];
+// These may be used throughout the jQuery core codebase
+data_user = new Data();
+data_priv = new Data();
 
-       // When all else fails, null
-       } else {
-               cache[ id ] = null;
-       }
-}
 
 jQuery.extend({
-       cache: {},
-
+       // This is no longer relevant to jQuery core, but must remain
+       // supported for the sake of jQuery 1.9.x API surface compatibility.
+       acceptData: function() {
+               return true;
+       },
        // Unique for each copy of jQuery on the page
        // Non-digits removed to match rinlinejQuery
        expando: "jQuery" + ( core_version + 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 );
+               return data_user.hasData( elem ) || data_priv.hasData( elem );
        },
 
        data: function( elem, name, data ) {
-               return internalData( elem, name, data );
+               return data_user.access( elem, name, data );
        },
 
        removeData: function( elem, name ) {
-               return internalRemoveData( elem, name );
+               return data_user.remove( elem, name );
        },
 
-       // For internal use only.
+       // TODO: Replace all calls to _data and _removeData with direct
+       // calls to
+       //
+       // data_priv.access( elem, name, data );
+       //
+       // data_priv.remove( elem, name );
+       //
        _data: function( elem, name, data ) {
-               return internalData( elem, name, data, true );
+               return data_priv.access( elem, name, data );
        },
 
        _removeData: function( elem, name ) {
-               return internalRemoveData( elem, name, true );
-       },
-
-       // A method for determining if a DOM node can handle the data expando
-       acceptData: function( elem ) {
-               // Do not set data on non-element because it will not be cleared (#8335).
-               if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
-                       return false;
-               }
-
-               var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
-
-               // nodes accept data unless otherwise specified; rejection can be conditional
-               return !noData || noData !== true && elem.getAttribute("classid") === noData;
+               return data_priv.remove( elem, name );
        }
 });
 
@@ -245,20 +209,19 @@ jQuery.fn.extend({
                // Gets all values
                if ( key === undefined ) {
                        if ( this.length ) {
-                               data = jQuery.data( elem );
+                               data = data_user.get( elem );
 
-                               if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+                               if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
                                        attrs = elem.attributes;
                                        for ( ; i < attrs.length; i++ ) {
                                                name = attrs[i].name;
 
-                                               if ( !name.indexOf( "data-" ) ) {
-                                                       name = jQuery.camelCase( name.slice(5) );
-
+                                               if ( name.indexOf( "data-" ) === 0 ) {
+                                                       name = jQuery.camelCase( name.substring(5) );
                                                        dataAttr( elem, name, data[ name ] );
                                                }
                                        }
-                                       jQuery._data( elem, "parsedAttrs", true );
+                                       data_priv.set( elem, "hasDataAttrs", true );
                                }
                        }
 
@@ -268,53 +231,94 @@ jQuery.fn.extend({
                // Sets multiple values
                if ( typeof key === "object" ) {
                        return this.each(function() {
-                               jQuery.data( this, key );
+                               data_user.set( this, key );
                        });
                }
 
                return jQuery.access( this, function( value ) {
+                       var data,
+                                       camelKey = jQuery.camelCase( key );
 
+                       // Get the Data...
                        if ( value === undefined ) {
-                               // Try to fetch any internally stored data first
-                               return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
+
+                               // Attempt to get data from the cache
+                               // with the key as-is
+                               data = data_user.get( elem, key );
+                               if ( data !== undefined ) {
+                                       return data;
+                               }
+
+                               // Attempt to "discover" the data in
+                               // HTML5 custom data-* attrs
+                               data = dataAttr( elem, key, undefined );
+                               if ( data !== undefined ) {
+                                       return data;
+                               }
+
+                               // As a last resort, attempt to find
+                               // the data by checking AGAIN, but with
+                               // a camelCased key.
+                               data = data_user.get( elem, camelKey );
+                               if ( data !== undefined ) {
+                                       return data;
+                               }
+
+                               // We tried really hard, but the data doesn't exist.
+                               return undefined;
                        }
 
+                       // Set the data...
                        this.each(function() {
-                               jQuery.data( this, key, value );
+                               // First, attempt to store a copy or reference of any
+                               // data that might've been store with a camelCased key.
+                               var data = data_user.get( this, camelKey );
+
+                               // For HTML5 data-* attribute interop, we have to
+                               // store property names with dashes in a camelCase form.
+                               // This might not apply to all properties...*
+                               data_user.set( this, camelKey, value );
+
+                               // *... In the case of properties that might ACTUALLY
+                               // have dashes, we need to also store a copy of that
+                               // unchanged property.
+                               if ( /-/.test( key ) && data !== undefined ) {
+                                       data_user.set( this, key, value );
+                               }
                        });
                }, null, value, arguments.length > 1, null, true );
        },
 
        removeData: function( key ) {
                return this.each(function() {
-                       jQuery.removeData( this, key );
+                       data_user.remove( this, key );
                });
        }
 });
 
 function dataAttr( elem, key, data ) {
+       var name;
+
        // If nothing was found internally, try to fetch any
        // data from the HTML5 data-* attribute
        if ( data === undefined && elem.nodeType === 1 ) {
 
-               var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
-
+               name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
                data = elem.getAttribute( name );
 
                if ( typeof data === "string" ) {
                        try {
                                data = data === "true" ? true :
-                                       data === "false" ? false :
-                                       data === "null" ? null :
-                                       // Only convert to a number if it doesn't change the string
-                                       +data + "" === data ? +data :
-                                       rbrace.test( data ) ? jQuery.parseJSON( data ) :
-                                               data;
+                               data === "false" ? false :
+                               data === "null" ? null :
+                               // Only convert to a number if it doesn't change the string
+                               +data + "" === data ? +data :
+                               rbrace.test( data ) ?
+                                       JSON.parse( data ) : data;
                        } catch( e ) {}
 
                        // Make sure we set the data so it isn't changed later
-                       jQuery.data( elem, key, data );
-
+                       data_user.set( elem, key, data );
                } else {
                        data = undefined;
                }
@@ -322,20 +326,3 @@ function dataAttr( elem, key, data ) {
 
        return data;
 }
-
-// checks a cache object for emptiness
-function isEmptyDataObject( obj ) {
-       var name;
-       for ( name in obj ) {
-
-               // if the public data object is empty, the private is still empty
-               if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
-                       continue;
-               }
-               if ( name !== "toJSON" ) {
-                       return false;
-               }
-       }
-
-       return true;
-}
index 4d7d6a895d31c7be01182433ae361b982f9a775c..4266a1ddbb66a02cecebf5a187a118a8dbda8d9c 100644 (file)
@@ -24,7 +24,7 @@ jQuery.event = {
                var handleObjIn, eventHandle, tmp,
                        events, t, handleObj,
                        special, handlers, type, namespaces, origType,
-                       elemData = jQuery._data( elem );
+                       elemData = data_priv.get( elem );
 
                // Don't attach events to noData or text/comment nodes (but allow plain objects)
                if ( !elemData ) {
@@ -130,7 +130,7 @@ jQuery.event = {
                var j, origCount, tmp,
                        events, t, handleObj,
                        special, handlers, type, namespaces, origType,
-                       elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+                       elemData = data_priv.hasData( elem ) && data_priv.get( elem );
 
                if ( !elemData || !(events = elemData.events) ) {
                        return;
index 708d7851dd2cfe40eba3b3a8a10c4f25e77266c1..d50ebddf7c5398732ea5a21a7cffdea48fd7106a 100644 (file)
@@ -328,7 +328,7 @@ jQuery.fn.extend({
                                        for ( i = 0; i < hasScripts; i++ ) {
                                                node = scripts[ i ];
                                                if ( rscriptType.test( node.type || "" ) &&
-                                                       !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+                                                       !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
 
                                                        if ( node.src ) {
                                                                // Hope ajax is available...
@@ -511,11 +511,9 @@ jQuery.extend({
        },
 
        cleanData: function( elems, /* internal */ acceptData ) {
-               var id, data, elem, type,
+               var data, elem, type,
                        l = elems.length,
                        i = 0,
-                       internalKey = jQuery.expando,
-                       cache = jQuery.cache,
                        special = jQuery.event.special;
 
                for ( ; i < l; i++ ) {
@@ -523,8 +521,7 @@ jQuery.extend({
 
                        if ( acceptData || jQuery.acceptData( elem ) ) {
 
-                               id = elem[ internalKey ];
-                               data = id && cache[ id ];
+                               data = data_priv.access( elem );
 
                                if ( data ) {
                                        for ( type in data.events ) {
@@ -536,14 +533,10 @@ jQuery.extend({
                                                        jQuery.removeEvent( elem, type, data.handle );
                                                }
                                        }
-
-                                       // Remove cache only if it was not already removed by jQuery.event.remove
-                                       if ( cache[ id ] ) {
-                                               delete cache[ id ];
-                                               delete elem[ internalKey ];
-                                       }
                                }
                        }
+                       // Discard any remaining `private` and `user` data
+                       data_discard( elem );
                }
        }
 });
@@ -576,38 +569,49 @@ function setGlobalEval( elems, refElements ) {
                i = 0;
 
        for ( ; i < l; i++ ) {
-               jQuery._data( elems[ i ], "globalEval", !refElements || jQuery._data( refElements[ i ], "globalEval" ) );
+               data_priv.set(
+                       elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
+               );
        }
 }
 
 function cloneCopyEvent( src, dest ) {
+       var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
 
-       if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+       if ( dest.nodeType !== 1 ) {
                return;
        }
 
-       var i, l, type,
-               oldData = jQuery._data( src ),
-               curData = jQuery._data( dest, oldData ),
-               events = oldData.events;
+       // 1. Copy private data: events, handlers, etc.
+       if ( data_priv.hasData( src ) ) {
+               pdataOld = data_priv.access( src );
+               pdataCur = jQuery.extend( {}, pdataOld );
+               events = pdataOld.events;
+
+               data_priv.set( dest, pdataCur );
 
-       if ( events ) {
-               delete curData.handle;
-               curData.events = {};
+               if ( events ) {
+                       delete pdataCur.handle;
+                       pdataCur.events = {};
 
-               for ( type in events ) {
-                       for ( i = 0, l = events[ type ].length; i < l; i++ ) {
-                               jQuery.event.add( dest, type, events[ type ][ i ] );
+                       for ( type in events ) {
+                               for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+                                       jQuery.event.add( dest, type, events[ type ][ i ] );
+                               }
                        }
                }
        }
 
-       // make the cloned public data object a copy from the original
-       if ( curData.data ) {
-               curData.data = jQuery.extend( {}, curData.data );
+       // 2. Copy user data
+       if ( data_user.hasData( src ) ) {
+               udataOld = data_user.access( src );
+               udataCur = jQuery.extend( {}, udataOld );
+
+               data_user.set( dest, udataCur );
        }
 }
 
+
 function getAll( context, tag ) {
        var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
                        context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
diff --git a/test/data/onbeforeunload.html b/test/data/onbeforeunload.html
new file mode 100644 (file)
index 0000000..62c424e
--- /dev/null
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+       <script src="/dist/jquery.js"></script>
+       <script>
+               function report( event ) {
+                       var payload = {
+                               event: event.type
+                       };
+                       return top.postMessage( JSON.stringify(payload), "*" );
+               }
+
+               jQuery( window ).on( "beforeunload", function( event ) {
+                       report( event );
+               }).on( "load", function( event ) {
+                       setTimeout(function() {
+                               window.location.reload();
+                       }, 50);
+               });
+       </script>
+</html>
index f7aca724a4d4d88805474b0be2c19d61dfb5190e..aa7c1301967657db9340e85efd5dc6be7630b7cd 100644 (file)
@@ -200,47 +200,53 @@ var Globals = (function() {
         */
        QUnit.expectJqData = function( elems, key ) {
                var i, elem, expando;
-               QUnit.current_testEnvironment.checkJqData = true;
 
-               if ( elems.jquery && elems.toArray ) {
-                       elems = elems.toArray();
-               }
-               if ( !jQuery.isArray( elems ) ) {
-                       elems = [ elems ];
-               }
+               // As of jQuery 2.0, there will be no "cache"-data is
+               // stored and managed completely below the API surface
+               if ( jQuery.cache ) {
+                       QUnit.current_testEnvironment.checkJqData = true;
 
-               for ( i = 0; i < elems.length; i++ ) {
-                       elem = elems[i];
-
-                       // jQuery.data only stores data for nodes in jQuery.cache,
-                       // for other data targets the data is stored in the object itself,
-                       // in that case we can't test that target for memory leaks.
-                       // But we don't have to since in that case the data will/must will
-                       // be available as long as the object is not garbage collected by
-                       // the js engine, and when it is, the data will be removed with it.
-                       if ( !elem.nodeType ) {
-                               // Fixes false positives for dataTests(window), dataTests({}).
-                               continue;
+                       if ( elems.jquery && elems.toArray ) {
+                               elems = elems.toArray();
+                       }
+                       if ( !jQuery.isArray( elems ) ) {
+                               elems = [ elems ];
                        }
 
-                       expando = elem[ jQuery.expando ];
-
-                       if ( expando === undefined ) {
-                               // In this case the element exists fine, but
-                               // jQuery.data (or internal data) was never (in)directly
-                               // called.
-                               // Since this method was called it means some data was
-                               // expected to be found, but since there is nothing, fail early
-                               // (instead of in teardown).
-                               notStrictEqual( expando, undefined, "Target for expectJqData must have an expando, for else there can be no data to expect." );
-                       } else {
-                               if ( expectedDataKeys[expando] ) {
-                                       expectedDataKeys[expando].push( key );
+                       for ( i = 0; i < elems.length; i++ ) {
+                               elem = elems[i];
+
+                               // jQuery.data only stores data for nodes in jQuery.cache,
+                               // for other data targets the data is stored in the object itself,
+                               // in that case we can't test that target for memory leaks.
+                               // But we don't have to since in that case the data will/must will
+                               // be available as long as the object is not garbage collected by
+                               // the js engine, and when it is, the data will be removed with it.
+                               if ( !elem.nodeType ) {
+                                       // Fixes false positives for dataTests(window), dataTests({}).
+                                       continue;
+                               }
+
+                               expando = elem[ jQuery.expando ];
+
+                               if ( expando === undefined ) {
+                                       // In this case the element exists fine, but
+                                       // jQuery.data (or internal data) was never (in)directly
+                                       // called.
+                                       // Since this method was called it means some data was
+                                       // expected to be found, but since there is nothing, fail early
+                                       // (instead of in teardown).
+                                       notStrictEqual( expando, undefined, "Target for expectJqData must have an expando, for else there can be no data to expect." );
                                } else {
-                                       expectedDataKeys[expando] = [ key ];
+                                       if ( expectedDataKeys[expando] ) {
+                                               expectedDataKeys[expando].push( key );
+                                       } else {
+                                               expectedDataKeys[expando] = [ key ];
+                                       }
                                }
                        }
                }
+
        };
        QUnit.config.urlConfig.push( {
                id: "jqdata",
@@ -334,7 +340,7 @@ var Globals = (function() {
                } else {
                        delete jQuery.ajaxSettings;
                }
-               
+
                // Cleanup globals
                Globals.cleanup();
 
index 34c98140be08875cb7f60484f67909ad3e287d6e..840014fd891816f95dbf53e608e37263dce9dc19 100644 (file)
@@ -6,6 +6,33 @@ test("expando", function(){
        equal(jQuery.expando !== undefined, true, "jQuery is exposing the expando");
 });
 
+test( "jQuery.data & removeData, expected returns", function() {
+       expect(2);
+
+       equal(
+               jQuery.data( document.body, "hello", "world" ), "world",
+               "jQuery.data( elem, key, value ) returns value"
+       );
+       equal(
+               jQuery.removeData( document.body, "hello" ), undefined,
+               "jQuery.removeData( elem, key, value ) returns undefined"
+       );
+
+});
+
+test( "jQuery._data & _removeData, expected returns", function() {
+       expect(2);
+
+       equal(
+               jQuery._data( document.body, "hello", "world" ), "world",
+               "jQuery.data( elem, key, value ) returns value"
+       );
+       equal(
+               jQuery._removeData( document.body, "hello" ), undefined,
+               "jQuery.removeData( elem, key, value ) returns undefined"
+       );
+});
+
 function dataTests (elem) {
        var oldCacheLength, dataObj, internalDataObj, expected, actual;
 
@@ -106,6 +133,10 @@ test("Data is not being set on comment and text nodes", function() {
        ok( !jQuery.hasData( jQuery("<span>text</span>").contents().data("foo", 0) ) );
 
 });
+/*
+// Since the new data system does not rely on exandos, limiting the type of
+// nodes that can have data is no longer necessary. jQuery.acceptData is now irrelevant
+// and should be removed from the library.
 
 test("jQuery.acceptData", function() {
        expect(9);
@@ -127,7 +158,7 @@ test("jQuery.acceptData", function() {
        ok( !jQuery.acceptData( document.createComment("") ), "comment" );
        ok( !jQuery.acceptData( document.createTextNode("") ), "text" );
 });
-
+*/
 test(".data()", function() {
        expect(5);
 
@@ -424,7 +455,7 @@ if (window.JSON && window.JSON.stringify) {
 }
 
 test("jQuery.data should follow html5 specification regarding camel casing", function() {
-       expect(10);
+       expect(12);
 
        var div = jQuery("<div id='myObject' data-w-t-f='ftw' data-big-a-little-a='bouncing-b' data-foo='a' data-foo-bar='b' data-foo-bar-baz='c'></div>")
                .prependTo("body");
@@ -445,6 +476,9 @@ test("jQuery.data should follow html5 specification regarding camel casing", fun
        equal( div.data("fooBar"), "d", "Verify updated data-* key" );
        equal( div.data("foo-bar"), "d", "Verify updated data-* key" );
 
+       equal( div.data("fooBar"), "d", "Verify updated data-* key (fooBar)" );
+       equal( div.data("foo-bar"), "d", "Verify updated data-* key (foo-bar)" );
+
        div.remove();
 });
 
index 295a4b8d4846a8cb823b8ec538b4fa3518d595cd..c72d873f01bdff934940683360ebca43f4c47094 100644 (file)
@@ -1371,75 +1371,20 @@ test("Submit event can be stopped (#11049)", function() {
 
 // Test beforeunload event only if it supported (i.e. not Opera)
 if ( window.onbeforeunload === null ) {
-       asyncTest("on(beforeunload)", 4, function() {
-               var win,
-                       forIE6 = 0,
-                       fired = false,
-                       iframe = jQuery("<iframe src='data/iframe.html' />");
-
-               iframe.appendTo("#qunit-fixture").one( "load", function() {
-                       win = this.contentWindow || this.contentDocument;
-
-                       jQuery( win ).on( "beforeunload", function() {
-                               fired = true;
-                               ok( true, "beforeunload event is fired" );
-                       });
-
-                       strictEqual( win.onbeforeunload, null, "onbeforeunload property on window object still equals null" );
-
-                       // In old Safari beforeunload event will not fire on iframes
-                       jQuery( win ).on( "unload", function() {
-                               if ( !fired ) {
-                                       ok( true, "This is suppose to be true only in old Safari" );
-                                       checker();
-                               }
-                       });
-
-                       jQuery( win ).on( "beforeunload", function() {
-
-                               // On iframe in IE6 beforeunload event will not fire if event is binded through window object,
-                               // nevertheless, test should continue
-                               window.setTimeout(function() {
-                                       if ( !forIE6 ) {
-                                               checker();
-                                       }
-                               });
-                       });
-
-                       win.onbeforeunload = function() {
-                               if ( !forIE6 ) {
-                                       forIE6++;
-                                       checker();
-                               }
-                       };
+       asyncTest("on(beforeunload)", 1, function() {
+               var iframe = jQuery(jQuery.parseHTML("<iframe src='data/onbeforeunload.html'><iframe>"));
 
-                       function checker() {
-                               ok( true, "window.onbeforeunload handler is called" );
-                               iframe = jQuery("<iframe src='data/iframe.html' />");
+               window.onmessage = function( event ) {
+                       var payload = JSON.parse( event.data );
 
-                               iframe.appendTo("#qunit-fixture").one( "load", function() {
-                                       win = iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument;
+                       ok( payload.event, "beforeunload", "beforeunload event" );
 
-                                       jQuery( win ).on( "beforeunload", function() {
-                                               strictEqual( win.onbeforeunload, null, "Event handler is fired, even when onbeforeunload property on window is nulled" );
-
-                                               start();
-                                       });
-
-                                       jQuery( win ).on( "unload", function() {
-                                               if ( !fired ) {
-                                                       jQuery( win ).trigger("beforeunload");
-                                               }
-                                       });
-
-                                       win.onbeforeunload = null;
-
-                                       win.location.reload();
-                               });
-                       }
+                       iframe.remove();
+                       window.onmessage = null;
+                       start();
+               };
 
-                       win.location.reload();
-               });
+               iframe.appendTo("#qunit-fixture");
        });
 }
 
@@ -2567,7 +2512,7 @@ test( "Namespace preserved when passed an Event (#12739)", function() {
                                e.handled = true;
                                equal( e.namespace, "bar", "namespace is bar" );
                                jQuery( e.target ).find("div").each(function() {
-                                 jQuery( this ).triggerHandler( e );
+                                       jQuery( this ).triggerHandler( e );
                                });
                        }
                })