From 50170e618059d10132a5319c64660a631b095f44 Mon Sep 17 00:00:00 2001 From: Russell Holbrook Date: Mon, 22 Nov 2010 18:26:46 -0500 Subject: jQuery.fn.offset no longer returns ClientRect object for disconnected elements Instead of returning box, which is a ClientRect, we take the top and left box values and place them into a generic object. --- src/offset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/offset.js b/src/offset.js index 3fb2917b2..dab053e00 100644 --- a/src/offset.js +++ b/src/offset.js @@ -30,7 +30,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { // Make sure we're not dealing with a disconnected DOM node if ( !box || !jQuery.contains( docElem, elem ) ) { - return box || { top: 0, left: 0 }; + return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; } var body = doc.body, -- cgit v1.2.3 From 0b6afcedd22aaffb96d3d45b9b220a16229e2f7c Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Thu, 23 Dec 2010 16:21:14 -0500 Subject: When a native browser event is bubbling up the DOM, make sure that the correct isDefaultPrevented value is reflected by jQuery's Event object. Fixes #7793. --- src/event.js | 6 ++++++ test/unit/event.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'src') diff --git a/src/event.js b/src/event.js index fd470e718..80e2af66b 100644 --- a/src/event.js +++ b/src/event.js @@ -600,6 +600,12 @@ jQuery.Event = function( src ) { if ( src && src.type ) { this.originalEvent = src; this.type = src.type; + // 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===true ? true : + src.getPreventDefault ? src.getPreventDefault() : + src.returnValue===false) ? returnTrue : returnFalse; // Event type } else { this.type = src; diff --git a/test/unit/event.js b/test/unit/event.js index a647e5f3b..d0183f89d 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -295,6 +295,44 @@ test("live/delegate immediate propagation", function() { $p.undelegate( "click" ); }); +test("bind/delegate bubbling, isDefaultPrevented", function() { + expect(2); + var $anchor2 = jQuery( "#anchor2" ), + $main = jQuery( "#main" ), + fakeClick = function($jq) { + // Prefer a native click so we don't get jQuery simulated bubbling + if ( $jq[0].click ) { + $jq[0].click(); // IE + } + else if ( document.createEvent ) { + var e = document.createEvent( 'MouseEvents' ); + e.initEvent( "click", true, true ); + $jq[0].dispatchEvent(e); + } + else { + $jq.click(); + } + }; + $anchor2.click(function(e) { + e.preventDefault(); + }); + $main.delegate("#foo", "click", function(e) { + equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + }); + fakeClick( $anchor2 ); + $anchor2.unbind( "click" ); + $main.undelegate( "click" ); + $anchor2.click(function(e) { + // Let the default action occur + }); + $main.delegate("#foo", "click", function(e) { + equals( e.isDefaultPrevented(), false, "isDefaultPrevented false passed to bubbled event" ); + }); + fakeClick( $anchor2 ); + $anchor2.unbind( "click" ); + $main.undelegate( "click" ); +}); + test("bind(), iframes", function() { // events don't work with iframes, see #939 - this test fails in IE because of contentDocument var doc = jQuery("#loadediframe").contents(); -- cgit v1.2.3 From c9e8a95709e12c6838a312850ce645e96a53ff5d Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Fri, 24 Dec 2010 09:53:39 -0500 Subject: Simplify the check for isDefaultPrevented. --- src/event.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/event.js b/src/event.js index 80e2af66b..b61b11e64 100644 --- a/src/event.js +++ b/src/event.js @@ -602,10 +602,8 @@ jQuery.Event = function( src ) { this.type = src.type; // 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===true ? true : - src.getPreventDefault ? src.getPreventDefault() : - src.returnValue===false) ? returnTrue : returnFalse; + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // Event type } else { this.type = src; -- cgit v1.2.3 From 8099cdce800d45109b961e3521fc0080e9a876c6 Mon Sep 17 00:00:00 2001 From: rwldrn Date: Wed, 5 Jan 2011 13:32:59 -0500 Subject: Bug #7608 elem.runtimeStyle throws exception in Opera --- src/css.js | 12 ++++++++---- test/unit/css.js | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/css.js b/src/css.js index 8a83c6072..19c6342d2 100644 --- a/src/css.js +++ b/src/css.js @@ -263,8 +263,9 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, rsLeft, + var left, ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; // From the awesome hack by Dean Edwards @@ -275,16 +276,19 @@ if ( document.documentElement.currentStyle ) { if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; - rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; - elem.runtimeStyle.left = rsLeft; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } } return ret === "" ? "auto" : ret; diff --git a/test/unit/css.js b/test/unit/css.js index fbbf937ca..edc340ceb 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -320,3 +320,25 @@ test(":visible selector works properly on children with a hidden parent (bug #45 jQuery('#table').css('display', 'none').html('cellcell'); equals(jQuery('#table td:visible').length, 0, "hidden cell children not perceived as visible"); }); + +test("internal ref to elem.runtimeStyle (bug #7608)", function () { + expect(1); + + var result = true, + val = 10; + + jQuery('
' + + '
 
').appendTo("body"); + + try { + // the bug is located within src/css.js + jQuery("#bug7608 #test").animate( { width: val }, 1000); + + } catch (e) { + result = false; + } + + ok( result, "elem.runtimeStyle does not throw exception" ); + + jQuery("#bug7608").remove(); +}); -- cgit v1.2.3 From 8e59a99e0ade75dec434f246f52e8b3f7393f359 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 9 Jan 2011 15:52:33 -0600 Subject: Change the way jQuery.data works so that there is no longer a chance of collision between user data and internal data. Fixes #6968. --- src/attributes.js | 4 +- src/data.js | 162 +++++++++++++++++++++----------- src/effects.js | 10 +- src/event.js | 46 +++++----- src/manipulation.js | 35 ++++--- src/queue.js | 4 +- test/unit/attributes.js | 10 +- test/unit/data.js | 238 ++++++++++++++++++++++++++++++++---------------- test/unit/effects.js | 4 +- test/unit/event.js | 20 ++-- 10 files changed, 341 insertions(+), 192 deletions(-) (limited to 'src') 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..a1abc9ed6 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,163 @@ 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; + 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, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : elem, - cache = jQuery.cache, - thisCache = isNode ? cache[ id ] : id; + // 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 675e5fff3..7fa5488ee 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 96caa02d0..657aef7d1 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -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; - - 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 ); + var internalKey = jQuery.expando, + oldData = jQuery.data( orig[nodeIndex] ), + 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 ) { + 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 ); + } } } } @@ -594,8 +601,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++ ) { @@ -606,13 +612,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..5fb04df80 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 ); diff --git a/test/unit/attributes.js b/test/unit/attributes.js index a1ab58179..04f168466 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -703,12 +703,12 @@ var testToggleClass = function(valueObj) { // toggleClass storage 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"); ok( e.is(".testD.testE"), "Assert class present" ); e.toggleClass(); 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(); ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); e.toggleClass(false); @@ -720,11 +720,9 @@ var testToggleClass = function(valueObj) { e.toggleClass(); ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); - - // Cleanup e.removeClass("testD"); - e.removeData('__className__'); + jQuery.removeData(e[0], '__className__', true); }; test("toggleClass(String|boolean|undefined[, boolean])", function() { @@ -785,7 +783,7 @@ test("toggleClass(Fucntion[, boolean]) with incoming value", function() { // Cleanup e.removeClass("test"); - e.removeData('__className__'); + jQuery.removeData(e[0], '__className__', true); }); test("addClass, removeClass, hasClass", function() { diff --git a/test/unit/data.js b/test/unit/data.js index 310cd6bc4..28e19a3e2 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -1,96 +1,177 @@ -module("data"); +module("data", { teardown: moduleTeardown }); test("expando", function(){ - expect(6); + expect(1); equals("expando" in jQuery, true, "jQuery is exposing the expando"); +}); - var obj = {}; - equals( jQuery.data(obj), obj, "jQuery.data(obj) returns the object"); - equals( jQuery.expando in obj, false, "jQuery.data(obj) did not add an expando to the object" ); +function dataTests (elem) { + // expect(32) - obj = {}; - jQuery.data(obj, 'test'); - equals( jQuery.expando in obj, false, "jQuery.data(obj,key) did not add an expando to the object" ); + function getCacheLength() { + var cacheLength = 0; + for (var i in jQuery.cache) { + ++cacheLength; + } - obj = {}; - 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." ); -}); + return cacheLength; + } -test("jQuery.acceptData", function() { - expect(7); + equals( jQuery.data(elem, "foo"), undefined, "No data exists initially" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists initially" ); - ok( jQuery.acceptData( document ), "document" ); - ok( jQuery.acceptData( document.documentElement ), "documentElement" ); - ok( jQuery.acceptData( {} ), "object" ); - ok( !jQuery.acceptData( document.createElement("embed") ), "embed" ); - ok( !jQuery.acceptData( document.createElement("applet") ), "applet" ); + 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" ); - var flash = document.createElement("object"); - flash.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"); - ok( jQuery.acceptData( flash ), "flash" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists even when an empty data obj exists" ); - var applet = document.createElement("object"); - applet.setAttribute("classid", "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"); - ok( !jQuery.acceptData( applet ), "applet" ); -}); + dataObj.foo = "bar"; + equals( jQuery.data(elem, "foo"), "bar", "Data is readable by jQuery.data when set directly on a returned data object" ); -test("jQuery.data", function() { - expect(15); - var div = document.createElement("div"); + strictEqual( jQuery.hasData(elem), true, "jQuery.hasData agrees data exists when data exists" ); - ok( jQuery.data(div, "test") === undefined, "Check for no 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(div, "test", "success"); - equals( jQuery.data(div, "test"), "success", "Check for added data" ); + jQuery.data(elem, "foo", undefined); + equals( jQuery.data(elem, "foo"), "baz", "Data is not unset by passing undefined to jQuery.data" ); - ok( jQuery.data(div, "notexist") === undefined, "Check for no data exists" ); + jQuery.data(elem, "foo", null); + strictEqual( jQuery.data(elem, "foo"), null, "Setting null using jQuery.data works OK" ); - var data = jQuery.data(div); - same( data, { "test": "success" }, "Return complete data set" ); + jQuery.data(elem, "foo", "foo1"); - jQuery.data(div, "test", "overwritten"); - equals( jQuery.data(div, "test"), "overwritten", "Check for overwritten data" ); + 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(div, "test", undefined); - equals( jQuery.data(div, "test"), "overwritten", "Check that data wasn't removed"); + 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" ); - jQuery.data(div, "test", null); - ok( jQuery.data(div, "test") === null, "Check for null 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" ); - 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" ); + strictEqual( elem.boom, undefined, "Data is never stored directly on the object" ); - var obj = {}; - jQuery.data( obj, "prop", true ); + jQuery.removeData(elem, "foo"); + strictEqual( jQuery.data(elem, "foo"), undefined, "jQuery.removeData removes single properties" ); - ok( obj.prop, "Data is being stored on the object" ); - equals( jQuery.data( obj, "prop" ), true, "Make sure the right value is retrieved" ); + jQuery.removeData(elem); + strictEqual( jQuery.data(elem, jQuery.expando), internalDataObj, "jQuery.removeData does not remove internal data if it exists" ); - 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." ); -}); + jQuery.removeData(elem, undefined, true); -test("jQuery.hasData", function() { - expect(6); + 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"); - 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" ); + 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" ); - testData(document.createElement('div')); - testData({}); + 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() { + expect(7); + + ok( jQuery.acceptData( document ), "document" ); + ok( jQuery.acceptData( document.documentElement ), "documentElement" ); + ok( jQuery.acceptData( {} ), "object" ); + ok( !jQuery.acceptData( document.createElement("embed") ), "embed" ); + ok( !jQuery.acceptData( document.createElement("applet") ), "applet" ); + + var flash = document.createElement("object"); + flash.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"); + ok( jQuery.acceptData( flash ), "flash" ); + + var applet = document.createElement("object"); + applet.setAttribute("classid", "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"); + ok( !jQuery.acceptData( applet ), "applet" ); }); test(".data()", function() { @@ -98,7 +179,6 @@ test(".data()", function() { var div = jQuery("#foo"); strictEqual( div.data("foo"), undefined, "Make sure that missing result is undefined" ); - div.data("test", "success"); same( div.data(), {test: "success"}, "data() get the entire data object" ); 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" ); 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() { @@ -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('emptyString','').data('emptyString'), '', "Empty strings 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 $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() { @@ -323,13 +406,17 @@ test(".data(Object)", function() { var obj = {test:"unset"}, jqobj = jQuery(obj); + jqobj.data("test", "unset"); jqobj.data({ "test": "in", "test2": "in2" }); - equals( obj.test, "in", "Verify setting an object on an object extends the object" ); - equals( obj.test2, "in2", "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, undefined, "Verify setting an object on an object does not extend the object" ); + + // manually clean up detached elements + div.remove(); }); test("jQuery.removeData", function() { - expect(7); + expect(6); var div = jQuery("#foo")[0]; jQuery.data(div, "test", "testing"); jQuery.removeData(div, "test"); @@ -342,10 +429,9 @@ test("jQuery.removeData", function() { var obj = {}; 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"); 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.removeData( window, "BAD" ); @@ -371,4 +457,4 @@ test(".removeData()", function() { div.removeData("test.foo"); equals( div.data("test.foo"), undefined, "Make sure data is intact" ); -}); +}); \ No newline at end of file diff --git a/test/unit/effects.js b/test/unit/effects.js index b7b60abbe..cf9d13109 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -895,7 +895,7 @@ test("hide hidden elements (bug #7141)", function() { var div = jQuery("
").appendTo("#main"); equals( div.css("display"), "none", "Element is hidden by default" ); 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(); 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("
").appendTo("#main"); equals( div.css("display"), "none", "Element is hidden by default" ); 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 () { equals( div.css("display"), "block", "Show a double-hidden element" ); start(); diff --git a/test/unit/event.js b/test/unit/event.js index b4672a8b8..cae1a83fe 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -28,7 +28,7 @@ test("bind(), with data", function() { }; 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() { @@ -39,7 +39,7 @@ test("click(), with data", function() { }; 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() { @@ -505,7 +505,7 @@ test("bind(), with different this object", function() { .bind("click", jQuery.proxy(handler1, thisObject)).click().unbind("click", handler1) .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() { @@ -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." ); equals( obj.events, undefined, "Events object on plain objects is not events" ); 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 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() { @@ -947,7 +949,7 @@ test("toggle(Function, Function, ...)", function() { equals( turn, 2, "Trying toggle with 3 functions, attempt 5 yields 2"); $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"); // Test Multi-Toggles @@ -1065,7 +1067,7 @@ test(".live()/.die()", function() { equals( clicked, 2, "live with a 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 jQuery("#foo", container).die("click"); @@ -1578,7 +1580,7 @@ test(".delegate()/.undelegate()", function() { equals( clicked, 2, "delegate with a 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 jQuery("#main").undelegate("#foo", "click"); @@ -1907,7 +1909,7 @@ test("window resize", function() { ok( true, "Resize event fired." ); }).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() { -- cgit v1.2.3 From 885d06c8ef906fa11d130d7d567c871d20ef9ba9 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 9 Jan 2011 15:56:40 -0600 Subject: Fix domManip leaks the first element when appending elements to multiple other elements. --- src/manipulation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/manipulation.js b/src/manipulation.js index 657aef7d1..206476c2d 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 ); -- cgit v1.2.3 From 80af46e8ffe8292e0af0537db6c7e89019e5edba Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 9 Jan 2011 15:58:23 -0600 Subject: Fix jQuery.queue leaks empty queues. --- src/queue.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/queue.js b/src/queue.js index 5fb04df80..9e3e2fb52 100644 --- a/src/queue.js +++ b/src/queue.js @@ -46,6 +46,10 @@ jQuery.extend({ jQuery.dequeue(elem, type); }); } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + } } }); -- cgit v1.2.3 From e5ee89ec9a11804b39e9722f47eeeb00648f37ea Mon Sep 17 00:00:00 2001 From: Scott González Date: Fri, 14 Jan 2011 09:55:40 -0500 Subject: Avoid running jQuery.unique() for methods that are guaranteed to produce a unique result set. Fixes #7964 - Some traversal methods perform an unnecessary uniqueness check. --- src/traversing.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/traversing.js b/src/traversing.js index 689e90196..e169be084 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -6,7 +6,14 @@ var runtil = /Until$/, rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, - POS = jQuery.expr.match.POS; + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; jQuery.fn.extend({ find: function( selector ) { @@ -206,7 +213,7 @@ jQuery.each({ ret = jQuery.filter( selector, ret ); } - ret = this.length > 1 ? jQuery.unique( ret ) : ret; + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); -- cgit v1.2.3 From d483ce0a9c8b19dafa70be93ca071673b86a65e2 Mon Sep 17 00:00:00 2001 From: Jared Grippe Date: Sat, 9 Oct 2010 17:32:54 -0700 Subject: added jQuery.subclass --- src/core.js | 31 ++++++++++++++++++++---- test/unit/core.js | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/core.js b/src/core.js index 4361577e2..0bf0cb4b7 100644 --- a/src/core.js +++ b/src/core.js @@ -3,7 +3,7 @@ var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); + return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite @@ -78,7 +78,8 @@ var jQuery = function( selector, context ) { class2type = {}; jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) @@ -112,6 +113,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; doc = (context ? context.ownerDocument || context : document); // If a single string is passed in and it's a single tag @@ -171,7 +173,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { - return jQuery( context ).find( selector ); + return this.constructor( context ).find( selector ); } // HANDLE: $(function) @@ -222,7 +224,7 @@ jQuery.fn = jQuery.prototype = { // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set - var ret = jQuery(); + var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); @@ -287,7 +289,7 @@ jQuery.fn = jQuery.prototype = { }, end: function() { - return this.prevObject || jQuery(null); + return this.prevObject || this.constructor(null); }, // For internal use only. @@ -960,6 +962,25 @@ jQuery.extend({ return { browser: match[1] || "", version: match[2] || "0" }; }, + subclass: function(){ + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if (context && context instanceof jQuery && !(context instanceof jQuerySubclass)){ + context = jQuerySubclass(context); + } + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, + browser: {} }); diff --git a/test/unit/core.js b/test/unit/core.js index bfb2f1cf4..d26e7c56d 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1063,3 +1063,75 @@ test("jQuery.when()", function() { }); } }); + +test("jQuery.subclass", function(){ + expect(378); + + var Subclass = jQuery.subclass(), + SubclassSubclass = Subclass.subclass(), + jQueryDocument = jQuery(document), + selectors, contexts, methods, method, arg, description; + + jQueryDocument.toString = function(){ return 'jQueryDocument'; }; + + Subclass.fn.subclassMethod = function(){}; + SubclassSubclass.fn.subclassSubclassMethod = function(){}; + + selectors = [ + 'body', + 'html, body', + '
' + ] + + methods = [ // all methods that return a new jQuery instance + ['eq', 1], + ['add', document], + ['end'], + ['has'], + ['closest', 'div'], + ['filter', document], + ['find'] + ] + + contexts = [undefined, document, jQueryDocument]; + + jQuery.each(selectors, function(i, selector){ + + jQuery.each(methods, function(){ + method = this[0] + arg = this[1] + + jQuery.each(contexts, function(i, context){ + + description = '("'+selector+'", '+context+').'+method+'('+(arg||'')+')'; + + same( + jQuery(selector, context)[method](arg).subclassMethod, undefined, + 'jQuery'+description+' doesnt have Subclass methods' + ); + same( + jQuery(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'jQuery'+description+' doesnt have SubclassSubclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'Subclass'+description+' has Subclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'Subclass'+description+' doesnt have SubclassSubclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'SubclassSubclass'+description+' has Subclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassSubclassMethod, SubclassSubclass.fn.subclassSubclassMethod, + 'SubclassSubclass'+description+' has SubclassSubclass methods' + ); + + }); + }); + }); + +}); -- cgit v1.2.3 From 52a02383fa521c51d9996a46f03a7080dd825f11 Mon Sep 17 00:00:00 2001 From: wycats Date: Fri, 14 Jan 2011 11:21:45 -0500 Subject: Fix a strange Chrome issue --- src/traversing.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/traversing.js b/src/traversing.js index 689e90196..8187357e7 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -196,7 +196,8 @@ jQuery.each({ } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); + var ret = jQuery.map( this, fn, until ), + args = slice.call(arguments); if ( !runtil.test( name ) ) { selector = until; @@ -212,7 +213,7 @@ jQuery.each({ ret = ret.reverse(); } - return this.pushStack( ret, name, slice.call(arguments).join(",") ); + return this.pushStack( ret, name, args.join(",") ); }; }); -- cgit v1.2.3 From cf7ddcf79a3d4d455711b67b252b19ae343645b1 Mon Sep 17 00:00:00 2001 From: jeresig Date: Fri, 14 Jan 2011 14:12:29 -0500 Subject: Revert "Revert fb4445070cd9e06929c7b6f27c10dbf42d4a3367 which is no longer necessary with the release of Opera 11. Fixes #7608." We will be continuing to support Opera 10.6 in jQuery 1.5. This reverts commit 012f0c3b4bd3d04c2f3e1ea80fc1230901d607d9. --- src/css.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/css.js b/src/css.js index a6e2bb614..8a83c6072 100644 --- a/src/css.js +++ b/src/css.js @@ -12,6 +12,9 @@ var ralpha = /alpha\([^)]*\)/i, cssHeight = [ "Top", "Bottom" ], curCSS, + getComputedStyle, + currentStyle, + fcamelCase = function( all, letter ) { return letter.toUpperCase(); }; @@ -169,6 +172,10 @@ jQuery.each(["height", "width"], function( i, name ) { if ( val <= 0 ) { val = curCSS( elem, name, name ); + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + if ( val != null ) { // Should return "auto" instead of 0, use 0 for // temporary backwards-compat @@ -234,7 +241,7 @@ if ( !jQuery.support.opacity ) { } if ( document.defaultView && document.defaultView.getComputedStyle ) { - curCSS = function( elem, newName, name ) { + getComputedStyle = function( elem, newName, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); @@ -252,8 +259,10 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { return ret; }; -} else if ( document.documentElement.currentStyle ) { - curCSS = function( elem, name ) { +} + +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { var left, rsLeft, ret = elem.currentStyle && elem.currentStyle[ name ], style = elem.style; @@ -282,6 +291,8 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { }; } +curCSS = getComputedStyle || currentStyle; + function getWH( elem, name, extra ) { var which = name === "width" ? cssWidth : cssHeight, val = name === "width" ? elem.offsetWidth : elem.offsetHeight; -- cgit v1.2.3 From 8ab23aec2c333834a6e442fa15b73125ba857afe Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 02:57:39 +0100 Subject: Fixes #2994. Not finding a transport now fires the error callbacks and doesn't make ajax return false. Had to revise how jsonp and script prefilters & transports work (better separation of concerns). Also took the opportunity to revise jXHR getRequestHeader and abort methods and enabled early transport garbage collection when the request completes. --- src/ajax.js | 142 +++++++++++++++++++++++++++++------------------------ src/ajax/jsonp.js | 71 ++++++++++++--------------- src/ajax/script.js | 16 +++--- test/unit/ajax.js | 16 ++---- 4 files changed, 123 insertions(+), 122 deletions(-) (limited to 'src') diff --git a/src/ajax.js b/src/ajax.js index 645163ad2..5c4d469fd 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -306,30 +306,35 @@ jQuery.extend({ // (match is used internally) getResponseHeader: function( key , match ) { - if ( state !== 2 ) { - return null; - } + if ( state === 2 ) { - if ( responseHeaders === undefined ) { + if ( responseHeaders === undefined ) { - responseHeaders = {}; + responseHeaders = {}; - if ( typeof responseHeadersString === "string" ) { + if ( typeof responseHeadersString === "string" ) { - while( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } } } + match = responseHeaders[ key.toLowerCase() ]; + + } else { + + match = null; } - return responseHeaders[ key.toLowerCase() ]; + + return match; }, // Cancel the request abort: function( statusText ) { - if ( transport && state !== 2 ) { + if ( transport ) { transport.abort( statusText || "abort" ); - done( 0 , statusText ); } + done( 0 , statusText ); return this; } }; @@ -347,6 +352,10 @@ jQuery.extend({ // State is "done" now state = 2; + // Dereference transport for early garbage collection + // (no matter how long the jXHR transport will be used + transport = 0; + // Set readyState jXHR.readyState = status ? 4 : 0; @@ -599,84 +608,87 @@ jQuery.extend({ s.data = jQuery.param( s.data , s.traditional ); } - // Get transport - transport = jQuery.ajaxPrefilter( s , options ).ajaxTransport( s ); + // Apply prefilters + jQuery.ajaxPrefilter( s , options ); // Watch for a new set of requests if ( s.global && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } - // If no transport, we auto-abort - if ( ! transport ) { - - done( 0 , "transport not found" ); - jXHR = false; + // More options handling for requests with no content + if ( ! s.hasContent ) { - } else { + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + } - // More options handling for requests with no content - if ( ! s.hasContent ) { + // Add anti-cache in url if needed + if ( s.cache === false ) { - // If data is available, append data to url - if ( s.data ) { - s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; - } + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts , "$1_=" + ts ); - // Add anti-cache in url if needed - if ( s.cache === false ) { + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); + } + } - var ts = jQuery.now(), - // try replacing _= if it is there - ret = s.url.replace( rts , "$1_=" + ts ); + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + requestHeaders[ "content-type" ] = s.contentType; + } - // if nothing was replaced, add timestamp to the end - s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); - } + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery_lastModified[ s.url ] ) { + requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - requestHeaders[ "content-type" ] = s.contentType; + if ( jQuery_etag[ s.url ] ) { + requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; } + } - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery_lastModified[ s.url ] ) { - requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; - } - if ( jQuery_etag[ s.url ] ) { - requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; - } - } + // Set the Accepts header for the server, depending on the dataType + requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ]; + + // Check for headers option + for ( i in s.headers ) { + requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + } - // Set the Accepts header for the server, depending on the dataType - requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : - s.accepts[ "*" ]; + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { - // Check for headers option - for ( i in s.headers ) { - requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + // Abort if not done already + done( 0 , "abort" ); + + // Return false + jXHR = false; + + } else { + + // Install callbacks on deferreds + for ( i in { success:1, error:1, complete:1 } ) { + jXHR[ i ]( s[ i ] ); } - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { + // Get transport + transport = jQuery.ajaxTransport( s ); - // Abort if not done already - done( 0 , "abort" ); - jXHR = false; + // If no transport, we auto-abort + if ( ! transport ) { + + done( 0 , "notransport" ); } else { // Set state as sending - state = 1; - jXHR.readyState = 1; - - // Install callbacks on deferreds - for ( i in { success:1, error:1, complete:1 } ) { - jXHR[ i ]( s[ i ] ); - } + state = jXHR.readyState = 1; // Send global event if ( s.global ) { diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js index 1df5dd427..675ecc085 100644 --- a/src/ajax/jsonp.js +++ b/src/ajax/jsonp.js @@ -11,9 +11,7 @@ jQuery.ajaxSetup({ return "jsonp" + jsc++; } -// Normalize jsonp queries -// 1) put callback parameter in url or data -// 2) sneakily ensure transportDataType is always jsonp for jsonp requests +// Detect, normalize options and install callbacks for jsonp requests }).ajaxPrefilter("json jsonp", function(s, originalSettings) { if ( s.dataTypes[ 0 ] === "jsonp" || @@ -22,8 +20,10 @@ jQuery.ajaxSetup({ jsre.test(s.url) || typeof(s.data) === "string" && jsre.test(s.data) ) { - var jsonpCallback = s.jsonpCallback = + var responseContainer, + jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], url = s.url.replace(jsre, "$1" + jsonpCallback + "$2"), data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "$1" + jsonpCallback + "$2") : s.data; @@ -33,51 +33,42 @@ jQuery.ajaxSetup({ s.url = url; s.data = data; - s.dataTypes[ 0 ] = "jsonp"; - } - -// Bind transport to jsonp dataType -}).ajaxTransport("jsonp", function(s) { - // Put callback in place - var responseContainer, - jsonpCallback = s.jsonpCallback, - previous = window[ jsonpCallback ]; + window [ jsonpCallback ] = function( response ) { + responseContainer = [response]; + }; - window [ jsonpCallback ] = function( response ) { - responseContainer = [response]; - }; + s.complete = [function() { - s.complete = [function() { + // Set callback back to previous value + window[ jsonpCallback ] = previous; - // Set callback back to previous value - window[ jsonpCallback ] = previous; - - // Call if it was a function and we have a response - if ( previous) { - if ( responseContainer && jQuery.isFunction ( previous ) ) { - window[ jsonpCallback ] ( responseContainer[0] ); + // Call if it was a function and we have a response + if ( previous) { + if ( responseContainer && jQuery.isFunction ( previous ) ) { + window[ jsonpCallback ] ( responseContainer[0] ); + } + } else { + // else, more memory leak avoidance + try{ delete window[ jsonpCallback ]; } catch(e){} } - } else { - // else, more memory leak avoidance - try{ delete window[ jsonpCallback ]; } catch(e){} - } - }, s.complete ]; + }, s.complete ]; - // Sneakily ensure this will be handled as json - s.dataTypes[ 0 ] = "json"; + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( ! responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; - // Use data converter to retrieve json after script execution - s.converters["script json"] = function() { - if ( ! responseContainer ) { - jQuery.error( jsonpCallback + " was not called" ); - } - return responseContainer[ 0 ]; - }; + // force json dataType + s.dataTypes[ 0 ] = "json"; - // Delegate to script transport - return "script"; + // Delegate to script + return "script"; + } }); })( jQuery ); diff --git a/src/ajax/script.js b/src/ajax/script.js index 8e2e89ac5..ee1d489eb 100644 --- a/src/ajax/script.js +++ b/src/ajax/script.js @@ -15,18 +15,22 @@ jQuery.ajaxSetup({ "text script": jQuery.globalEval } -// Bind script tag hack transport -}).ajaxTransport("script", function(s) { +// Handle cache's special case and global +}).ajaxPrefilter("script", function(s) { - // Handle cache special case if ( s.cache === undefined ) { s.cache = false; } - // This transport only deals with cross domain get requests - if ( s.crossDomain && s.async && ( s.type === "GET" || ! s.data ) ) { - + if ( s.crossDomain ) { s.global = false; + } + +// Bind script tag hack transport +}).ajaxTransport("script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { var script, head = document.getElementsByTagName("head")[0] || document.documentElement; diff --git a/test/unit/ajax.js b/test/unit/ajax.js index f5b71da39..49196cbbb 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1865,25 +1865,19 @@ test("jQuery ajax - failing cross-domain", function() { var i = 2; - if ( jQuery.ajax({ + jQuery.ajax({ url: 'http://somewebsitethatdoesnotexist-67864863574657654.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }) === false ) { - ok( true , "no transport" ); - if ( ! --i ) start(); - } + }); - if ( jQuery.ajax({ + jQuery.ajax({ url: 'http://www.google.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }) === false ) { - ok( true , "no transport" ); - if ( ! --i ) start(); - } + }); }); @@ -1937,7 +1931,7 @@ test( "jQuery.ajax - statusCode" , function() { 404: function() { ok( ! isSuccess , name ); } - } + }; } jQuery.each( { -- cgit v1.2.3 From 21143c3b213843bc202c16b5532b6e9de951eb2c Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 03:05:03 +0100 Subject: Removed internal dataTypes option and added headers & crossDomain options into commented out options of ajaxSettings. --- src/ajax.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ajax.js b/src/ajax.js index 5c4d469fd..3eb36c3a8 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -175,11 +175,12 @@ jQuery.extend({ timeout: 0, data: null, dataType: null, - dataTypes: null, username: null, password: null, cache: null, traditional: false, + headers: {}, + crossDomain: null, */ xhr: function() { return new window.XMLHttpRequest(); -- cgit v1.2.3 From 914aa3d66b0d34fc44377a2facc00a77c65d0891 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 05:24:14 +0100 Subject: Makes it so a prefilter can change the type of a request. --- src/ajax.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/ajax.js b/src/ajax.js index 3eb36c3a8..871481d01 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -584,12 +584,6 @@ jQuery.extend({ // Remove hash character (#7531: and string promotion) s.url = ( "" + s.url ).replace( rhash , "" ); - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = ! rnoContent.test( s.type ); - // Extract dataTypes list s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( /\s+/ ); @@ -605,13 +599,19 @@ jQuery.extend({ } // Convert data if not already a string - if ( s.data && s.processData && typeof s.data != "string" ) { + if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data , s.traditional ); } // Apply prefilters jQuery.ajaxPrefilter( s , options ); + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = ! rnoContent.test( s.type ); + // Watch for a new set of requests if ( s.global && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); -- cgit v1.2.3 From f74b84498987ace9bbbc3c041607016a23ff251e Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 05:25:45 +0100 Subject: The script prefilter now forces cross-domain requests type to GET. --- src/ajax/script.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/ajax/script.js b/src/ajax/script.js index ee1d489eb..b0e576f27 100644 --- a/src/ajax/script.js +++ b/src/ajax/script.js @@ -23,6 +23,7 @@ jQuery.ajaxSetup({ } if ( s.crossDomain ) { + s.type = "GET"; s.global = false; } -- cgit v1.2.3 From 158fa822dea3198de5a4bcff3955b869ebb758c8 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 05:26:46 +0100 Subject: Setting the jsonp option to false now inhibits any url manipulation regarding the callback. --- src/ajax/jsonp.js | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js index 675ecc085..883876fc0 100644 --- a/src/ajax/jsonp.js +++ b/src/ajax/jsonp.js @@ -1,8 +1,7 @@ (function( jQuery ) { var jsc = jQuery.now(), - jsre = /(\=)(?:\?|%3F)(&|$)|()(?:\?\?|%3F%3F)()/i, - rquery_jsonp = /\?/; + jsre = /(\=)(?:\?|%3F)(&|$)|()(?:\?\?|%3F%3F)()/i; // Default jsonp settings jQuery.ajaxSetup({ @@ -12,23 +11,36 @@ jQuery.ajaxSetup({ } // Detect, normalize options and install callbacks for jsonp requests -}).ajaxPrefilter("json jsonp", function(s, originalSettings) { +// (dataIsString is used internally) +}).ajaxPrefilter("json jsonp", function(s, originalSettings, dataIsString) { + + dataIsString = ( typeof(s.data) === "string" ); if ( s.dataTypes[ 0 ] === "jsonp" || - originalSettings.jsonp || originalSettings.jsonpCallback || - jsre.test(s.url) || - typeof(s.data) === "string" && jsre.test(s.data) ) { + originalSettings.jsonp != null || + s.jsonp !== false && ( jsre.test( s.url ) || + dataIsString && jsre.test( s.data ) ) ) { var responseContainer, jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, previous = window[ jsonpCallback ], - url = s.url.replace(jsre, "$1" + jsonpCallback + "$2"), - data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "$1" + jsonpCallback + "$2") : s.data; - - if ( url === s.url && data === s.data ) { - url += (rquery_jsonp.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( dataIsString ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } } s.url = url; -- cgit v1.2.3 From c272f5f7da3473fd5ac85efe783a0d63608ec62b Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 17:41:39 +0100 Subject: Implements joined jQuery.when statements. Makes it so calling jQuery.when with no parameter returns a resolved promise. Ensures promise method on promises supports the promise(obj) signature. Ensures a deferred and its promise always return the same promise (itself for the promise). Unit tests provided. --- src/core.js | 47 ++++++++++++++++++++++++++++++++++++----------- test/unit/core.js | 46 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/core.js b/src/core.js index 0bf0cb4b7..25ef22d76 100644 --- a/src/core.js +++ b/src/core.js @@ -898,9 +898,10 @@ jQuery.extend({ Deferred: function( func ) { var deferred = jQuery._Deferred(), - failDeferred = jQuery._Deferred(); + failDeferred = jQuery._Deferred(), + promise; - // Add errorDeferred methods and redefine cancel + // Add errorDeferred methods, then and promise jQuery.extend( deferred , { then: function( doneCallbacks , failCallbacks ) { @@ -914,13 +915,15 @@ jQuery.extend({ // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { - obj = obj || {}; - jQuery.each( "then done fail isResolved isRejected".split( " " ) , function( _ , method ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + jQuery.each( "then done fail isResolved isRejected promise".split( " " ) , function( _ , method ) { obj[ method ] = deferred[ method ]; }); - obj.promise = function() { - return obj; - }; return obj; } @@ -942,10 +945,32 @@ jQuery.extend({ // Deferred helper when: function( object ) { - object = object && jQuery.isFunction( object.promise ) ? - object : - jQuery.Deferred().resolve( object ); - return object.promise(); + var args = arguments, + length = args.length, + deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(), + resolveArray; + + if ( length > 1 ) { + resolveArray = new Array( length ); + jQuery.each( args, function( index, element, args ) { + jQuery.when( element ).done( function( value ) { + args = arguments; + resolveArray[ index ] = args.length > 1 ? slice.call( args , 0 ) : value; + if( ! --length ) { + deferred.fire( promise, resolveArray ); + } + }).fail( function() { + deferred.fireReject( promise, arguments ); + }); + return !deferred.isRejected(); + }); + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; }, // Use of jQuery.browser is frowned upon. diff --git a/test/unit/core.js b/test/unit/core.js index db160b2d6..8e3756a9c 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -21,7 +21,7 @@ test("jQuery()", function() { equals( jQuery(null).length, 0, "jQuery(null) === jQuery([])" ); equals( jQuery("").length, 0, "jQuery('') === jQuery([])" ); - var obj = jQuery("div") + var obj = jQuery("div"); equals( jQuery(obj).selector, "div", "jQuery(jQueryObj) == jQueryObj" ); // can actually yield more than one, when iframes are included, the window is an array as well @@ -1003,7 +1003,7 @@ test("jQuery._Deferred()", function() { test("jQuery.Deferred()", function() { - expect( 4 ); + expect( 6 ); jQuery.Deferred( function( defer ) { strictEqual( this , defer , "Defer passed as this & first argument" ); @@ -1023,11 +1023,16 @@ test("jQuery.Deferred()", function() { }, function() { ok( true , "Error on reject" ); }); + + var tmp = jQuery.Deferred(); + + strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); + strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); }); test("jQuery.when()", function() { - expect( 21 ); + expect( 23 ); // Some other objects jQuery.each( { @@ -1050,6 +1055,10 @@ test("jQuery.when()", function() { } ); + ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); + } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); + var cache, i; for( i = 1 ; i < 4 ; i++ ) { @@ -1064,6 +1073,37 @@ test("jQuery.when()", function() { } }); +test("jQuery.when() - joined", function() { + + expect(8); + + jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); + strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); + strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - non-observables"); + }); + + var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), + errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); + + jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); + same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); + strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - resolved observable"); + }); + + jQuery.when( 1 , errorDeferred , 3 ).done( function() { + ok( false , "Test the created deferred was rejected - rejected observable"); + }).fail( function( error , errorParam ) { + strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); + strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); + }); +}); + test("jQuery.subclass", function(){ expect(378); -- cgit v1.2.3 From 5798446b9816daca48e8c03791f4afc0ab0ac4cf Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 18:33:32 +0100 Subject: Put the split to get the list of promise methods out of the promise method itself and also switched from jQuery.each to a while loop to remove as much overhead as possible. Thanks go to scott_gonzalez for reminding me of this. --- src/core.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/core.js b/src/core.js index 25ef22d76..fbf64910e 100644 --- a/src/core.js +++ b/src/core.js @@ -63,6 +63,9 @@ var jQuery = function( selector, context ) { // The deferred used on DOM ready readyList, + // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + // The ready event handler DOMContentLoaded, @@ -914,16 +917,18 @@ jQuery.extend({ isRejected: failDeferred.isResolved, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { + // (i is used internally) + promise: function( obj , i ) { if ( obj == null ) { if ( promise ) { return promise; } promise = obj = {}; } - jQuery.each( "then done fail isResolved isRejected promise".split( " " ) , function( _ , method ) { - obj[ method ] = deferred[ method ]; - }); + i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ]; + } return obj; } -- cgit v1.2.3 From 28a1bad7b18b7ca4937666893e268d6e1378ee4f Mon Sep 17 00:00:00 2001 From: "adam j. sontag" Date: Mon, 17 Jan 2011 16:10:14 -0500 Subject: Add a comment to explain (and enforce the temporary-ness of) an extra line of code added to workaround a Chrome 10 bug --- src/traversing.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/traversing.js b/src/traversing.js index b36ce3db8..ee1d78b0e 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -204,6 +204,9 @@ jQuery.each({ }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a02383fa521c51d9996a46f03a7080dd825f11 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. args = slice.call(arguments); if ( !runtil.test( name ) ) { -- cgit v1.2.3 From 57cc182a40e909868d41f9b1bb405b06138f6cae Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Mon, 17 Jan 2011 15:22:49 -0600 Subject: Introduce a temporary hack to allow jQuery.fn.data("events") to continue to work. This will be going away in 1.6. More information will be available in the 1.5 release notes. --- src/data.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/data.js b/src/data.js index a1abc9ed6..21f0e3a55 100644 --- a/src/data.js +++ b/src/data.js @@ -93,6 +93,13 @@ jQuery.extend({ thisCache[ name ] = data; } + // 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; }, -- cgit v1.2.3 From 220a0ce1628d376ec14394c9b0be3c10f92a4cdb Mon Sep 17 00:00:00 2001 From: Brandon Sterne Date: Mon, 17 Jan 2011 16:31:12 -0500 Subject: Defer scriptEval test until first use to prevent Content Security Policy inline-script violations from occuring. Fixes #7371. --- src/core.js | 2 +- src/support.js | 56 +++++++++++++++++++++++++++++++++----------------------- 2 files changed, 34 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/core.js b/src/core.js index fbf64910e..4311e3103 100644 --- a/src/core.js +++ b/src/core.js @@ -583,7 +583,7 @@ jQuery.extend({ script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) { + if ( jQuery.support.scriptEval() ) { script.appendChild( document.createTextNode( data ) ); } else { script.text = data; diff --git a/src/support.js b/src/support.js index e4c3ea916..f502811ae 100644 --- a/src/support.js +++ b/src/support.js @@ -4,10 +4,7 @@ jQuery.support = {}; - var root = document.documentElement, - script = document.createElement("script"), - div = document.createElement("div"), - id = "script" + jQuery.now(); + var div = document.createElement("div"); div.style.display = "none"; div.innerHTML = "
a"; @@ -64,7 +61,7 @@ deleteExpando: true, optDisabled: false, checkClone: false, - scriptEval: false, + _scriptEval: null, noCloneEvent: true, boxModel: null, inlineBlockNeedsLayout: false, @@ -77,32 +74,45 @@ select.disabled = true; jQuery.support.optDisabled = !opt.disabled; - script.type = "text/javascript"; - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - // Make sure that the execution of code works by injecting a script - // tag with appendChild/createTextNode - // (IE doesn't support this, fails, and uses .text instead) - if ( window[ id ] ) { - jQuery.support.scriptEval = true; - delete window[ id ]; - } + jQuery.support.scriptEval = function() { + if ( jQuery.support._scriptEval === null) { + var root = document.documentElement, + script = document.createElement("script"), + id = "script" + jQuery.now(); + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support._scriptEval = true; + delete window[ id ]; + } else { + jQuery.support._scriptEval = false; + } + + root.removeChild( script ); + // release memory in IE + root = script = id = null; + } + return jQuery.support._scriptEval; + }; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { - delete script.test; + delete div.test; } catch(e) { jQuery.support.deleteExpando = false; } - root.removeChild( script ); - if ( div.attachEvent && div.fireEvent ) { div.attachEvent("onclick", function click() { // Cloning a node shouldn't copy over any @@ -191,6 +201,6 @@ jQuery.support.changeBubbles = eventSupported("change"); // release memory in IE - root = script = div = all = a = null; + div = all = a = null; })(); })( jQuery ); -- cgit v1.2.3 From 4058881784cec4adad881188064421ded69a0258 Mon Sep 17 00:00:00 2001 From: "adam j. sontag" Date: Mon, 17 Jan 2011 17:03:45 -0500 Subject: Add link to chrome issue ticket --- src/traversing.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/traversing.js b/src/traversing.js index ee1d78b0e..c54e25430 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -207,6 +207,7 @@ jQuery.each({ // The variable 'args' was introduced in // https://github.com/jquery/jquery/commit/52a02383fa521c51d9996a46f03a7080dd825f11 // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 args = slice.call(arguments); if ( !runtil.test( name ) ) { -- cgit v1.2.3 From 78be517727674d1e0a208e3e144eac8840d653e7 Mon Sep 17 00:00:00 2001 From: "adam j. sontag" Date: Mon, 17 Jan 2011 17:08:44 -0500 Subject: shorten the SHA --- src/traversing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/traversing.js b/src/traversing.js index c54e25430..f41816cfd 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -205,7 +205,7 @@ jQuery.each({ jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ), // The variable 'args' was introduced in - // https://github.com/jquery/jquery/commit/52a02383fa521c51d9996a46f03a7080dd825f11 + // https://github.com/jquery/jquery/commit/52a0238 // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. // http://code.google.com/p/v8/issues/detail?id=1050 args = slice.call(arguments); -- cgit v1.2.3 From d9660e1bf4f378e0fcb77ba266f27cdec7cda022 Mon Sep 17 00:00:00 2001 From: "adam j. sontag" Date: Mon, 17 Jan 2011 17:20:37 -0500 Subject: Fix tabs vs spaces in initial workaround commit --- src/traversing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/traversing.js b/src/traversing.js index f41816cfd..929547c11 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -208,7 +208,7 @@ jQuery.each({ // https://github.com/jquery/jquery/commit/52a0238 // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. // http://code.google.com/p/v8/issues/detail?id=1050 - args = slice.call(arguments); + args = slice.call(arguments); if ( !runtil.test( name ) ) { selector = until; -- cgit v1.2.3 From 9c763ad39d42c54d24f659e7895a8f361a08d27c Mon Sep 17 00:00:00 2001 From: John Resig Date: Tue, 18 Jan 2011 15:13:09 -0500 Subject: Add another tweak for handling CSP - we need to make sure that we don't trigger any eval on load (not sure if it's the best tweak, definitely not ideal). Add a test page as well so that it's easier to catch problem. --- src/support.js | 11 ++++++++++- test/csp.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/csp.php (limited to 'src') diff --git a/src/support.js b/src/support.js index f502811ae..7be28fdaf 100644 --- a/src/support.js +++ b/src/support.js @@ -75,7 +75,7 @@ jQuery.support.optDisabled = !opt.disabled; jQuery.support.scriptEval = function() { - if ( jQuery.support._scriptEval === null) { + if ( jQuery.support._scriptEval === null ) { var root = document.documentElement, script = document.createElement("script"), id = "script" + jQuery.now(); @@ -101,6 +101,7 @@ // release memory in IE root = script = id = null; } + return jQuery.support._scriptEval; }; @@ -187,6 +188,14 @@ var el = document.createElement("div"); eventName = "on" + eventName; + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( !el.attachEvent ) { + return true; + } + var isSupported = (eventName in el); if ( !isSupported ) { el.setAttribute(eventName, "return;"); diff --git a/test/csp.php b/test/csp.php new file mode 100644 index 000000000..acf8f32c9 --- /dev/null +++ b/test/csp.php @@ -0,0 +1,30 @@ + + + + + + CSP Test Page + + + + + + + + + + + + + + + + + + + + + +

CSP Test Page

+ + -- cgit v1.2.3 From 265cf0efa7ab3296b3fc4917b863d7b09e3d8bb4 Mon Sep 17 00:00:00 2001 From: Anton M Date: Wed, 19 Jan 2011 00:15:28 +0100 Subject: Remove an unused regex and optimize character escape regex usage. --- src/core.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/core.js b/src/core.js index 4311e3103..f116ef4d1 100644 --- a/src/core.js +++ b/src/core.js @@ -19,12 +19,8 @@ var jQuery = function( selector, context ) { // (both of which we optimize for) quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/, - // Check if a string has a non-whitespace character in it rnotwhite = /\S/, - rwhite = /\s/, // Used for trimming whitespace trimLeft = /^\s+/, @@ -1039,9 +1035,8 @@ if ( indexOf ) { }; } -// Verify that \s matches non-breaking spaces -// (IE fails on this test) -if ( !rwhite.test( "\xA0" ) ) { +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } -- cgit v1.2.3