From 612a908514b6ad6ec35754b0193887b01ce2c9f2 Mon Sep 17 00:00:00 2001 From: rwldrn Date: Sat, 1 Jan 2011 13:49:59 -0500 Subject: [PATCH 01/33] #7883 .delegate and .live should accept false as the fn arg, like bind --- src/event.js | 7 ++++++- test/unit/event.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index 675e5fff..4d908b92 100644 --- a/src/event.js +++ b/src/event.js @@ -1023,10 +1023,15 @@ jQuery.each(["live", "die"], function( i, name ) { return this; } - if ( jQuery.isFunction( data ) ) { + if ( jQuery.isFunction( data ) || data === false ) { fn = data; data = undefined; } + + if ( fn === false ) { + fn = returnFalse; + } + types = (types || "").split(" "); diff --git a/test/unit/event.js b/test/unit/event.js index b4672a8b..01fbac57 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -527,6 +527,45 @@ test("bind(name, false), unbind(name, false)", function() { equals( main, 1, "Verify that the trigger happened correctly." ); }); +test("live(name, false), die(name, false)", function() { + expect(3); + + var main = 0; + jQuery("#main").live("click", function(e){ main++; }); + jQuery("#ap").trigger("click"); + equals( main, 1, "Verify that the trigger happened correctly." ); + + main = 0; + jQuery("#ap").live("click", false); + jQuery("#ap").trigger("click"); + equals( main, 0, "Verify that no bubble happened." ); + + main = 0; + jQuery("#ap").die("click", false); + jQuery("#ap").trigger("click"); + equals( main, 1, "Verify that the trigger happened correctly." ); +}); + +test("delegate(selector, name, false), undelegate(selector, name, false)", function() { + expect(3); + + var main = 0; + + jQuery("#main").delegate("#ap", "click", function(e){ main++; }); + jQuery("#ap").trigger("click"); + equals( main, 1, "Verify that the trigger happened correctly." ); + + main = 0; + jQuery("#ap").delegate("#groups", "click", false); + jQuery("#groups").trigger("click"); + equals( main, 0, "Verify that no bubble happened." ); + + main = 0; + jQuery("#ap").undelegate("#groups", "click", false); + jQuery("#groups").trigger("click"); + equals( main, 1, "Verify that the trigger happened correctly." ); +}); + test("bind()/trigger()/unbind() on plain object", function() { expect( 8 ); From 037d6bd5c33cea91974bbfeaaa30d26e9dc94d48 Mon Sep 17 00:00:00 2001 From: rwldrn Date: Sat, 1 Jan 2011 17:13:04 -0500 Subject: [PATCH 02/33] 4321 returns empty jquery object --- src/core.js | 3 ++- test/unit/core.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core.js b/src/core.js index 0211808c..fcbd1c35 100644 --- a/src/core.js +++ b/src/core.js @@ -17,7 +17,8 @@ var jQuery = function( selector, context ) { // A simple way to check for HTML strings or ID strings // (both of which we optimize for) - quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + // Is it a simple selector isSimple = /^.[^:#\[\.,]*$/, diff --git a/test/unit/core.js b/test/unit/core.js index 9367ab10..2563f17c 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -12,7 +12,7 @@ test("Basic requirements", function() { }); test("jQuery()", function() { - expect(24); + expect(25); strictEqual( commonJSDefined, jQuery, "CommonJS registered (Bug #7102)" ); @@ -22,6 +22,7 @@ test("jQuery()", function() { equals( jQuery(undefined).length, 0, "jQuery(undefined) === jQuery([])" ); equals( jQuery(null).length, 0, "jQuery(null) === jQuery([])" ); equals( jQuery("").length, 0, "jQuery('') === jQuery([])" ); + equals( jQuery("#").length, 0, "jQuery('#') === jQuery([])" ); var obj = jQuery("div") equals( jQuery(obj).selector, "div", "jQuery(jQueryObj) == jQueryObj" ); From 5f52e61d74678786e2f40d4edbeb2396661bb9bc Mon Sep 17 00:00:00 2001 From: rwldrn Date: Sat, 1 Jan 2011 17:18:47 -0500 Subject: [PATCH 03/33] 4321 jQuery('#') returns empty jquery object --- src/core.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core.js b/src/core.js index fcbd1c35..3a4d9e66 100644 --- a/src/core.js +++ b/src/core.js @@ -19,7 +19,6 @@ var jQuery = function( selector, context ) { // (both of which we optimize for) quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, - // Is it a simple selector isSimple = /^.[^:#\[\.,]*$/, From d4e4414451e15d23d7174e8eeddaa952ed0e4d73 Mon Sep 17 00:00:00 2001 From: jeresig Date: Sun, 6 Mar 2011 22:47:40 -0500 Subject: [PATCH 04/33] Very crude first pass at splitting apart the attr/prop logic. Also adding in attrHooks/propHooks. All of it is completely untested. --- src/attributes.js | 274 ++++++++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 106 deletions(-) diff --git a/src/attributes.js b/src/attributes.js index 59972105..e5425a05 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -3,38 +3,37 @@ var rclass = /[\n\t\r]/g, rspaces = /\s+/, rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rradiocheck = /^(?:radio|checkbox)$/i; -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); + removeAttr: function( name ) { + return this.each(function() { if ( this.nodeType === 1 ) { this.removeAttribute( name ); } }); }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, addClass: function( value ) { if ( jQuery.isFunction(value) ) { @@ -275,6 +274,21 @@ jQuery.extend({ height: true, offset: true }, + + // TODO: Check to see if any of these are needed anymore? + // If not, it may be good to standardize on all-lowercase names instead + attrFix: { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" + }, attr: function( elem, name, value, pass ) { // don't get/set attributes on text, comment and attribute nodes @@ -286,104 +300,152 @@ jQuery.extend({ return jQuery(elem)[name](value); } - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - + var ret, + notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + hooks; + // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; + name = notxml && jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( value !== undefined ) { + + if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value )) !== undefined ) { + return ret; + + } else { + // convert the value to a string (all browsers do this but IE) see #1070 + value = "" + value; + + elem.setAttribute( name, value ); + + return value; + } + + } else { + + if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem )) !== undefined ) { + return ret; + + } else { + + if ( !jQuery.hasAttr( elem, name ) ) { + return undefined; + } - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); + var attr = elem.getAttribute( name ); - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } + // Non-existent attributes return null, we normalize to undefined + return attr === null ? + undefined : + attr; + } + } + }, + + hasAttr: function( elem, name ) { + // Blackberry 4.7 returns "" from getAttribute #6938 + return elem.attributes[ name ] || (elem.hasAttribute && elem.hasAttribute( name )); + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); } } + }, + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + tabIndex: { + get: function( elem ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + }, + + // TODO: Check to see if we really need any here. + propFix: {}, + + prop: function( elem, name, value ) { + var ret, hooks; + + // Try to normalize/fix the name + name = notxml && jQuery.propFix[ name ] || name; + + hooks = jQuery.propHooks[ name ]; + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem )) !== undefined ) { + return ret; + + } else { return elem[ name ]; } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; } - // Handle everything which isn't a DOM element node - if ( set ) { - elem[ name ] = value; - } - return elem[ name ]; - } + }, + + propHooks: {} }); +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "style" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + return elem.getAttribute( name, 2 ); + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + return elem.style.cssText; + }, + + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.attrHooks.selected = { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + })( jQuery ); From 368bfc0fe0628e10ebf9873b123253c2a8dbf844 Mon Sep 17 00:00:00 2001 From: Ben Alman Date: Thu, 31 Mar 2011 11:37:48 -0400 Subject: [PATCH 05/33] DOM Ready unit tests (but not the supporting fixed code). --- test/unit/event.js | 95 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 14 deletions(-) diff --git a/test/unit/event.js b/test/unit/event.js index 2a6d8a66..00fad6e5 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -685,7 +685,7 @@ test("hover()", function() { test("mouseover triggers mouseenter", function() { expect(1); - + var count = 0, elem = jQuery(""); elem.mouseenter(function () { @@ -693,7 +693,7 @@ test("mouseover triggers mouseenter", function() { }); elem.trigger('mouseover'); equals(count, 1, "make sure mouseover triggers a mouseenter" ); - + elem.remove(); }); @@ -1982,8 +1982,8 @@ test("window resize", function() { test("focusin bubbles", function() { expect(5); - - var input = jQuery( '' ).prependTo( "body" ), + + var input = jQuery( '' ).prependTo( "body" ), order = 0; jQuery( "body" ).bind( "focusin.focusinBubblesTest", function(){ @@ -1996,12 +1996,12 @@ test("focusin bubbles", function() { // DOM focus method input[0].focus(); - + // To make the next focus test work, we need to take focus off the input. // This will fire another focusin event, so set order to reflect that. order = 1; jQuery("#text1")[0].focus(); - + // jQuery trigger, which calls DOM focus order = 0; input.trigger( "focus" ); @@ -2010,15 +2010,82 @@ test("focusin bubbles", function() { jQuery( "body" ).unbind( "focusin.focusinBubblesTest" ); }); -/* -test("jQuery(function($) {})", function() { - stop(); - jQuery(function($) { - equals(jQuery, $, "ready doesn't provide an event object, instead it provides a reference to the jQuery function, see http://docs.jquery.com/Events/ready#fn"); - start(); - }); -}); +(function(){ + // This code must be run before DOM ready! + var notYetReady, noEarlyExecution, + order = [], + args = {}; + + notYetReady = !jQuery.isReady; + + test("jQuery.isReady", function() { + expect(2); + + equals(notYetReady, true, "jQuery.isReady should not be true before DOM ready"); + equals(jQuery.isReady, true, "jQuery.isReady should be true once DOM is ready"); + }); + + // Create an event handler. + function makeHandler( testId ) { + // When returned function is executed, push testId onto `order` array + // to ensure execution order. Also, store event handler arg to ensure + // the correct arg is being passed into the event handler. + return function( arg ) { + order.push(testId); + args[testId] = arg; + }; + } + + // Bind to the ready event in every possible way. + jQuery(makeHandler("a")); + jQuery(document).ready(makeHandler("b")); + jQuery(document).bind("ready.readytest", makeHandler("c")); + + // Do it twice, just to be sure. + jQuery(makeHandler("d")); + jQuery(document).ready(makeHandler("e")); + jQuery(document).bind("ready.readytest", makeHandler("f")); + + noEarlyExecution = order.length == 0; + + // This assumes that QUnit tests are run on DOM ready! + test("jQuery ready", function() { + expect(11); + + ok(noEarlyExecution, "Handlers bound to DOM ready should not execute before DOM ready"); + + // Ensure execution order. + same(order, ["a", "b", "c", "d", "e", "f"], "All bound DOM ready handlers should have executed in bind-order"); + + // Ensure handler argument is correct. + equals(args.a, jQuery, "Argument passed to fn in jQuery( fn ) should be jQuery"); + equals(args.b, jQuery, "Argument passed to fn in jQuery(document).ready( fn ) should be jQuery"); + ok(args.c instanceof jQuery.Event, "Argument passed to fn in jQuery(document).bind( 'ready', fn ) should be an event object"); + + order = []; + + // Now that the ready event has fired, again bind to the ready event + // in every possible way. These event handlers should execute immediately. + jQuery(makeHandler("g")); + equals(order.pop(), "g", "Event handler should execute immediately"); + equals(args.g, jQuery, "Argument passed to fn in jQuery( fn ) should be jQuery"); + + jQuery(document).ready(makeHandler("h")); + equals(order.pop(), "h", "Event handler should execute immediately"); + equals(args.h, jQuery, "Argument passed to fn in jQuery(document).ready( fn ) should be jQuery"); + + jQuery(document).bind("ready.readytest", makeHandler("i")); + equals(order.pop(), "i", "Event handler should execute immediately"); + ok(args.i instanceof jQuery.Event, "Argument passed to fn in jQuery(document).bind( 'ready', fn ) should be an event object"); + + // Cleanup. + jQuery(document).unbind("ready.readytest"); + }); + +})(); + +/* test("event properties", function() { stop(); jQuery("#simon1").click(function(event) { From de0c25bef485bab8c2d1e642947311596a9b5e87 Mon Sep 17 00:00:00 2001 From: Ben Alman Date: Thu, 31 Mar 2011 13:36:16 -0400 Subject: [PATCH 06/33] Updated DOM ready unit tests. --- test/unit/event.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/unit/event.js b/test/unit/event.js index 00fad6e5..0acab543 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2051,12 +2051,12 @@ test("focusin bubbles", function() { // This assumes that QUnit tests are run on DOM ready! test("jQuery ready", function() { - expect(11); + expect(10); ok(noEarlyExecution, "Handlers bound to DOM ready should not execute before DOM ready"); // Ensure execution order. - same(order, ["a", "b", "c", "d", "e", "f"], "All bound DOM ready handlers should have executed in bind-order"); + same(order, ["a", "b", "d", "e", "c", "f"], "Bound DOM ready handlers should execute in bind-order, but those bound with jQuery(document).bind( 'ready', fn ) will always execute last"); // Ensure handler argument is correct. equals(args.a, jQuery, "Argument passed to fn in jQuery( fn ) should be jQuery"); @@ -2075,9 +2075,8 @@ test("focusin bubbles", function() { equals(order.pop(), "h", "Event handler should execute immediately"); equals(args.h, jQuery, "Argument passed to fn in jQuery(document).ready( fn ) should be jQuery"); - jQuery(document).bind("ready.readytest", makeHandler("i")); - equals(order.pop(), "i", "Event handler should execute immediately"); - ok(args.i instanceof jQuery.Event, "Argument passed to fn in jQuery(document).bind( 'ready', fn ) should be an event object"); + jQuery(document).bind("ready.readytest", makeHandler("never")); + equals(order.length, 0, "Event handler should never execute since DOM ready has already passed"); // Cleanup. jQuery(document).unbind("ready.readytest"); From 2ed81b44be958b5f2b5569ab15f22bde262b4eb6 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Mon, 4 Apr 2011 17:41:30 +0200 Subject: [PATCH 07/33] Fixes #8744. Makes sure script transport abort method actually removes the script tag even if readyState exists. --- src/ajax/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ajax/script.js b/src/ajax/script.js index 34ddd046..d3364ee6 100644 --- a/src/ajax/script.js +++ b/src/ajax/script.js @@ -53,7 +53,7 @@ jQuery.ajaxTransport( "script", function(s) { // Attach handlers for all browsers script.onload = script.onreadystatechange = function( _, isAbort ) { - if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) { + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { // Handle memory leak in IE script.onload = script.onreadystatechange = null; From 123dd72e808800d670769f9bca75c6f2c866326b Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Mon, 4 Apr 2011 11:21:15 -0700 Subject: [PATCH 08/33] Bug 7345; Add support for explicit/relative string values in .css - modified from original pull req by brandonaron #78 --- src/css.js | 10 +++++++++- test/unit/css.js | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/css.js b/src/css.js index 17ac136b..d23549fa 100644 --- a/src/css.js +++ b/src/css.js @@ -7,6 +7,8 @@ var ralpha = /alpha\([^)]*\)/i, rupper = /([A-Z]|^ms)/g, rnumpx = /^-?\d+(?:px)?$/i, rnum = /^-?\d/, + rrelNum = /=/, + rrelString = /[^+\-\de]+/g, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], @@ -75,7 +77,7 @@ jQuery.extend({ } // Make sure that we're working with the right name - var ret, origName = jQuery.camelCase( name ), + var ret, parsed, type, origName = jQuery.camelCase( name ), style = elem.style, hooks = jQuery.cssHooks[ origName ]; name = jQuery.cssProps[ origName ] || origName; @@ -87,6 +89,12 @@ jQuery.extend({ return; } + // convert string to number or relative number + if ( type === "string" ) { + parsed = +value.replace( rrelString, '' ); + value = rrelNum.test( value ) ? parsed + jQuery.css( elem, name ) : parsed; + } + // If a number was passed in, add 'px' to the (except for certain CSS properties) if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; diff --git a/test/unit/css.js b/test/unit/css.js index 8ae8fcb3..c4724fcd 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -105,6 +105,27 @@ test("css(String|Hash)", function() { equals( child[0].style.fontSize, old, "Make sure font-size isn't changed on null." ); }); +test("css() explicit and relative values", function() { + var $elem = jQuery('#nothiddendiv'); + + $elem.css({ width: 1, height: 1 }); + ok( [$elem.width(), $elem.height()], [1,1] ); + $elem.css({ width: "+=9", height: "+=9" }); + ok( [$elem.width(), $elem.height()], [10,10] ); + $elem.css({ width: "-=9", height: "-=9" }); + ok( [$elem.width(), $elem.height()], [1,1] ); + $elem.css({ width: "+=9px", height: "+=9px" }); + ok( [$elem.width(), $elem.height()], [10,10] ); + $elem.css({ width: "-=9px", height: "-=9px" }); + ok( [$elem.width(), $elem.height()], [1,1] ); + $elem.css("width", "+=9").css("height", "+=9"); + ok( [$elem.width(), $elem.height()], [10,10] ); + $elem.css("width", "+9").css("height", "+9"); + ok( [$elem.width(), $elem.height()], [9,9] ); + $elem.css("width", "-9").css("height", "-9"); + ok( [$elem.width(), $elem.height()], [0,0] ); +}); + test("css(String, Object)", function() { expect(22); From 44a3b5839e6db9e3735b3d2d3035c6f79b3bcbe8 Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Mon, 4 Apr 2011 16:48:24 -0700 Subject: [PATCH 09/33] Improve relative string performance in .css and some code cleanup --- src/css.js | 19 ++++++++++--------- test/unit/css.js | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/css.js b/src/css.js index d23549fa..65ec20f5 100644 --- a/src/css.js +++ b/src/css.js @@ -7,8 +7,8 @@ var ralpha = /alpha\([^)]*\)/i, rupper = /([A-Z]|^ms)/g, rnumpx = /^-?\d+(?:px)?$/i, rnum = /^-?\d/, - rrelNum = /=/, - rrelString = /[^+\-\de]+/g, + rrelNum = /^[+\-]=/, + rrelNumFilter = /[^+\-\.\de]+/g, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], @@ -77,26 +77,27 @@ jQuery.extend({ } // Make sure that we're working with the right name - var ret, parsed, type, origName = jQuery.camelCase( name ), + var ret, type, origName = jQuery.camelCase( name ), style = elem.style, hooks = jQuery.cssHooks[ origName ]; name = jQuery.cssProps[ origName ] || origName; // Check if we're setting a value if ( value !== undefined ) { + type = typeof value; + // Make sure that NaN and null values aren't set. See: #7116 - if ( typeof value === "number" && isNaN( value ) || value == null ) { + if ( type === "number" && isNaN( value ) || value == null ) { return; } - // convert string to number or relative number - if ( type === "string" ) { - parsed = +value.replace( rrelString, '' ); - value = rrelNum.test( value ) ? parsed + jQuery.css( elem, name ) : parsed; + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && rrelNum.test( value ) ) { + value = +value.replace( rrelNumFilter, '' ) + parseFloat( jQuery.css( elem, name ) ); } // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; } diff --git a/test/unit/css.js b/test/unit/css.js index c4724fcd..08f50ef2 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -106,24 +106,35 @@ test("css(String|Hash)", function() { }); test("css() explicit and relative values", function() { + expect(9); var $elem = jQuery('#nothiddendiv'); - + $elem.css({ width: 1, height: 1 }); - ok( [$elem.width(), $elem.height()], [1,1] ); - $elem.css({ width: "+=9", height: "+=9" }); - ok( [$elem.width(), $elem.height()], [10,10] ); - $elem.css({ width: "-=9", height: "-=9" }); - ok( [$elem.width(), $elem.height()], [1,1] ); - $elem.css({ width: "+=9px", height: "+=9px" }); - ok( [$elem.width(), $elem.height()], [10,10] ); - $elem.css({ width: "-=9px", height: "-=9px" }); - ok( [$elem.width(), $elem.height()], [1,1] ); - $elem.css("width", "+=9").css("height", "+=9"); - ok( [$elem.width(), $elem.height()], [10,10] ); - $elem.css("width", "+9").css("height", "+9"); - ok( [$elem.width(), $elem.height()], [9,9] ); - $elem.css("width", "-9").css("height", "-9"); - ok( [$elem.width(), $elem.height()], [0,0] ); + equals( $elem.width(), 1, "Initial css set or width/height works (hash)" ); + + $elem.css({ width: "+=9" }); + equals( $elem.width(), 10, "'+=9' on width (hash)" ); + + $elem.css({ width: "-=9" }); + equals( $elem.width(), 1, "'-=9' on width (hash)" ); + + $elem.css({ width: "+=9px" }); + equals( $elem.width(), 10, "'+=9px' on width (hash)" ); + + $elem.css({ width: "-=9px" }); + equals( $elem.width(), 1, "'-=9px' on width (hash)" ); + + $elem.css( "width", "+=9" ); + equals( $elem.width(), 10, "'+=9' on width (params)" ); + + $elem.css( "width", "-=9" ) ; + equals( $elem.width(), 1, "'-=9' on width (params)" ); + + $elem.css( "width", "+=9px" ); + equals( $elem.width(), 10, "'+=9px' on width (params)" ); + + $elem.css( "width", "-=9px" ); + equals( $elem.width(), 1, "'-=9px' on width (params)" ); }); test("css(String, Object)", function() { From a7e7dbd978b97c55a29b364fd277c4ade1ec22b6 Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Tue, 5 Apr 2011 01:43:14 -0700 Subject: [PATCH 10/33] Bug 7587; Enhancement/1.6 Feature: Bypass regexp filter on $.parseJSON and use native thrown exceptions if window.JSON.parse is available --- src/core.js | 16 +++++++++------- test/unit/ajax.js | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/core.js b/src/core.js index 9312ee28..205a6e30 100644 --- a/src/core.js +++ b/src/core.js @@ -515,16 +515,18 @@ jQuery.extend({ // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test(data.replace(rvalidescape, "@") - .replace(rvalidtokens, "]") - .replace(rvalidbraces, "")) ) { + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { - // Try to use the native JSON parser first - return window.JSON && window.JSON.parse ? - window.JSON.parse( data ) : - (new Function("return " + data))(); + return (new Function( "return " + data ))(); } else { jQuery.error( "Invalid JSON: " + data ); diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 7c572a32..e9c7a00c 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1600,7 +1600,7 @@ test("jQuery.ajax() - malformed JSON", function() { }, error: function(xhr, msg, detailedMsg) { equals( "parsererror", msg, "A parse error occurred." ); - ok( /^Invalid JSON/.test(detailedMsg), "Detailed parsererror message provided" ); + ok( /^(Invalid|SyntaxError|exception)/i.test(detailedMsg), "Detailed parsererror message provided" ); start(); } }); From 3609bed9e7aa4c0aea4ebfc446ad15bc216aed0f Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Tue, 5 Apr 2011 07:20:58 -0700 Subject: [PATCH 11/33] Remove extra else in parseJSON --- src/core.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core.js b/src/core.js index 205a6e30..d8ed38c5 100644 --- a/src/core.js +++ b/src/core.js @@ -528,9 +528,8 @@ jQuery.extend({ return (new Function( "return " + data ))(); - } else { - jQuery.error( "Invalid JSON: " + data ); } + jQuery.error( "Invalid JSON: " + data ); }, // Cross-browser xml parsing From c1316a4cb1fa9b9f85ef96f0e2ce45768ccd59cd Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Tue, 5 Apr 2011 21:59:09 -0400 Subject: [PATCH 12/33] Shorten up the code and do event cleanup on test cases. --- src/event.js | 11 +++-------- test/unit/event.js | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/event.js b/src/event.js index 9164bd67..b8de9386 100644 --- a/src/event.js +++ b/src/event.js @@ -1027,15 +1027,10 @@ jQuery.each(["live", "die"], function( i, name ) { return this; } - if ( jQuery.isFunction( data ) || data === false ) { - fn = data; + if ( data === false || jQuery.isFunction( data ) ) { + fn = data || returnFalse; data = undefined; - } - - if ( fn === false ) { - fn = returnFalse; - } - + } types = (types || "").split(" "); diff --git a/test/unit/event.js b/test/unit/event.js index 48a97a1d..0b4b364b 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -567,6 +567,7 @@ test("live(name, false), die(name, false)", function() { jQuery("#ap").die("click", false); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); + jQuery("#main").die("click"); }); test("delegate(selector, name, false), undelegate(selector, name, false)", function() { @@ -587,6 +588,7 @@ test("delegate(selector, name, false), undelegate(selector, name, false)", funct jQuery("#ap").undelegate("#groups", "click", false); jQuery("#groups").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); + jQuery("#main").undelegate("#ap", "click"); }); test("bind()/trigger()/unbind() on plain object", function() { From e0856738e6151368cffc963800e06ebcc0bbc314 Mon Sep 17 00:00:00 2001 From: carpie Date: Wed, 19 Jan 2011 17:37:31 -0600 Subject: [PATCH 13/33] Skip id regex check when large html strings are passed to the jQuery constructor (#7990). --- src/core.js | 7 ++++++- test/unit/core.js | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/core.js b/src/core.js index f19de96d..a893fc9b 100644 --- a/src/core.js +++ b/src/core.js @@ -96,7 +96,12 @@ jQuery.fn = jQuery.prototype = { // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? - match = quickExpr.exec( selector ); + if ( selector.length > 1024 ) { + // Assume very large strings are HTML and skip the regex check + match = [ null, selector, null ]; + } else { + match = quickExpr.exec( selector ); + } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { diff --git a/test/unit/core.js b/test/unit/core.js index a8da85fc..03325ab7 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -469,7 +469,7 @@ test("isWindow", function() { }); test("jQuery('html')", function() { - expect(15); + expect(18); QUnit.reset(); jQuery.foo = false; @@ -501,6 +501,19 @@ test("jQuery('html')", function() { ok( jQuery("
")[0], "Create a div with closing tag." ); ok( jQuery("
")[0], "Create a table with closing tag." ); + + // Test very large html string #7990 + var i; + var li = '
  • very large html string
  • '; + var html = ['
      ']; + for ( i = 0; i < 50000; i += 1 ) { + html.push(li); + } + html.push('
    '); + html = jQuery(html.join(''))[0]; + equals( html.nodeName.toUpperCase(), 'UL'); + equals( html.firstChild.nodeName.toUpperCase(), 'LI'); + equals( html.childNodes.length, 50000 ); }); test("jQuery('html', context)", function() { From bfa32317d2a56145a2d1151c60ce2d71d1328dd9 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Thu, 10 Feb 2011 23:43:45 -0500 Subject: [PATCH 14/33] Switch from recursion to iteration for event triggering. Move event-name namespace processing out of event handler, since it has to always go through trigger. --- src/event.js | 122 ++++++++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/src/event.js b/src/event.js index b8de9386..2837c1e0 100644 --- a/src/event.js +++ b/src/event.js @@ -277,13 +277,12 @@ jQuery.event = { } }, - // bubbling is internal - trigger: function( event, data, elem /*, bubbling */ ) { + trigger: function( event, data, elem ) { // Event object or event type var type = event.type || event, - bubbling = arguments[3]; + bubbling = arguments[3], + namespaces = []; - if ( !bubbling ) { event = typeof event === "object" ? // jQuery.Event object event[ jQuery.expando ] ? event : @@ -293,10 +292,19 @@ jQuery.event = { jQuery.Event(type); if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the bare event type (no namespaces) event.type = type = type.slice(0, -1); event.exclusive = true; } - + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + event.type = type = namespaces.shift(); + namespaces.sort(); + } + event.namespace = namespaces.join("."); + event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); + // Handle a global trigger if ( !elem ) { // Don't bubble custom events when global (to avoid too much overhead) @@ -317,13 +325,12 @@ jQuery.event = { } }); } + return; } - // Handle triggering a single element - // don't do events on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; } // Clean up in case it is reused @@ -333,62 +340,53 @@ jQuery.event = { // Clone the incoming data, if any data = jQuery.makeArray( data ); data.unshift( event ); - } - event.currentTarget = elem; - - // Trigger the event, it is assumed that "handle" is a function - var handle = jQuery._data( elem, "handle" ); + // Always fire on the current element, e.g. triggerHandler or global trigger + var cur = elem; + do { + var handle = jQuery._data( cur, "handle" ); + event.currentTarget = cur; if ( handle ) { - handle.apply( elem, data ); + handle.apply( cur, data ); } - var parent = elem.parentNode || elem.ownerDocument; - - // Trigger an inline bound script - try { - if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + // Trigger an inline bound script; IE dies on special-char event names + try { + if ( jQuery.acceptData( cur ) && cur[ "on" + type ] && cur[ "on" + type ].apply( cur, data ) === false ) { event.result = false; event.preventDefault(); } - } + } catch ( ieError ) {} - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (inlineError) {} + cur = cur.parentNode || cur.ownerDocument; + } while ( cur && !event.isPropagationStopped() ); - if ( !event.isPropagationStopped() && parent ) { - jQuery.event.trigger( event, data, parent, true ); - - } else if ( !event.isDefaultPrevented() ) { + // If nobody prevented the default action, do it now + if ( !event.isDefaultPrevented() ) { var old, - target = event.target, - targetType = type.replace( rnamespaces, "" ), - isClick = jQuery.nodeName( target, "a" ) && targetType === "click", - special = jQuery.event.special[ targetType ] || {}; + special = jQuery.event.special[ type ] || {}; - if ( (!special._default || special._default.call( elem, event ) === false) && - !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + // IE may die here on non-alpha event names or hidden elements (#3533) try { - if ( target[ targetType ] ) { - // Make sure that we don't accidentally re-trigger the onFOO events - old = target[ "on" + targetType ]; + if ( elem[ type ] ) { + // Don't accidentally re-trigger the onFOO events + old = elem[ "on" + type ]; if ( old ) { - target[ "on" + targetType ] = null; + elem[ "on" + type ] = null; } jQuery.event.triggered = event.type; - target[ targetType ](); + elem[ type ](); } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (triggerError) {} + } catch ( ieError ) {} if ( old ) { - target[ "on" + targetType ] = old; + elem[ "on" + type ] = old; } jQuery.event.triggered = undefined; @@ -397,38 +395,24 @@ jQuery.event = { }, handle: function( event ) { - var all, handlers, namespaces, namespace_re, events, - namespace_sort = [], + // It's rare to arrive without handlers to call, so do all setup here. + // Copy the handler list since a called handler may add/remove events. + event = jQuery.event.fix( event || window.event ); + var events = jQuery._data( this, "events" ), + handlers = ((events || {})[ event.type ] || []).slice(0), + all_handlers = !event.exclusive && !event.namespace, args = jQuery.makeArray( arguments ); - event = args[0] = jQuery.event.fix( event || window.event ); + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; event.currentTarget = this; - // Namespaced event handlers - all = event.type.indexOf(".") < 0 && !event.exclusive; - - if ( !all ) { - namespaces = event.type.split("."); - event.type = namespaces.shift(); - namespace_sort = namespaces.slice(0).sort(); - namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.namespace = event.namespace || namespace_sort.join("."); - - events = jQuery._data(this, "events"); - - handlers = (events || {})[ event.type ]; - - if ( events && handlers ) { - // Clone the handlers to prevent manipulation - handlers = handlers.slice(0); - for ( var j = 0, l = handlers.length; j < l; j++ ) { var handleObj = handlers[ j ]; - // Filter the functions by class - if ( all || namespace_re.test( handleObj.namespace ) ) { + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if ( all_handlers || event.namespace_re.test( handleObj.namespace ) ) { // Pass in a reference to the handler function itself // So that we can later remove it event.handler = handleObj.handler; @@ -450,8 +434,6 @@ jQuery.event = { } } } - } - return event.result; }, From 246757bacdd6224724c667b7b9812d7d88bdd8e1 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Tue, 15 Mar 2011 23:40:25 -0400 Subject: [PATCH 15/33] Only bubble a triggered event if we have attached a jQuery handler, but check the current element for an inline handler regardless. Make some other size optimizations as well. --- src/event.js | 57 +++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/event.js b/src/event.js index 2837c1e0..25168ffb 100644 --- a/src/event.js +++ b/src/event.js @@ -143,7 +143,7 @@ jQuery.event = { // Add the function to the element's handler list handlers.push( handleObj ); - // Keep track of which events have been used, for global triggering + // Keep track of which events have been used, for event optimization jQuery.event.global[ type ] = true; } @@ -280,8 +280,10 @@ jQuery.event = { trigger: function( event, data, elem ) { // Event object or event type var type = event.type || event, - bubbling = arguments[3], - namespaces = []; + ontype = "on" + type, + handling = jQuery.event.global, + namespaces = [], + cur = elem; event = typeof event === "object" ? // jQuery.Event object @@ -310,8 +312,8 @@ jQuery.event = { // Don't bubble custom events when global (to avoid too much overhead) event.stopPropagation(); - // Only trigger if we've ever bound an event for it - if ( jQuery.event.global[ type ] ) { + // Save some time, only trigger if we've ever bound an event for this type + if ( handling[ type ] ) { // XXX This code smells terrible. event.js should not be directly // inspecting the data cache jQuery.each( jQuery.cache, function() { @@ -328,21 +330,20 @@ jQuery.event = { return; } - // don't do events on text and comment nodes + // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } - // Clean up in case it is reused + // Clean up the event in case it is being reused event.result = undefined; event.target = elem; - // Clone the incoming data, if any + // Clone any incoming data and prepend the event, creating the handler arg list data = jQuery.makeArray( data ); data.unshift( event ); - // Always fire on the current element, e.g. triggerHandler or global trigger - var cur = elem; + // Fire event on the current element, then bubble up the DOM tree do { var handle = jQuery._data( cur, "handle" ); @@ -351,16 +352,16 @@ jQuery.event = { handle.apply( cur, data ); } - // Trigger an inline bound script; IE dies on special-char event names + // Trigger an inline bound script; IE<9 dies on special-char event name try { - if ( jQuery.acceptData( cur ) && cur[ "on" + type ] && cur[ "on" + type ].apply( cur, data ) === false ) { + if ( jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { event.result = false; event.preventDefault(); } - } catch ( ieError ) {} + } catch ( ieError1 ) {} cur = cur.parentNode || cur.ownerDocument; - } while ( cur && !event.isPropagationStopped() ); + } while ( cur && !event.isPropagationStopped() && handling[ type ] ); // If nobody prevented the default action, do it now if ( !event.isDefaultPrevented() ) { @@ -370,23 +371,25 @@ jQuery.event = { if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { - // IE may die here on non-alpha event names or hidden elements (#3533) + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction)() check here because IE6/7 fails that test. + // Use try/catch so IE<9 won't die on special-char event name or hidden element (#3533). try { if ( elem[ type ] ) { - // Don't accidentally re-trigger the onFOO events - old = elem[ "on" + type ]; + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; if ( old ) { - elem[ "on" + type ] = null; + elem[ ontype ] = null; } - jQuery.event.triggered = event.type; + jQuery.event.triggered = type; elem[ type ](); } - } catch ( ieError ) {} + } catch ( ieError2 ) {} if ( old ) { - elem[ "on" + type ] = old; + elem[ ontype ] = old; } jQuery.event.triggered = undefined; @@ -395,11 +398,10 @@ jQuery.event = { }, handle: function( event ) { - // It's rare to arrive without handlers to call, so do all setup here. - // Copy the handler list since a called handler may add/remove events. + // It's rare to arrive without handlers to call, so do all setup now. + // Snapshot the handlers list since a called handler may add/remove events. event = jQuery.event.fix( event || window.event ); - var events = jQuery._data( this, "events" ), - handlers = ((events || {})[ event.type ] || []).slice(0), + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), all_handlers = !event.exclusive && !event.namespace, args = jQuery.makeArray( arguments ); @@ -472,8 +474,9 @@ jQuery.event = { // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, - body = document.body; + var eventDocument = event.target.ownerDocument || document, + doc = eventDocument.documentElement, + body = eventDocument.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); From 29386db3196e883bffeda63f9e9db1f9ed336bba Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Wed, 6 Apr 2011 10:31:14 -0400 Subject: [PATCH 16/33] Sadly, we still have to bubble the event so inline handlers will work. --- src/event.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/event.js b/src/event.js index 25168ffb..acaaf281 100644 --- a/src/event.js +++ b/src/event.js @@ -71,8 +71,8 @@ jQuery.event = { if ( !eventHandle ) { elemData.handle = eventHandle = function( e ) { - // Handle the second event of a trigger and when - // an event is called after a page has unloaded + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; @@ -281,7 +281,6 @@ jQuery.event = { // Event object or event type var type = event.type || event, ontype = "on" + type, - handling = jQuery.event.global, namespaces = [], cur = elem; @@ -313,7 +312,7 @@ jQuery.event = { event.stopPropagation(); // Save some time, only trigger if we've ever bound an event for this type - if ( handling[ type ] ) { + if ( jQuery.event.global[ type ] ) { // XXX This code smells terrible. event.js should not be directly // inspecting the data cache jQuery.each( jQuery.cache, function() { @@ -361,7 +360,7 @@ jQuery.event = { } catch ( ieError1 ) {} cur = cur.parentNode || cur.ownerDocument; - } while ( cur && !event.isPropagationStopped() && handling[ type ] ); + } while ( cur && !event.isPropagationStopped() ); // If nobody prevented the default action, do it now if ( !event.isDefaultPrevented() ) { From 879be3d8121179da4e7cafe1d36f7f1b1a97263b Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Wed, 6 Apr 2011 11:34:41 -0400 Subject: [PATCH 17/33] Fix some spacing and comment issues that crept in with the rebase. --- src/event.js | 117 +++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/src/event.js b/src/event.js index acaaf281..424ad02f 100644 --- a/src/event.js +++ b/src/event.js @@ -284,19 +284,19 @@ jQuery.event = { namespaces = [], cur = elem; - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); - if ( type.indexOf("!") >= 0 ) { - // Exclusive events trigger only for the bare event type (no namespaces) - event.type = type = type.slice(0, -1); - event.exclusive = true; - } + if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the bare event type (no namespaces) + event.type = type = type.slice(0, -1); + event.exclusive = true; + } if ( type.indexOf(".") >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); @@ -306,50 +306,50 @@ jQuery.event = { event.namespace = namespaces.join("."); event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); - // Handle a global trigger - if ( !elem ) { - // Don't bubble custom events when global (to avoid too much overhead) - event.stopPropagation(); + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); // Save some time, only trigger if we've ever bound an event for this type if ( jQuery.event.global[ type ] ) { - // XXX This code smells terrible. event.js should not be directly - // inspecting the data cache - jQuery.each( jQuery.cache, function() { - // 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 ); - } - }); - } - return; + // XXX This code smells terrible. event.js should not be directly + // inspecting the data cache + jQuery.each( jQuery.cache, function() { + // 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 ); + } + }); } + return; + } // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; - } + } // Clean up the event in case it is being reused - event.result = undefined; - event.target = elem; + event.result = undefined; + event.target = elem; // Clone any incoming data and prepend the event, creating the handler arg list - data = jQuery.makeArray( data ); - data.unshift( event ); + data = jQuery.makeArray( data ); + data.unshift( event ); // Fire event on the current element, then bubble up the DOM tree do { var handle = jQuery._data( cur, "handle" ); event.currentTarget = cur; - if ( handle ) { + if ( handle ) { handle.apply( cur, data ); - } + } // Trigger an inline bound script; IE<9 dies on special-char event name try { @@ -397,44 +397,43 @@ jQuery.event = { }, handle: function( event ) { - // It's rare to arrive without handlers to call, so do all setup now. - // Snapshot the handlers list since a called handler may add/remove events. event = jQuery.event.fix( event || window.event ); + // Snapshot the handlers list since a called handler may add/remove events. var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), - all_handlers = !event.exclusive && !event.namespace, + run_all = !event.exclusive && !event.namespace, args = jQuery.makeArray( arguments ); // Use the fix-ed Event rather than the (read-only) native event args[0] = event; event.currentTarget = this; - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; // Triggered event must 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event. - if ( all_handlers || event.namespace_re.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; + if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; - var ret = handleObj.handler.apply( this, args ); + var ret = handleObj.handler.apply( this, args ); - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - - if ( event.isImmediatePropagationStopped() ) { - break; + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); } } + + if ( event.isImmediatePropagationStopped() ) { + break; + } } + } return event.result; }, From b7dd8404c52518bc4d71b0e715c506252d651a3f Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Wed, 6 Apr 2011 22:11:58 -0400 Subject: [PATCH 18/33] Fixes #8712. Bubble custom events to the window when they are triggered. Ride that, Cowboy! --- src/event.js | 3 ++- test/unit/event.js | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/event.js b/src/event.js index 424ad02f..0323ffb7 100644 --- a/src/event.js +++ b/src/event.js @@ -359,7 +359,8 @@ jQuery.event = { } } catch ( ieError1 ) {} - cur = cur.parentNode || cur.ownerDocument; + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; } while ( cur && !event.isPropagationStopped() ); // If nobody prevented the default action, do it now diff --git a/test/unit/event.js b/test/unit/event.js index 6ad112c0..a26f54d2 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -779,10 +779,11 @@ test("trigger() shortcuts", function() { }); test("trigger() bubbling", function() { - expect(14); + expect(17); - var doc = 0, html = 0, body = 0, main = 0, ap = 0; + var win = 0, doc = 0, html = 0, body = 0, main = 0, ap = 0; + jQuery(window).bind("click", function(e){ win++; }); jQuery(document).bind("click", function(e){ if ( e.target !== document) { doc++; } }); jQuery("html").bind("click", function(e){ html++; }); jQuery("body").bind("click", function(e){ body++; }); @@ -790,15 +791,18 @@ test("trigger() bubbling", function() { jQuery("#ap").bind("click", function(){ ap++; return false; }); jQuery("html").trigger("click"); + equals( win, 1, "HTML bubble" ); equals( doc, 1, "HTML bubble" ); equals( html, 1, "HTML bubble" ); jQuery("body").trigger("click"); + equals( win, 2, "Body bubble" ); equals( doc, 2, "Body bubble" ); equals( html, 2, "Body bubble" ); equals( body, 1, "Body bubble" ); jQuery("#main").trigger("click"); + equals( win, 3, "Main bubble" ); equals( doc, 3, "Main bubble" ); equals( html, 3, "Main bubble" ); equals( body, 2, "Main bubble" ); From bbd9c776ea22296a6807d5b760bd4e82ee5f4414 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Mon, 4 Apr 2011 10:27:31 -0400 Subject: [PATCH 19/33] Fix #8732. Change feature detect for focusin event support, so IE9 won't have duplicate events. --- src/event.js | 2 +- src/support.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index 0323ffb7..9a63b5f9 100644 --- a/src/event.js +++ b/src/event.js @@ -850,7 +850,7 @@ function trigger( type, elem, args ) { } // Create "bubbling" focus and blur events -if ( document.addEventListener ) { +if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler while someone wants focusin/focusout diff --git a/src/support.js b/src/support.js index 4c309562..8b950e25 100644 --- a/src/support.js +++ b/src/support.js @@ -226,6 +226,7 @@ jQuery.support.submitBubbles = eventSupported("submit"); jQuery.support.changeBubbles = eventSupported("change"); + jQuery.support.focusinBubbles = document.attachEvent && eventSupported("focusin"); // release memory in IE div = all = a = null; From 75a4bc4a4e55360c7c3c734cbef2558050ed4456 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Thu, 7 Apr 2011 05:07:20 +0200 Subject: [PATCH 20/33] Makes sure each animation tick has the same timestamp for all animations in the batch. --- src/effects.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/effects.js b/src/effects.js index d9e9a8b3..5ff85d13 100644 --- a/src/effects.js +++ b/src/effects.js @@ -11,7 +11,17 @@ var elemdisplay = {}, [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] - ]; + ], + fxNow; + +function clearFxNow() { + fxNow = undefined; +} + +function createFxNow() { + setTimeout( clearFxNow, 0 ); + return ( fxNow = jQuery.now() ); +} jQuery.fn.extend({ show: function( speed, easing, callback ) { @@ -349,7 +359,7 @@ jQuery.fx.prototype = { var self = this, fx = jQuery.fx; - this.startTime = jQuery.now(); + this.startTime = fxNow || createFxNow(); this.start = from; this.end = to; this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); @@ -394,7 +404,8 @@ jQuery.fx.prototype = { // Each step of an animation step: function( gotoEnd ) { - var t = jQuery.now(), done = true; + var t = fxNow || createFxNow(), + done = true; if ( gotoEnd || t >= this.options.duration + this.startTime ) { this.now = this.end; From bb99899ca0de93dd12f5a53f409ff6f72bfcf94c Mon Sep 17 00:00:00 2001 From: jaubourg Date: Thu, 7 Apr 2011 05:49:32 +0200 Subject: [PATCH 21/33] Adds always and chain methods to deferreds. --- src/deferred.js | 32 +++- test/unit/deferred.js | 428 +++++++++++++++++++++++++++--------------- 2 files changed, 307 insertions(+), 153 deletions(-) diff --git a/src/deferred.js b/src/deferred.js index 90f9c808..feaccaef 100644 --- a/src/deferred.js +++ b/src/deferred.js @@ -1,7 +1,7 @@ (function( jQuery ) { var // Promise methods - promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + promiseMethods = "done fail isResolved isRejected promise then always chain".split( " " ), // Static reference to slice sliceDeferred = [].slice; @@ -100,10 +100,38 @@ jQuery.extend({ deferred.done( doneCallbacks ).fail( failCallbacks ); return this; }, + always: function() { + return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); + }, fail: failDeferred.done, rejectWith: failDeferred.resolveWith, reject: failDeferred.resolve, isRejected: failDeferred.isResolved, + // Chain + chain: function( fnDone, fnFail ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject ); + } else { + newDefer[ action ]( returned ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + } ); + }).promise(); + }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { @@ -169,4 +197,4 @@ jQuery.extend({ } }); -})( jQuery ); +})( jQuery ); \ No newline at end of file diff --git a/test/unit/deferred.js b/test/unit/deferred.js index 6ba4767a..c1ed7c57 100644 --- a/test/unit/deferred.js +++ b/test/unit/deferred.js @@ -1,153 +1,273 @@ module("deferred", { teardown: moduleTeardown }); -test("jQuery._Deferred()", function() { +jQuery.each( [ "", " - new operator" ], function( _, withNew ) { - expect( 11 ); - - var deferred, - object, - test; - - deferred = jQuery._Deferred(); - - test = false; - - deferred.done( function( value ) { - equals( value , "value" , "Test pre-resolve callback" ); - test = true; - } ); - - deferred.resolve( "value" ); - - ok( test , "Test pre-resolve callbacks called right away" ); - - test = false; - - deferred.done( function( value ) { - equals( value , "value" , "Test post-resolve callback" ); - test = true; - } ); - - ok( test , "Test post-resolve callbacks called right away" ); - - deferred.cancel(); - - test = true; - - deferred.done( function() { - ok( false , "Cancel was ignored" ); - test = false; - } ); - - ok( test , "Test cancel" ); - - deferred = jQuery._Deferred().resolve(); - - try { - deferred.done( function() { - throw "Error"; - } , function() { - ok( true , "Test deferred do not cancel on exception" ); - } ); - } catch( e ) { - strictEqual( e , "Error" , "Test deferred propagates exceptions"); - deferred.done(); + function createDeferred() { + return withNew ? new jQuery._Deferred() : jQuery._Deferred(); } - test = ""; - deferred = jQuery._Deferred().done( function() { + test("jQuery._Deferred" + withNew, function() { - test += "A"; + expect( 11 ); - }, function() { + var deferred, + object, + test; - test += "B"; + deferred = createDeferred(); - } ).resolve(); + test = false; - strictEqual( test , "AB" , "Test multiple done parameters" ); + deferred.done( function( value ) { + equals( value , "value" , "Test pre-resolve callback" ); + test = true; + } ); - test = ""; + deferred.resolve( "value" ); - deferred.done( function() { + ok( test , "Test pre-resolve callbacks called right away" ); + + test = false; + + deferred.done( function( value ) { + equals( value , "value" , "Test post-resolve callback" ); + test = true; + } ); + + ok( test , "Test post-resolve callbacks called right away" ); + + deferred.cancel(); + + test = true; + + deferred.done( function() { + ok( false , "Cancel was ignored" ); + test = false; + } ); + + ok( test , "Test cancel" ); + + deferred = createDeferred().resolve(); + + try { + deferred.done( function() { + throw "Error"; + } , function() { + ok( true , "Test deferred do not cancel on exception" ); + } ); + } catch( e ) { + strictEqual( e , "Error" , "Test deferred propagates exceptions"); + deferred.done(); + } + + test = ""; + deferred = createDeferred().done( function() { + + test += "A"; + + }, function() { + + test += "B"; + + } ).resolve(); + + strictEqual( test , "AB" , "Test multiple done parameters" ); + + test = ""; deferred.done( function() { - test += "C"; + deferred.done( function() { + test += "C"; + + } ); + + test += "A"; + + }, function() { + + test += "B"; } ); - test += "A"; + strictEqual( test , "ABC" , "Test done callbacks order" ); - }, function() { + deferred = createDeferred(); - test += "B"; + deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) { + ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); + }); + + // #8421 + deferred = createDeferred(); + deferred.resolveWith().done(function() { + ok( true, "Test resolveWith can be called with no argument" ); + }); + }); +} ); + +jQuery.each( [ "", " - new operator" ], function( _, withNew ) { + + function createDeferred( fn ) { + return withNew ? new jQuery.Deferred( fn ) : jQuery.Deferred( fn ); + } + + test("jQuery.Deferred" + withNew, function() { + + expect( 8 ); + + createDeferred().resolve().then( function() { + ok( true , "Success on resolve" ); + ok( this.isResolved(), "Deferred is resolved" ); + }, function() { + ok( false , "Error on resolve" ); + }).always( function() { + ok( true , "Always callback on resolve" ); + }); + + createDeferred().reject().then( function() { + ok( false , "Success on reject" ); + }, function() { + ok( true , "Error on reject" ); + ok( this.isRejected(), "Deferred is rejected" ); + }).always( function() { + ok( true , "Always callback on reject" ); + }); + + createDeferred( function( defer ) { + ok( this === defer , "Defer passed as this & first argument" ); + this.resolve( "done" ); + }).then( function( value ) { + strictEqual( value , "done" , "Passed function executed" ); + }); + }); +} ); + +test( "jQuery.Deferred.chain - filtering (done)", function() { + + expect(3); + + var defer = jQuery.Deferred(), + chained = defer.chain(function( a, b ) { + return a * b; + }), + value1, + value2, + value3; + + chained.done(function( result ) { + value3 = result; + }); + + defer.done(function( a, b ) { + value1 = a; + value2 = b; + }); + + defer.resolve( 2, 3 ); + + strictEqual( value1, 2, "first resolve value ok" ); + strictEqual( value2, 3, "second resolve value ok" ); + strictEqual( value3, 6, "result of filter ok" ); + + jQuery.Deferred().reject().chain(function() { + ok( false, "chain should not be called on reject" ); + }); +}); + +test( "jQuery.Deferred.chain - filtering (fail)", function() { + + expect(3); + + var defer = jQuery.Deferred(), + chained = defer.chain( null, function( a, b ) { + return a * b; + } ), + value1, + value2, + value3; + + chained.fail(function( result ) { + value3 = result; + }); + + defer.fail(function( a, b ) { + value1 = a; + value2 = b; + }); + + defer.reject( 2, 3 ); + + strictEqual( value1, 2, "first reject value ok" ); + strictEqual( value2, 3, "second reject value ok" ); + strictEqual( value3, 6, "result of filter ok" ); + + jQuery.Deferred().resolve().chain( null, function() { + ok( false, "chain should not be called on resolve" ); } ); - - strictEqual( test , "ABC" , "Test done callbacks order" ); - - deferred = jQuery._Deferred(); - - deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) { - ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); - }); - - // #8421 - deferred = jQuery._Deferred(); - deferred.resolveWith().done(function() { - ok( true, "Test resolveWith can be called with no argument" ); - }); }); -test("jQuery.Deferred()", function() { +test( "jQuery.Deferred.chain - deferred (done)", function() { - expect( 10 ); + expect(3); - jQuery.Deferred( function( defer ) { - strictEqual( this , defer , "Defer passed as this & first argument" ); - this.resolve( "done" ); - }).then( function( value ) { - strictEqual( value , "done" , "Passed function executed" ); + var defer = jQuery.Deferred(), + chained = defer.chain(function( a, b ) { + return jQuery.Deferred(function( defer ) { + defer.reject( a * b ); + }); + }), + value1, + value2, + value3; + + chained.fail(function( result ) { + value3 = result; }); - jQuery.Deferred().resolve().then( function() { - ok( true , "Success on resolve" ); - }, function() { - ok( false , "Error on resolve" ); + defer.done(function( a, b ) { + value1 = a; + value2 = b; }); - jQuery.Deferred().reject().then( function() { - ok( false , "Success on reject" ); - }, function() { - ok( true , "Error on reject" ); - }); + defer.resolve( 2, 3 ); - ( new jQuery.Deferred( function( defer ) { - strictEqual( this , defer , "Defer passed as this & first argument (new)" ); - this.resolve( "done" ); - }) ).then( function( value ) { - strictEqual( value , "done" , "Passed function executed (new)" ); - }); - - ( new jQuery.Deferred() ).resolve().then( function() { - ok( true , "Success on resolve (new)" ); - }, function() { - ok( false , "Error on resolve (new)" ); - }); - - ( new jQuery.Deferred() ).reject().then( function() { - ok( false , "Success on reject (new)" ); - }, function() { - ok( true , "Error on reject (new)" ); - }); - - 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" ); + strictEqual( value1, 2, "first resolve value ok" ); + strictEqual( value2, 3, "second resolve value ok" ); + strictEqual( value3, 6, "result of filter ok" ); }); -test("jQuery.when()", function() { +test( "jQuery.Deferred.chain - deferred (fail)", function() { + + expect(3); + + var defer = jQuery.Deferred(), + chained = defer.chain( null, function( a, b ) { + return jQuery.Deferred(function( defer ) { + defer.resolve( a * b ); + }); + } ), + value1, + value2, + value3; + + chained.done(function( result ) { + value3 = result; + }); + + defer.fail(function( a, b ) { + value1 = a; + value2 = b; + }); + + defer.reject( 2, 3 ); + + strictEqual( value1, 2, "first reject value ok" ); + strictEqual( value2, 3, "second reject value ok" ); + strictEqual( value3, 6, "result of filter ok" ); +}); + +test( "jQuery.when" , function() { expect( 23 ); @@ -166,57 +286,63 @@ test("jQuery.when()", function() { } , function( message , value ) { - ok( jQuery.isFunction( jQuery.when( value ).then( function( resolveValue ) { + ok( jQuery.isFunction( jQuery.when( value ).done(function( resolveValue ) { strictEqual( resolveValue , value , "Test the promise was resolved with " + message ); - } ).promise ) , "Test " + message + " triggers the creation of a new Promise" ); + }).promise ) , "Test " + message + " triggers the creation of a new Promise" ); } ); - ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + ok( jQuery.isFunction( jQuery.when().done(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" ); + }).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); var cache, i; for( i = 1 ; i < 4 ; i++ ) { jQuery.when( cache || jQuery.Deferred( function() { this.resolve( i ); - }) ).then( function( value ) { + }) ).done(function( value ) { strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) ); cache = value; - }, function() { - ok( false , "Fail called" ); }); } }); -test("jQuery.when() - joined", function() { +test("jQuery.when - joined", function() { - expect(8); + expect(25); - 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 deferreds = { + value: 1, + success: jQuery.Deferred().resolve( 1 ), + error: jQuery.Deferred().reject( 0 ), + futureSuccess: jQuery.Deferred(), + futureError: jQuery.Deferred() + }, + willSucceed = { + value: true, + success: true, + error: false, + futureSuccess: true, + futureError: false + }; - 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" ); - }); + jQuery.each( deferreds, function( id1, defer1 ) { + jQuery.each( deferreds, function( id2, defer2 ) { + var shouldResolve = willSucceed[ id1 ] && willSucceed[ id2 ], + expected = shouldResolve ? [ 1, 1 ] : [ 0, undefined ], + code = id1 + "/" + id2; + jQuery.when( defer1, defer2 ).done(function( a, b ) { + if ( shouldResolve ) { + same( [ a, b ], expected, code + " => resolve" ); + } + }).fail(function( a, b ) { + if ( !shouldResolve ) { + same( [ a, b ], expected, code + " => resolve" ); + } + }); + } ); + } ); + deferreds.futureSuccess.resolve( 1 ); + deferreds.futureError.reject( 0 ); }); From 4552d135f404b78f4fa1494a3de2911b2e2e4773 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Thu, 7 Apr 2011 06:00:52 +0200 Subject: [PATCH 22/33] Adds fn.promise as a mean to observe the completion of animations on a set of elements. Only queued animations are handled for now, non-queued animations support coming soon. Effects unit tests updated to test the feature (needs more testing though). --- src/queue.js | 45 +++++++++++++++++++++++++++++++++++++++++++- test/unit/effects.js | 41 ++++++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/queue.js b/src/queue.js index 9e3e2fb5..701d06ad 100644 --- a/src/queue.js +++ b/src/queue.js @@ -28,7 +28,8 @@ jQuery.extend({ type = type || "fx"; var queue = jQuery.queue( elem, type ), - fn = queue.shift(); + fn = queue.shift(), + defer; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -49,6 +50,17 @@ jQuery.extend({ if ( !queue.length ) { jQuery.removeData( elem, type + "queue", true ); + // Look if we have observers and resolve if needed + if (( defer = jQuery.data( elem, type + "defer", undefined, true ) )) { + // Give room for hard-coded callbacks to fire first + // and eventually add another animation on the element + setTimeout( function() { + if ( !jQuery.data( elem, type + "queue", undefined, true ) ) { + jQuery.removeData( elem, type + "defer", true ); + defer.resolve(); + } + }, 0 ); + } } } }); @@ -93,6 +105,37 @@ jQuery.fn.extend({ clearQueue: function( type ) { return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue"; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + jQuery.data( elements[ i ], queueDataKey, undefined, true ) && + jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { + count++; + tmp.done( resolve ); + } + } + resolve(); + return defer.promise(); } }); diff --git a/test/unit/effects.js b/test/unit/effects.js index c0a812f4..4f678511 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -717,59 +717,64 @@ jQuery.each( { var anim = { width: t_w, height: t_h, opacity: t_o }; - elem.animate(anim, 50, function(){ + elem.animate(anim, 50); + + jQuery.when( elem ).done(function( elem ){ + + elem = elem[ 0 ]; + if ( t_w == "show" ) - equals( this.style.display, "block", "Showing, display should block: " + this.style.display); + equals( elem.style.display, "block", "Showing, display should block: " + elem.style.display); if ( t_w == "hide"||t_w == "show" ) - ok(f_w === "" ? this.style.width === f_w : this.style.width.indexOf(f_w) === 0, "Width must be reset to " + f_w + ": " + this.style.width); + ok(f_w === "" ? elem.style.width === f_w : elem.style.width.indexOf(f_w) === 0, "Width must be reset to " + f_w + ": " + elem.style.width); if ( t_h == "hide"||t_h == "show" ) - ok(f_h === "" ? this.style.height === f_h : this.style.height.indexOf(f_h) === 0, "Height must be reset to " + f_h + ": " + this.style.height); + ok(f_h === "" ? elem.style.height === f_h : elem.style.height.indexOf(f_h) === 0, "Height must be reset to " + f_h + ": " + elem.style.height); - var cur_o = jQuery.style(this, "opacity"); + var cur_o = jQuery.style(elem, "opacity"); if ( t_o == "hide" || t_o == "show" ) equals(cur_o, f_o, "Opacity must be reset to " + f_o + ": " + cur_o); if ( t_w == "hide" ) - equals(this.style.display, "none", "Hiding, display should be none: " + this.style.display); + equals(elem.style.display, "none", "Hiding, display should be none: " + elem.style.display); if ( t_o.constructor == Number ) { equals(cur_o, t_o, "Final opacity should be " + t_o + ": " + cur_o); - ok(jQuery.css(this, "opacity") != "" || cur_o == t_o, "Opacity should be explicitly set to " + t_o + ", is instead: " + cur_o); + ok(jQuery.css(elem, "opacity") != "" || cur_o == t_o, "Opacity should be explicitly set to " + t_o + ", is instead: " + cur_o); } if ( t_w.constructor == Number ) { - equals(this.style.width, t_w + "px", "Final width should be " + t_w + ": " + this.style.width); + equals(elem.style.width, t_w + "px", "Final width should be " + t_w + ": " + elem.style.width); - var cur_w = jQuery.css(this,"width"); + var cur_w = jQuery.css(elem,"width"); - ok(this.style.width != "" || cur_w == t_w, "Width should be explicitly set to " + t_w + ", is instead: " + cur_w); + ok(elem.style.width != "" || cur_w == t_w, "Width should be explicitly set to " + t_w + ", is instead: " + cur_w); } if ( t_h.constructor == Number ) { - equals(this.style.height, t_h + "px", "Final height should be " + t_h + ": " + this.style.height); + equals(elem.style.height, t_h + "px", "Final height should be " + t_h + ": " + elem.style.height); - var cur_h = jQuery.css(this,"height"); + var cur_h = jQuery.css(elem,"height"); - ok(this.style.height != "" || cur_h == t_h, "Height should be explicitly set to " + t_h + ", is instead: " + cur_w); + ok(elem.style.height != "" || cur_h == t_h, "Height should be explicitly set to " + t_h + ", is instead: " + cur_w); } if ( t_h == "show" ) { - var old_h = jQuery.css(this, "height"); - jQuery(this).append("
    Some more text
    and some more..."); + var old_h = jQuery.css(elem, "height"); + jQuery(elem).append("
    Some more text
    and some more..."); if ( /Auto/.test( fn ) ) { - notEqual(jQuery.css(this, "height"), old_h, "Make sure height is auto."); + notEqual(jQuery.css(elem, "height"), old_h, "Make sure height is auto."); } else { - equals(jQuery.css(this, "height"), old_h, "Make sure height is not auto."); + equals(jQuery.css(elem, "height"), old_h, "Make sure height is not auto."); } } // manually remove generated element - jQuery(this).remove(); + jQuery(elem).remove(); start(); }); From f3c6077da02f080f09d73ec4d8a8029f76654c2b Mon Sep 17 00:00:00 2001 From: jaubourg Date: Thu, 7 Apr 2011 06:47:15 +0200 Subject: [PATCH 23/33] Rewrite of globalEval. Uses window.execScript or window.eval with a trick to ensure proper context. Unit tests added. --- src/core.js | 27 ++++++++++----------------- test/unit/core.js | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/core.js b/src/core.js index a893fc9b..7f63012a 100644 --- a/src/core.js +++ b/src/core.js @@ -561,24 +561,17 @@ jQuery.extend({ noop: function() {}, - // Evalulates a script in a global context + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { - if ( data && rnotwhite.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement, - script = document.createElement( "script" ); - - if ( jQuery.support.scriptEval() ) { - script.appendChild( document.createTextNode( data ) ); - } else { - script.text = data; - } - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); } }, diff --git a/test/unit/core.js b/test/unit/core.js index 03325ab7..79710025 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -172,6 +172,26 @@ test("selector state", function() { ); }); +test( "globalEval", function() { + + expect( 3 ); + + jQuery.globalEval( "var globalEvalTest = true;" ); + ok( window.globalEvalTest, "Test variable declarations are global" ); + + window.globalEvalTest = false; + + jQuery.globalEval( "globalEvalTest = true;" ); + ok( window.globalEvalTest, "Test variable assignments are global" ); + + window.globalEvalTest = false; + + jQuery.globalEval( "this.globalEvalTest = true;" ); + ok( window.globalEvalTest, "Test context (this) is the window object" ); + + window.globalEvalTest = undefined; +}); + if ( !isLocal ) { test("browser", function() { stop(); From c1dcad69427b78f3e70628ab1444f42033ee593a Mon Sep 17 00:00:00 2001 From: jaubourg Date: Thu, 7 Apr 2011 06:51:37 +0200 Subject: [PATCH 24/33] Rewrite of the support module. We use a temporary body element in order not to have to wait for the document to be ready for boxModel-related support tests. --- src/support.js | 273 ++++++++++++++++++++++++------------------------- 1 file changed, 132 insertions(+), 141 deletions(-) diff --git a/src/support.js b/src/support.js index 4c309562..d5625bed 100644 --- a/src/support.js +++ b/src/support.js @@ -1,44 +1,58 @@ (function( jQuery ) { -(function() { +jQuery.support = (function() { - jQuery.support = {}; + var div = document.createElement( "div" ), + all, + a, + select, + opt, + input, + support, + fragment, + body, + bodyStyle, + tds, + events, + eventName, + i, + isSupported; - var div = document.createElement("div"); - - div.style.display = "none"; + // Preliminary tests div.innerHTML = "
    a"; - var all = div.getElementsByTagName("*"), - a = div.getElementsByTagName("a")[0], - select = document.createElement("select"), - opt = select.appendChild( document.createElement("option") ), - input = div.getElementsByTagName("input")[0]; + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { - return; + return {}; } - jQuery.support = { + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, + leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, + tbody: !div.getElementsByTagName( "tbody" ).length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, + htmlSerialize: !!div.getElementsByTagName( "link" ).length, // Get the style information from getAttribute - // (IE uses .cssText insted) + // (IE uses .cssText instead) style: /red/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", + hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) @@ -59,175 +73,152 @@ optSelected: opt.selected, // Will be defined later + submitBubbles: true, + changeBubbles: true, deleteExpando: true, - optDisabled: false, - checkClone: false, noCloneEvent: true, - noCloneChecked: true, - boxModel: null, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, - reliableHiddenOffsets: true, reliableMarginRight: true }; + // Make sure checked status is properly cloned input.checked = true; - jQuery.support.noCloneChecked = input.cloneNode( true ).checked; + support.noCloneChecked = input.cloneNode( true ).checked; // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as diabled) + // (WebKit marks them as disabled) select.disabled = true; - jQuery.support.optDisabled = !opt.disabled; - - var _scriptEval = null; - jQuery.support.scriptEval = function() { - if ( _scriptEval === null ) { - var root = document.documentElement, - script = document.createElement("script"), - id = "script" + jQuery.now(); - - // 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) - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - if ( window[ id ] ) { - _scriptEval = true; - delete window[ id ]; - } else { - _scriptEval = false; - } - - root.removeChild( script ); - } - - return _scriptEval; - }; + support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; - - } catch(e) { - jQuery.support.deleteExpando = false; + } catch( e ) { + support.deleteExpando = false; } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent("onclick", function click() { + div.attachEvent( "onclick", function click() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) - jQuery.support.noCloneEvent = false; - div.detachEvent("onclick", click); - }); - div.cloneNode(true).fireEvent("onclick"); + support.noCloneEvent = false; + div.detachEvent( "onclick", click ); + } ); + div.cloneNode( true ).fireEvent( "onclick" ); } - div = document.createElement("div"); div.innerHTML = ""; - var fragment = document.createDocumentFragment(); + fragment = document.createDocumentFragment(); fragment.appendChild( div.firstChild ); // WebKit doesn't clone checked state correctly in fragments - jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; // Figure out if the W3C box model works as expected - // document.body must exist before we can do this - jQuery(function() { - var div = document.createElement("div"), - body = document.getElementsByTagName("body")[0]; + div.style.width = div.style.paddingLeft = "1px"; - // Frameset documents with no body should not run this code - if ( !body ) { - return; - } + // We use our own, invisible, body + body = document.createElement( "body" ); + bodyStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0 + }; + for ( i in bodyStyle ) { + body.style[ i ] = bodyStyle[ i ]; + } + body.appendChild( div ); + document.documentElement.appendChild( body ); - div.style.width = div.style.paddingLeft = "1px"; - body.appendChild( div ); - jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + support.boxModel = div.offsetWidth === 2; - if ( "zoom" in div.style ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "
    "; - jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; - } + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
    "; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } - div.innerHTML = "
    t
    "; - var tds = div.getElementsByTagName("td"); + div.innerHTML = "
    t
    "; + tds = div.getElementsByTagName( "td" ); - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + isSupported = ( tds[ 0 ].offsetHeight === 0 ); - tds[0].style.display = ""; - tds[1].style.display = "none"; + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; - // Check if empty table cells still have offsetWidth/Height - // (IE < 8 fail this test) - jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; - div.innerHTML = ""; + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + div.innerHTML = ""; - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if ( document.defaultView && document.defaultView.getComputedStyle ) { - div.style.width = "1px"; - div.style.marginRight = "0"; - jQuery.support.reliableMarginRight = ( parseInt(document.defaultView.getComputedStyle(div, null).marginRight, 10) || 0 ) === 0; - } + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( document.defaultView && document.defaultView.getComputedStyle ) { + div.style.width = "1px"; + div.style.marginRight = "0"; + support.reliableMarginRight = + ( parseInt( document.defaultView.getComputedStyle(div).marginRight, 10 ) || 0 ) === 0; + } - body.removeChild( div ).style.display = "none"; - div = tds = null; - }); + // Remove the body element we added + document.documentElement.removeChild( body ); // Technique from Juriy Zaytsev // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - var eventSupported = function( eventName ) { - 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; + // 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 ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + } ) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; } - - var isSupported = (eventName in el); - if ( !isSupported ) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - return isSupported; - }; - - jQuery.support.submitBubbles = eventSupported("submit"); - jQuery.support.changeBubbles = eventSupported("change"); + } // release memory in IE - div = all = a = null; + body = div = all = a = tds = undefined; + + return support; })(); + +// Keep track of boxModel +jQuery.boxModel = jQuery.support.boxModel; + })( jQuery ); From 14193e449efe76105df61d745ebd7031dd7fe783 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Wed, 2 Feb 2011 21:57:44 -0500 Subject: [PATCH 25/33] Create jQuery.holdReady(true/false) method to encapsulate jQuery.readyWait++ / jQuery.ready(true) logic. Fix problem where jQuery.ready may trigger twice, causing the (unsupported) document.onready to run twice. Fixes #8803 . --- src/core.js | 18 +++++++++++------- test/data/readywaitloader.js | 6 +++--- test/readywait.html | 18 ++++++++++-------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/core.js b/src/core.js index a893fc9b..b81fa764 100644 --- a/src/core.js +++ b/src/core.js @@ -374,15 +374,19 @@ jQuery.extend({ // the ready event fires. See #6781 readyWait: 1, + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + // Handle when the DOM is ready ready: function( wait ) { - // A third-party is pushing the ready event forwards - if ( wait === true ) { - jQuery.readyWait--; - } - - // Make sure that the DOM is not already loaded - if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); diff --git a/test/data/readywaitloader.js b/test/data/readywaitloader.js index 483e07c4..e07dac7a 100644 --- a/test/data/readywaitloader.js +++ b/test/data/readywaitloader.js @@ -1,14 +1,14 @@ -// Simple script loader that uses jQuery.readyWait +// Simple script loader that uses jQuery.readyWait via jQuery.holdReady() //Hold on jQuery! -jQuery.readyWait++; +jQuery.holdReady(true); var readyRegExp = /^(complete|loaded)$/; function assetLoaded( evt ){ var node = evt.currentTarget || evt.srcElement; if ( evt.type === "load" || readyRegExp.test(node.readyState) ) { - jQuery.ready(true); + jQuery.holdReady(false); } } diff --git a/test/readywait.html b/test/readywait.html index 4f124767..b4d8111e 100644 --- a/test/readywait.html +++ b/test/readywait.html @@ -1,13 +1,13 @@ - jQuery.readyWait Test + jQuery.holdReady Test