From 7acb141ed7f2dedd950bb65acf878098640d081e Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Mon, 7 Feb 2011 10:48:38 -0600 Subject: [PATCH] Update $.data to use a function instead of an object when attaching to JS objects in order to hide attached metadata from JSON.stringify. Remove event.js code that was doing this before specifically for events, which is now redundant. Fixes #8108. 1.5-stable --- src/data.js | 6 ++++-- src/event.js | 56 +++++++++++----------------------------------------- test/unit/data.js | 23 +++++++++++++++++---- test/unit/event.js | 5 ++--- 4 files changed, 37 insertions(+), 53 deletions(-) diff --git a/src/data.js b/src/data.js index 21f0e3a..faa44f3 100644 --- a/src/data.js +++ b/src/data.js @@ -63,12 +63,14 @@ jQuery.extend({ } if ( !cache[ id ] ) { - cache[ id ] = {}; + // Use a Function as the cache object instead of an Object on JS objects + // as a hack to prevent JSON.stringify from serializing it (#8108) + cache[ id ] = isNode ? {} : function () {}; } // 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 ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); } else { diff --git a/src/event.js b/src/event.js index 2d53562..f3a5d9f 100644 --- a/src/event.js +++ b/src/event.js @@ -7,8 +7,7 @@ var rnamespaces = /\.(.*)$/, rescape = /[^\w\s.|`]/g, fcleanup = function( nm ) { return nm.replace(rescape, "\\$&"); - }, - eventKey = "events"; + }; /* * A number of helper functions used for managing events. @@ -58,23 +57,10 @@ jQuery.event = { return; } - var events = elemData[ eventKey ], + var events = elemData.events, eventHandle = elemData.handle; - if ( typeof events === "function" ) { - // On plain objects events is a fn that holds the the data - // which prevents this data from being JSON serialized - // the function does not need to be called, it just contains the data - eventHandle = events.handle; - events = events.events; - - } else if ( !events ) { - if ( !elem.nodeType ) { - // On plain objects, create a fn that acts as the holder - // of the values to avoid JSON serialization of event data - elemData[ eventKey ] = elemData = function(){}; - } - + if ( !events ) { elemData.events = events = {}; } @@ -175,17 +161,12 @@ jQuery.event = { var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - events = elemData && elemData[ eventKey ]; + events = elemData && elemData.events; if ( !elemData || !events ) { return; } - if ( typeof events === "function" ) { - elemData = events; - events = events.events; - } - // types is actually an event object here if ( types && types.type ) { handler = types.handler; @@ -285,10 +266,7 @@ jQuery.event = { delete elemData.events; delete elemData.handle; - if ( typeof elemData === "function" ) { - jQuery.removeData( elem, eventKey, true ); - - } else if ( jQuery.isEmptyObject( elemData ) ) { + if ( jQuery.isEmptyObject( elemData ) ) { jQuery.removeData( elem, undefined, true ); } } @@ -329,7 +307,7 @@ jQuery.event = { // points to jQuery.expando var internalKey = jQuery.expando, internalCache = this[ internalKey ]; - if ( internalCache && internalCache.events && internalCache.events[type] ) { + if ( internalCache && internalCache.events && internalCache.events[ type ] ) { jQuery.event.trigger( event, data, internalCache.handle.elem ); } }); @@ -355,9 +333,7 @@ jQuery.event = { event.currentTarget = elem; // Trigger the event, it is assumed that "handle" is a function - var handle = elem.nodeType ? - jQuery._data( elem, "handle" ) : - (jQuery._data( elem, eventKey ) || {}).handle; + var handle = jQuery._data( elem, "handle" ); if ( handle ) { handle.apply( elem, data ); @@ -435,11 +411,7 @@ jQuery.event = { event.namespace = event.namespace || namespace_sort.join("."); - events = jQuery._data(this, eventKey); - - if ( typeof events === "function" ) { - events = events.events; - } + events = jQuery._data(this, "events"); handlers = (events || {})[ event.type ]; @@ -606,7 +578,7 @@ jQuery.Event = function( src ) { // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // Event type @@ -880,8 +852,8 @@ if ( document.addEventListener ) { jQuery.event.special[ fix ] = { setup: function() { this.addEventListener( orig, handler, true ); - }, - teardown: function() { + }, + teardown: function() { this.removeEventListener( orig, handler, true ); } }; @@ -1074,11 +1046,7 @@ function liveHandler( event ) { var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, elems = [], selectors = [], - events = jQuery._data( this, eventKey ); - - if ( typeof events === "function" ) { - events = events.events; - } + events = jQuery._data( this, "events" ); // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { diff --git a/test/unit/data.js b/test/unit/data.js index 889fc2d..1ce512c 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -22,7 +22,7 @@ function dataTests (elem) { 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" ); + equals( typeof dataObj, elem.nodeType ? "object" : "function", "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" ); @@ -187,8 +187,12 @@ test(".data()", function() { equals( nodiv.data(), null, "data() on empty set returns null" ); var obj = { foo: "bar" }; - deepEqual( jQuery(obj).data(), {}, "Retrieve data object from a wrapped JS object (#7524)" ); -}) + jQuery(obj).data("foo", "baz"); + + var dataObj = jQuery.extend(true, {}, jQuery(obj).data()); + + deepEqual( dataObj, { foo: "baz" }, "Retrieve data object from a wrapped JS object (#7524)" ); +}); test(".data(String) and .data(String, Object)", function() { expect(29); @@ -461,4 +465,15 @@ test(".removeData()", function() { div.removeData("test.foo"); equals( div.data("test.foo"), undefined, "Make sure data is intact" ); -}); \ No newline at end of file +}); + +if (window.JSON && window.JSON.stringify) { + test("JSON serialization (#8108)", function () { + expect(1); + + var obj = { foo: "bar" }; + jQuery.data(obj, "hidden", true); + + equals( JSON.stringify(obj), '{"foo":"bar"}', "Expando is hidden from JSON.stringify" ); + }); +} \ No newline at end of file diff --git a/test/unit/event.js b/test/unit/event.js index e4caee8..045ea73 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -312,7 +312,7 @@ test("bind/delegate bubbling, isDefaultPrevented", function() { // Use a native click so we don't get jQuery simulated bubbling if ( document.createEvent ) { var e = document.createEvent( 'MouseEvents' ); - e.initEvent( "click", true, true ); + e.initEvent( "click", true, true ); $jq[0].dispatchEvent(e); } else if ( $jq[0].click ) { @@ -548,7 +548,7 @@ test("bind(name, false), unbind(name, false)", function() { }); test("bind()/trigger()/unbind() on plain object", function() { - expect( 8 ); + expect( 7 ); var obj = {}; @@ -570,7 +570,6 @@ test("bind()/trigger()/unbind() on plain object", function() { var events = jQuery._data(obj, "events"); ok( events, "Object has events bound." ); 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( obj.test, undefined, "Make sure that test event is not on the plain object." ); equals( obj.handle, undefined, "Make sure that the event handler is not on the plain object." ); -- 1.7.10.4