Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

fixes #10996 (simplify offset) #642

Closed
wants to merge 11 commits into from

8 participants

@mikesherov
Collaborator

In all of jQuery's supported browsers, either getBoundingClientRect() or window.webkitConvertPointFromNodeToPage() exists, thereby eliminating the need for the old lengthy getOffset function. Good riddance!

http://bugs.jquery.com/ticket/10996

In case anyone is curious, and I know you are, here is the size diff:

  247053  (-2576) jquery.js
   92588  (-1462) jquery.min.js
   32937   (-448) jquery.min.js.gz
@mikesherov
Collaborator

Just one last thing, all 3 of my open pull requests will probably conflict with each other:

#639
#630

src/offset.js
((14 lines not shown))
try {
box = elem.getBoundingClientRect();
} catch(e) {}
// Make sure we're not dealing with a disconnected DOM node
if ( !box || !jQuery.contains( docElem, elem ) ) {
- return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+ return box ? { top: box.top, left: box.left } : noOffset;
@jaubourg Collaborator

I wouldn't do that. I know I happen to use the object returned here and modify it... better return a new object all the time.

@jaubourg Collaborator

Oh and merry christmas! :)

@mikesherov Collaborator

O.o

I completely forgot that the object would be by reference! Making the change shortly. And merry Christmas to you as well.

@gibson042 Collaborator

First: awesome job; bon voyage to manual getOffset!

Second: You can save another 8/10/2 bytes here with { top: box ? box.top : 0, left: box ? box.left : 0 }... or 19/14/2 with jQuery.extend( { top: 0, left: 0 }, box ) (but that would come with extraneous right and bottom properties).

@mikesherov Collaborator

I'm holding off on further changes until I can get some confirmation for jQuery Mobile team that this is safe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@laughinghan

is it just me or is this broken?

$ make
Building ./dist/jquery.js
Minifying jQuery ./dist/jquery.min.js
Checking jQuery against JSHint...
JSHint found errors.
 [L1562:C52] 'paddingMarginBorder' is not defined.
  div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>";

 [L1580:C13] 'marginDiv' is not defined.
  marginDiv = document.createElement( "div" );

 [L1581:C13] 'marginDiv' is not defined.
  marginDiv.style.width = "0";

 [L1582:C13] 'marginDiv' is not defined.
  marginDiv.style.marginRight = "0";

 [L1584:C30] 'marginDiv' is not defined.
  div.appendChild( marginDiv );

 [L1586:C56] 'marginDiv' is not defined.
  ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;

 [L1630:C9] 'marginDiv' is not defined.
  marginDiv = div = container = null;

jQuery Size - compared to last make
  250149  (-2738) jquery.js
   93502  (-1344) jquery.min.js
   33258   (-391) jquery.min.js.gz
jQuery build complete.
jaubourg and others added some commits
@jaubourg jaubourg Allows traditional options object for $.Callbacks flags. Fixes #11011…
…. Unit tests added.
7fa0da0
@jaubourg jaubourg Makes Deferred implementation truly Promise/A compliant. Unit tests a…
…mended. Actually few changes required in jQuery's own source and we gained 8 bytes minified gzipped \o/.
a41f240
@jaubourg jaubourg $.ajax now always returns an object implementing the Promise interfac…
…e. Fixes #10944. Unit tests amended.

For back-compat, in case of an early abort, callbacks passed in the options are not called (while subsequent callbacks attached to the returned Promise are).
For early abort triggered by returning false in beforeSend, statusText is "canceled".
395612b
@jaubourg jaubourg For much improved consistency, jqXHR.abort() sets a default statusTex…
…t of 'canceled' right until after beforeSend has been called (in which case it reverts to the default of 'abort'): now all early aborts have a statusText of 'canceled'.
914df9c
@jaubourg jaubourg Merge branch '1.8/#11010/Deferred' into 1.8pre 03c5836
@jaubourg jaubourg Merge branch '1.8/#11011/Callbacks' into 1.8pre 41056ab
@mikesherov mikesherov remove old offset code 8190ce5
@mikesherov mikesherov remove unneeded fixed position support test fb0af94
@mikesherov mikesherov remove unnecessary support tests 4aa9619
@mikesherov mikesherov removing unnecessary unit tests 9200c64
@mikesherov mikesherov function parity with getBoundingClientRect
return new object literal in all cases
style and JSHint fixes
b784967
@mikesherov mikesherov closed this
@briancavalier

Cool, it's great to see this headed toward Promises/A! I think it's gonna become the norm for multiple promise implementations to be in play in any reasonably interesting app. I found a situation where I believe it still isn't fully compliant, so I created a quick test case. It should show 3 for both jQuery and when.js, but the jQuery.Deferred seems never to resolve.

Collaborator

It seems to me that when.js wrongly calls the last resolve callback. I'd expect this to be the correct code and behaviour.

Hey @jaubourg,

I'm pretty sure the correct behavior is that the promise chain should return to resolving when a registered rejection handler does not explicitly propagate the rejection by either throwing or returning a rejected promise--i.e. it should behave similar to a catch statement. To propagate the exception, a catch statement must explicitly rethrow (or throw a new exception). If it doesn't explicitly (re)throw, the original exception is considered to be handled and doesn't propagate.

I've updated the original fiddle to include Q, which matches when.js's behavior. I also created a new fiddle that includes when.js, Q, and dojo (1.6, because it's easier to use in jsfiddle than 1.7, but the behavior is the same). Those show the same behavior, so I think jQuery is the outlier in this case.

Please don't take this the wrong way. Like I said, I'm really interested in trying to ensure that the most popular promise implementations are all interoperable, and hopefully this is a test case that will help.

Collaborator

Well, Brian, it's probably the first time I see jQuery being called the "outlier" rather than the nasty-hype-machine-that-will-devour-the-world (tm), so it's quite refreshing ;)

This use-case is probably yet another nail in the coffin of jQuery's Promise/A compliance. You probably followed the multiple discussions that took place about this. Equating exceptions and rejections is something we cannot do in a our environment: it makes debugging impossible. The metaphor is broken, btw, because, with Promise/A, exceptions are always handled (which is the gist of the problem). But let's not beat a dead horse.

Here is why I have a problem with rejection handlers changing state:

// Promise/A
promise.then( function() {}  ); // => resolved
promise.then( null, function() {}  ); // => resolved
promise.then( null, null, function() {}  ); // => pending

// jQuery
promise.then( function() {}  ); // => resolved
promise.then( null, function() {}  ); // => rejected
promise.then( null, null, function() {}  ); // => pending

"Redirecting" state has to be done explicitly in jQuery's implementation: it's explicit and it's symmetrical.

If people want us to write down a spec of jQuery's take on Promises, I'll try and find time to do so (though I'd love to get some help on this one). However, pushing jQuery's implementation forward to be fully Promise/A compliant is probably never gonna happen at this point. As minimal as Promise/A appeared to be, it has one contraint too many, at least for us.

I'm def not here to be a jQuery-hater, just trying to ensure some interoperability.

I disagree with your feelings about Promises/A, and I don't see Promises/A as having too many constraints at all. In fact, I see it as quite simple:

  1. If a handler returns successfully, the next promise is resolved.
  2. If a handler throws or returns a rejection, the next promise is rejected.

That said, I respect that every project has its own goals and constraints. If it doesn't make sense for jQuery to provide a Promises/A compliant promise, then I do think it's important to document its promise behavior.

Given the current (good) trend of more modular code, and moving more responsiblity (and therefore, code) to the browser-side, it's inevitable that applications will contain multiple promise implementations. Having an equalizer like when.js's when(), that can consume all of the above will help.

I think it would make sense for jQuery documentation to point people to when.js or Q as ways to enable promise interoperability.

Owner

@briancavalier Is "what you see" written down as part of Promises/A? It would help implementers if the proposal actually had a detailed description. The divergence in implementations seems to be a direct result of that.

If the promise is rejected and the user expects their rejected promise to be sent a complex object, what should our implementation pass for that case?

@dmethvin It is indeed part of Promises/A. From previous discussions, jQuery has decided not to interoperate with the following Promises/A clauses:

This function should return a new promise...

If the callback throws an error, the returned promise will be moved to failed state.

jQuery is the only implementation (among Q, when.js, WinJS, and Dojo) that diverges from these; implementations are otherwise convergent.

FWIW I've resigned myself to this, but second @briancavalier in that pointing people to Q/when.js/WinJS/Dojo, with their assimilation methods, would be good for those desiring interoperability.

I also support jQuery's current documentation tactic of advocating .done in place of .then, since their "thenables" are not Promises/A compliant as all others are. It's a reasonable compromise given the priorities of the jQuery project. (Personally I would favor deprecating jQuery's .then entirely, but @rwldrn was pretty harsh on me when I suggested that previously, so I assume it's a no-go.)

Aside: part of the problem, IMO, is that it's hard to spot these few crucial sentences in the middle of five paragraphs of prose spec-text. If only Promises/A had come with tests, then it could have been unambiguous what the requirements for conformance were. Too late now, sadly.

Owner

Sorry, if I had time I'd look at the other implementations and perhaps they'd answer this question. In the meantime, perhaps you know?

I am assuming that when the promise is moved to the failed state due to an exception being thrown, any "fail/rejected" handlers attached (or to-be-attached) are called. What arguments are they called with?

@dmethvin afaict, it's not specified, but @domenic's eye may be more keen than mine. The bit I'm looking at is:

If the callback throws an error, the returned promise will be moved to failed state

It seems reasonable, though, to use the thrown exception as the rejection reason/value and pass it as an arg to the rejection handlers. That's what when.js, Q, and Dojo do--they pass it as the one-and-only argument to rejection handlers.

Owner

@briancavalier, that seems like a problem. If I am explicitly failing a promise with .reject({ code: 42, msg: "not good", time: new Date() }) or perhaps some other custom error object, now the client needs to be aware that their promise can fail not only with that object but also some implicit error object or string. Promise/A doesn't seem to say anything about arguments so perhaps it's outside the scope. But in practical terms how would that be handled? Are we reduced to sniffing args and duck typing?

Aren't caught exceptions real Errors in all browsers?

@dmethvin Yes, as @briancavalier says, the handling of that case is only a de-facto standard. (I find this somewhat strange; pinging @kriskowal for any insight.) It is however quite important for building implementation-agnostic promise-consuming-and-producing methods; see my linked gist below.

As to your point, I think some context is helpful. In Promises/A, there is a direct parallel between sync exceptions and async rejections, just like there is a direct parallel between sync return values and async fulfillment values. So, just as synchronous code could receive multiple types of exceptions---e.g., a custom { code, msg, time } object, or a TypeError from someone calling something incorrectly, or a SyntaxError from bad JSON parsing---asynchronous code could indeed receive multiple types of rejections. So you would handle that the same way you do exceptions: if you sniff and duck-type exceptions, do the same for rejections.

Promises/A not only gives you this parallel, but it also completely removes exceptions from your thinking in favor of rejections, since they can be thought of as an async generalization. It seems you are anticipating rejections used as more of a signaling mechanism, where people often inspect their contents and use them to perform flow control logic. But for Promises/A-using code, that is not really the case. In such code they are used identically to exceptions, in that they are only caught and handled at the boundaries of systems. (E.g.: an Ajax library has no idea how to handle network errors, but my app does; my app has no idea how to handle TypeErrors, but my top-level error handler that tells the user "something went wrong" does.)

Here is a small example that applies these principles: https://gist.github.com/2936696

Thanks for taking the time to discuss and hope this helps answer your questions.

I'll add that from my perspective, @dmethvin's question is one of application design, and not of promises or exceptions. The data used as an error payload is, imho, separate from the mechanism that transports the error. Rejecting a promise with custom data is analogous to throwing that custom data. Same data, different transport. If your application design is one that uses custom data in that way, then, as @domenic described, Promises/A simply gives you an asynchronous transport that is analogous to the synchronous transport provided by throw. Any application-level logic built around the data would remain nearly identical when using Promises/A, as Domenic's gist shows.

@scottgonzalez it depends entirely on what was thrown. You can throw anything, and a catch statement higher up will receive it verbatim:

node -e 'try { throw "hello world"; } catch(message) { console.log(message, typeof message); }'

I'm certainly not advocating that in application code ;)

Owner

Let me try to make this more concrete, so I can tell whether this is a misunderstanding on my part or yours of the impact this would have to existing jQuery code.

jQuery wears several hats in this conversation: 1) An implementer of something that aspires to be Promise/A compliant. 2) A client of the current $.Deferred implementation, for example in $.ajax. and $.fn.animate. 3) A provider of the current $.Deferred implementation to jQuery developers.

Let's say someone has included jQuery in their web page and writes this code:

$.ajax(url).then(
    function(jqXHR) { /* do successful stuff */ },
    function(jqXHR, errorCode) { /* deal with failed request */ }
);

If some code or callback has a code error today, the browser throws an uncaught error with all sorts of detail which is visible on the console. It can also be caught by a window.onerror handler and sent back to a server for analysis using tools such as DamnIT, Errorception, AirBrake, Runtime Intelligence, or Google Analytics. For unanticipated script errors that fall into the "Never check for an error condition you don't know how to handle" category, this is by far the lowest-hassle way to go.

The proposal on the table as I understand it is to catch the error and pass it to the fail handler, which is expecting the (jqXHR, errorCode) it always got in the past but now has to deal with (Error) as well. The same goes for a developer's direct use of jQuery $.Deferred. So to handle a case that is supposedly a "de-facto standard" (of the Promise/A proposal which is not a standard) we should put on Steve's hat and tell everyone to go back and rewrite their code to accommodate this? Plus, it's again asking the developer to handle a totally unanticipated condition, and giving them less information than if they had let it get to the window.onerror handler or the browser's error console.

I have been talking with @jaubourg about how we might be able to change the semantics with a flag to make it do this, but even so this seems like it would have to be opt-in or we'd be breaking code all over the place. And to prevent further hassles, we should be recommending that all fail handlers take a single Error or Error-like argument unless the function is willing to sniff around (which seems incredibly ugly to me). Still, as large as the jQuery ecosystem is, there would be a mix of old-behavior and new-behavior code out there for years to come.

Is it clear now why we can't just change the fundamental behavior of our promise implementation? Or is there some easy way out? If the issue is simply that we're not really Promise/A and can't become so without breaking user code, one option would simply be to publicize that our promises implementation isn't interoperable and explain why we placed user code compatibility over interoperability.

Ok, I think it's time step back for a moment and remember how this thread started by rereading the commit message and my initial comment.

The stated intent of the commit was for jQuery Deferred to be made Promises/A compliant. I came upon it, and was very happy to see this. In an effort to try to help, I supplied a test case where post-this-commit, it isn't compliant. I think it's also important to note that both @domenic and I have said that we respect that jQuery has it's own goals and priorities, like every project. Neither of us has in any way demanded that jQuery Deferred become Promises/A. The intent to do so was implied by the commit.

If Promises/A is not a desirable goal of jQuery Deferred, I respect that. Unfortunately, I believe there is pain for developers either way.

My original suggestion was for jQuery documentation to point developers who need or desire promise interoperability to when.js or Q, as their when() functions can provide it.

And @jaubourg suggested documenting jQuery's promise behavior.

I stand by both of those as reasonable suggestions that would benefit jQuery, implementors of other libraries, and the larger Javascript developer community.

@briancavalier basically said everything I would want to say. I just jumped in to help educate and clarify, not to recommend Promises/A interoperation---I assumed that battle was already lost. As you say, @dmethvin, there's really no easy way out.

The only thing I would argue for, that nobody else seems to be behind, is dropping then completely so that jQuery properly fails the Promises/A duck-type test. This makes a lot of sense to me: as of 1.7, it's just a less powerful pipe, and most examples use and encourage done anyway. So perhaps deprecate in 1.8, remove in 1.9. But it's not that hard to deal with anyway, so whatevs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 1, 2012
  1. @jaubourg
  2. @jaubourg

    Makes Deferred implementation truly Promise/A compliant. Unit tests a…

    jaubourg authored
    …mended. Actually few changes required in jQuery's own source and we gained 8 bytes minified gzipped \o/.
  3. @jaubourg

    $.ajax now always returns an object implementing the Promise interfac…

    jaubourg authored
    …e. Fixes #10944. Unit tests amended.
    
    For back-compat, in case of an early abort, callbacks passed in the options are not called (while subsequent callbacks attached to the returned Promise are).
    For early abort triggered by returning false in beforeSend, statusText is "canceled".
Commits on Apr 2, 2012
  1. @jaubourg

    For much improved consistency, jqXHR.abort() sets a default statusTex…

    jaubourg authored
    …t of 'canceled' right until after beforeSend has been called (in which case it reverts to the default of 'abort'): now all early aborts have a statusText of 'canceled'.
Commits on Apr 3, 2012
  1. @jaubourg
  2. @jaubourg
  3. @mikesherov

    remove old offset code

    mikesherov authored
  4. @mikesherov
  5. @mikesherov
  6. @mikesherov
  7. @mikesherov

    function parity with getBoundingClientRect

    mikesherov authored
    return new object literal in all cases
    style and JSHint fixes
This page is out of date. Refresh to see the latest.
View
3  build/jshint-check.js
@@ -12,7 +12,8 @@
smarttabs: true,
predef: [
"define",
- "DOMParser"
+ "DOMParser",
+ "WebKitPoint",
],
maxerr: 100
};
View
16 src/ajax.js
@@ -426,6 +426,8 @@ jQuery.extend({
fireGlobals,
// Loop variable
i,
+ // Default abort message
+ strAbort = "canceled",
// Fake xhr
jqXHR = {
@@ -471,7 +473,7 @@ jQuery.extend({
// Cancel the request
abort: function( statusText ) {
- statusText = statusText || "abort";
+ statusText = statusText || strAbort;
if ( transport ) {
transport.abort( statusText );
}
@@ -609,7 +611,7 @@ jQuery.extend({
}
} else {
tmp = map[ jqXHR.status ];
- jqXHR.then( tmp, tmp );
+ jqXHR.always( tmp );
}
}
return this;
@@ -643,7 +645,7 @@ jQuery.extend({
// If request was aborted inside a prefilter, stop there
if ( state === 2 ) {
- return false;
+ return jqXHR;
}
// We can fire global events as of now if asked to
@@ -716,12 +718,14 @@ jQuery.extend({
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
- // Abort if not done already
- jqXHR.abort();
- return false;
+ // Abort if not done already and return
+ return jqXHR.abort();
}
+ // aborting is no longer a cancelation
+ strAbort = "abort";
+
// Install callbacks on deferreds
for ( i in { success: 1, error: 1, complete: 1 } ) {
jqXHR[ i ]( s[ i ] );
View
4 src/callbacks.js
@@ -38,9 +38,9 @@ function createFlags( flags ) {
*/
jQuery.Callbacks = function( flags ) {
- // Convert flags from String-formatted to Object-formatted
+ // Convert flags from String-formatted to Object-formatted if needed
// (we check in cache first)
- flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+ flags = typeof flags === "string" ? ( flagsCache[ flags ] || createFlags( flags ) ) : ( flags || {} );
var // Actual callback list
list = [],
View
18 src/deferred.js
@@ -28,15 +28,11 @@ jQuery.extend({
isResolved: doneList.fired,
isRejected: failList.fired,
- then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
- deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
- return this;
- },
always: function() {
deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
return this;
},
- pipe: function( fnDone, fnFail, fnProgress ) {
+ then: function( fnDone, fnFail, fnProgress ) {
return jQuery.Deferred(function( newDefer ) {
jQuery.each( {
done: [ fnDone, "resolve" ],
@@ -50,7 +46,7 @@ jQuery.extend({
deferred[ handler ](function() {
returned = fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ returned.promise().done( newDefer.resolve ).fail( newDefer.reject ).progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
}
@@ -74,9 +70,15 @@ jQuery.extend({
return obj;
}
},
- deferred = promise.promise({}),
+ deferred,
key;
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Construct deferred
+ deferred = promise.promise({});
+
for ( key in lists ) {
deferred[ key ] = lists[ key ].fire;
deferred[ key + "With" ] = lists[ key ].fireWith;
@@ -127,7 +129,7 @@ jQuery.extend({
if ( length > 1 ) {
for ( ; i < length; i++ ) {
if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
- args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ args[ i ].promise().done( resolveFunc(i) ).fail( deferred.reject ).progress( progressFunc(i) );
} else {
--count;
}
View
57 src/offset.js
@@ -1,11 +1,12 @@
(function( jQuery ) {
var getOffset,
- rtable = /^t(?:able|d|h)$/i,
rroot = /^(?:body|html)$/i;
if ( "getBoundingClientRect" in document.documentElement ) {
- getOffset = function( elem, doc, docElem, box ) {
+ getOffset = function( elem, doc, docElem ) {
+ var box;
+
try {
box = elem.getBoundingClientRect();
} catch(e) {}
@@ -29,56 +30,12 @@ if ( "getBoundingClientRect" in document.documentElement ) {
} else {
getOffset = function( elem, doc, docElem ) {
- var computedStyle,
- offsetParent = elem.offsetParent,
- prevOffsetParent = elem,
- body = doc.body,
- defaultView = doc.defaultView,
- prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
- top = elem.offsetTop,
- left = elem.offsetLeft;
-
- while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
- if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
- break;
- }
-
- computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
- top -= elem.scrollTop;
- left -= elem.scrollLeft;
-
- if ( elem === offsetParent ) {
- top += elem.offsetTop;
- left += elem.offsetLeft;
-
- if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevOffsetParent = offsetParent;
- offsetParent = elem.offsetParent;
- }
-
- if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevComputedStyle = computedStyle;
- }
-
- if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
- top += body.offsetTop;
- left += body.offsetLeft;
- }
-
- if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
- top += Math.max( docElem.scrollTop, body.scrollTop );
- left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return { top: 0, left: 0 };
}
+ var point = getWindow( doc ).webkitConvertPointFromNodeToPage( elem, new WebKitPoint( 0, 0 ) );
+ return { top: point.y, left: point.x };
- return { top: top, left: left };
};
}
View
39 src/support.js
@@ -178,9 +178,8 @@ jQuery.support = (function() {
// Run tests that need a body at doc ready
jQuery(function() {
- var container, outer, inner, table, td, offsetSupport,
- marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
- paddingMarginBorderVisibility, paddingMarginBorder,
+ var container, offsetSupport,
+ conMarginTop = 1,
body = document.getElementsByTagName("body")[0];
if ( !body ) {
@@ -188,17 +187,8 @@ jQuery.support = (function() {
return;
}
- conMarginTop = 1;
- paddingMarginBorder = "padding:0;margin:0;border:";
- positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
- paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
- style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
- html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
- "<table " + style + "' cellpadding='0' cellspacing='0'>" +
- "<tr><td></td></tr></table>";
-
container = document.createElement("div");
- container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
body.insertBefore( container, body.firstChild );
// Construct the test element
@@ -260,31 +250,10 @@ jQuery.support = (function() {
support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
}
- div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
- div.innerHTML = html;
-
- outer = div.firstChild;
- inner = outer.firstChild;
- td = outer.nextSibling.firstChild.firstChild;
-
offsetSupport = {
- doesNotAddBorder: ( inner.offsetTop !== 5 ),
- doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ doesNotIncludeMarginInBodyOffset: ( body.offsetTop !== conMarginTop )
};
- inner.style.position = "fixed";
- inner.style.top = "20px";
-
- // safari subtracts parent border width here which is 5px
- offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
- inner.style.position = inner.style.top = "";
-
- outer.style.overflow = "hidden";
- outer.style.position = "relative";
-
- offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
- offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
-
if ( window.getComputedStyle ) {
div.style.marginTop = "1%";
support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
View
19 test/unit/ajax.js
@@ -857,7 +857,7 @@ test("jQuery.ajax - beforeSend", function() {
test("jQuery.ajax - beforeSend, cancel request (#2688)", function() {
expect(2);
- var request = jQuery.ajax({
+ jQuery.ajax({
url: url("data/name.html"),
beforeSend: function() {
ok( true, "beforeSend got called, canceling" );
@@ -872,13 +872,14 @@ test("jQuery.ajax - beforeSend, cancel request (#2688)", function() {
error: function() {
ok( false, "request didn't get canceled" );
}
+ }).fail(function( _, reason ) {
+ strictEqual( reason, "canceled", "canceled request must fail with 'canceled' status text" );
});
- ok( request === false, "canceled request must return false instead of XMLHttpRequest instance" );
});
test("jQuery.ajax - beforeSend, cancel request manually", function() {
expect(2);
- var request = jQuery.ajax({
+ jQuery.ajax({
url: url("data/name.html"),
beforeSend: function(xhr) {
ok( true, "beforeSend got called, canceling" );
@@ -893,8 +894,9 @@ test("jQuery.ajax - beforeSend, cancel request manually", function() {
error: function() {
ok( false, "request didn't get canceled" );
}
+ }).fail(function( _, reason ) {
+ strictEqual( reason, "canceled", "manually canceled request must fail with 'canceled' status text" );
});
- ok( request === false, "canceled request must return false instead of XMLHttpRequest instance" );
});
window.foobar = null;
@@ -2109,13 +2111,14 @@ test( "jQuery.ajax - Context with circular references (#9887)", 2, function () {
context = {};
context.field = context;
try {
- success = !jQuery.ajax( "non-existing", {
+ jQuery.ajax( "non-existing", {
context: context,
beforeSend: function() {
ok( this === context, "context was not deep extended" );
return false;
}
});
+ success = true;
} catch (e) { console.log( e ); }
ok( success, "context with circular reference did not generate an exception" );
});
@@ -2315,12 +2318,14 @@ test("jQuery.ajax - abort in prefilter", function() {
}
});
- strictEqual( jQuery.ajax({
+ jQuery.ajax({
abortInPrefilter: true,
error: function() {
ok( false, "error callback called" );
}
- }), false, "Request was properly aborted early by the prefilter" );
+ }).fail(function( _, reason ) {
+ strictEqual( reason, 'canceled', "Request aborted by the prefilter must fail with 'canceled' status text" );
+ });
});
View
292 test/unit/callbacks.js
@@ -33,149 +33,171 @@ var output,
}
};
-jQuery.each( tests, function( flags, resultString ) {
-
- jQuery.each( filters, function( filterLabel, filter ) {
-
- test( "jQuery.Callbacks( \"" + flags + "\" ) - " + filterLabel, function() {
+ function showFlags( flags ) {
+ if ( typeof flags === "string" ) {
+ return '"' + flags + '"';
+ }
+ var output = [], key;
+ for ( key in flags ) {
+ output.push( '"' + key + '": ' + flags[ key ] );
+ }
+ return "{ " + output.join( ", " ) + " }";
+ }
- expect( 20 );
+jQuery.each( tests, function( strFlags, resultString ) {
- // Give qunit a little breathing room
- stop();
- setTimeout( start, 0 );
+ var objectFlags = {};
- var cblist;
- results = resultString.split( /\s+/ );
+ jQuery.each( strFlags.split( " " ), function() {
+ if ( this.length ) {
+ objectFlags[ this ] = true;
+ }
+ });
- // Basic binding and firing
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add(function( str ) {
- output += str;
- });
- cblist.fire( "A" );
- strictEqual( output, "XA", "Basic binding and firing" );
- strictEqual( cblist.fired(), true, ".fired() detects firing" );
- output = "X";
- cblist.disable();
- cblist.add(function( str ) {
- output += str;
- });
- strictEqual( output, "X", "Adding a callback after disabling" );
- cblist.fire( "A" );
- strictEqual( output, "X", "Firing after disabling" );
-
- // Basic binding and firing (context, arguments)
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add(function() {
- equal( this, window, "Basic binding and firing (context)" );
- output += Array.prototype.join.call( arguments, "" );
- });
- cblist.fireWith( window, [ "A", "B" ] );
- strictEqual( output, "XAB", "Basic binding and firing (arguments)" );
-
- // fireWith with no arguments
- output = "";
- cblist = jQuery.Callbacks( flags );
- cblist.add(function() {
- equal( this, window, "fireWith with no arguments (context is window)" );
- strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" );
- });
- cblist.fireWith();
-
- // Basic binding, removing and firing
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add( outputA, outputB, outputC );
- cblist.remove( outputB, outputC );
- cblist.fire();
- strictEqual( output, "XA", "Basic binding, removing and firing" );
-
- // Empty
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add( outputA );
- cblist.add( outputB );
- cblist.add( outputC );
- cblist.empty();
- cblist.fire();
- strictEqual( output, "X", "Empty" );
-
- // Locking
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add( function( str ) {
- output += str;
- });
- cblist.lock();
- cblist.add( function( str ) {
- output += str;
- });
- cblist.fire( "A" );
- cblist.add( function( str ) {
- output += str;
- });
- strictEqual( output, "X", "Lock early" );
+ jQuery.each( filters, function( filterLabel, filter ) {
- // Ordering
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add( function() {
+ jQuery.each( { "string": strFlags, "object": objectFlags }, function( flagsTypes, flags ) {
+
+ test( "jQuery.Callbacks( " + showFlags( flags ) + " ) - " + filterLabel, function() {
+
+ expect( 20 );
+
+ // Give qunit a little breathing room
+ stop();
+ setTimeout( start, 0 );
+
+ var cblist;
+ results = resultString.split( /\s+/ );
+
+ // Basic binding and firing
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add(function( str ) {
+ output += str;
+ });
+ cblist.fire( "A" );
+ strictEqual( output, "XA", "Basic binding and firing" );
+ strictEqual( cblist.fired(), true, ".fired() detects firing" );
+ output = "X";
+ cblist.disable();
+ cblist.add(function( str ) {
+ output += str;
+ });
+ strictEqual( output, "X", "Adding a callback after disabling" );
+ cblist.fire( "A" );
+ strictEqual( output, "X", "Firing after disabling" );
+
+ // Basic binding and firing (context, arguments)
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add(function() {
+ equal( this, window, "Basic binding and firing (context)" );
+ output += Array.prototype.join.call( arguments, "" );
+ });
+ cblist.fireWith( window, [ "A", "B" ] );
+ strictEqual( output, "XAB", "Basic binding and firing (arguments)" );
+
+ // fireWith with no arguments
+ output = "";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add(function() {
+ equal( this, window, "fireWith with no arguments (context is window)" );
+ strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" );
+ });
+ cblist.fireWith();
+
+ // Basic binding, removing and firing
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add( outputA, outputB, outputC );
+ cblist.remove( outputB, outputC );
+ cblist.fire();
+ strictEqual( output, "XA", "Basic binding, removing and firing" );
+
+ // Empty
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add( outputA );
+ cblist.add( outputB );
cblist.add( outputC );
- outputA();
- }, outputB );
- cblist.fire();
- strictEqual( output, results.shift(), "Proper ordering" );
-
- // Add and fire again
- output = "X";
- cblist.add( function() {
+ cblist.empty();
+ cblist.fire();
+ strictEqual( output, "X", "Empty" );
+
+ // Locking
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add( function( str ) {
+ output += str;
+ });
+ cblist.lock();
+ cblist.add( function( str ) {
+ output += str;
+ });
+ cblist.fire( "A" );
+ cblist.add( function( str ) {
+ output += str;
+ });
+ strictEqual( output, "X", "Lock early" );
+
+ // Ordering
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add( function() {
+ cblist.add( outputC );
+ outputA();
+ }, outputB );
+ cblist.fire();
+ strictEqual( output, results.shift(), "Proper ordering" );
+
+ // Add and fire again
+ output = "X";
+ cblist.add( function() {
+ cblist.add( outputC );
+ outputA();
+ }, outputB );
+ strictEqual( output, results.shift(), "Add after fire" );
+
+ output = "X";
+ cblist.fire();
+ strictEqual( output, results.shift(), "Fire again" );
+
+ // Multiple fire
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add( function( str ) {
+ output += str;
+ } );
+ cblist.fire( "A" );
+ strictEqual( output, "XA", "Multiple fire (first fire)" );
+ output = "X";
+ cblist.add( function( str ) {
+ output += str;
+ } );
+ strictEqual( output, results.shift(), "Multiple fire (first new callback)" );
+ output = "X";
+ cblist.fire( "B" );
+ strictEqual( output, results.shift(), "Multiple fire (second fire)" );
+ output = "X";
+ cblist.add( function( str ) {
+ output += str;
+ } );
+ strictEqual( output, results.shift(), "Multiple fire (second new callback)" );
+
+ // Return false
+ output = "X";
+ cblist = jQuery.Callbacks( flags );
+ cblist.add( outputA, function() { return false; }, outputB );
+ cblist.add( outputA );
+ cblist.fire();
+ strictEqual( output, results.shift(), "Callback returning false" );
+
+ // Add another callback (to control lists with memory do not fire anymore)
+ output = "X";
cblist.add( outputC );
- outputA();
- }, outputB );
- strictEqual( output, results.shift(), "Add after fire" );
-
- output = "X";
- cblist.fire();
- strictEqual( output, results.shift(), "Fire again" );
-
- // Multiple fire
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add( function( str ) {
- output += str;
- } );
- cblist.fire( "A" );
- strictEqual( output, "XA", "Multiple fire (first fire)" );
- output = "X";
- cblist.add( function( str ) {
- output += str;
- } );
- strictEqual( output, results.shift(), "Multiple fire (first new callback)" );
- output = "X";
- cblist.fire( "B" );
- strictEqual( output, results.shift(), "Multiple fire (second fire)" );
- output = "X";
- cblist.add( function( str ) {
- output += str;
- } );
- strictEqual( output, results.shift(), "Multiple fire (second new callback)" );
-
- // Return false
- output = "X";
- cblist = jQuery.Callbacks( flags );
- cblist.add( outputA, function() { return false; }, outputB );
- cblist.add( outputA );
- cblist.fire();
- strictEqual( output, results.shift(), "Callback returning false" );
-
- // Add another callback (to control lists with memory do not fire anymore)
- output = "X";
- cblist.add( outputC );
- strictEqual( output, results.shift(), "Adding a callback after one returned false" );
+ strictEqual( output, results.shift(), "Adding a callback after one returned false" );
+ });
});
});
});
View
70 test/unit/deferred.js
@@ -8,32 +8,36 @@ jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
test("jQuery.Deferred" + withNew, function() {
- expect( 22 );
+ expect( 23 );
- createDeferred().resolve().then( function() {
+ var defer = createDeferred();
+
+ strictEqual( defer.pipe, defer.then, "pipe is an alias of then" );
+
+ createDeferred().resolve().done(function() {
ok( true , "Success on resolve" );
ok( this.isResolved(), "Deferred is resolved" );
strictEqual( this.state(), "resolved", "Deferred is resolved (state)" );
- }, function() {
+ }).fail(function() {
ok( false , "Error on resolve" );
- }).always( function() {
+ }).always(function() {
ok( true , "Always callback on resolve" );
});
- createDeferred().reject().then( function() {
+ createDeferred().reject().done(function() {
ok( false , "Success on reject" );
- }, function() {
+ }).fail(function() {
ok( true , "Error on reject" );
ok( this.isRejected(), "Deferred is rejected" );
strictEqual( this.state(), "rejected", "Deferred is rejected (state)" );
- }).always( function() {
+ }).always(function() {
ok( true , "Always callback on reject" );
});
- createDeferred( function( defer ) {
+ createDeferred(function( defer ) {
ok( this === defer , "Defer passed as this & first argument" );
this.resolve( "done" );
- }).then( function( value ) {
+ }).done( function( value ) {
strictEqual( value , "done" , "Passed function executed" );
});
@@ -58,7 +62,7 @@ jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
test( "jQuery.Deferred - chainability", function() {
- var methods = "resolve reject notify resolveWith rejectWith notifyWith done fail progress then always".split( " " ),
+ var methods = "resolve reject notify resolveWith rejectWith notifyWith done fail progress always".split( " " ),
defer = jQuery.Deferred();
expect( methods.length );
@@ -69,12 +73,12 @@ test( "jQuery.Deferred - chainability", function() {
});
});
-test( "jQuery.Deferred.pipe - filtering (done)", function() {
+test( "jQuery.Deferred.then - filtering (done)", function() {
expect(4);
var defer = jQuery.Deferred(),
- piped = defer.pipe(function( a, b ) {
+ piped = defer.then(function( a, b ) {
return a * b;
}),
value1,
@@ -96,21 +100,21 @@ test( "jQuery.Deferred.pipe - filtering (done)", function() {
strictEqual( value2, 3, "second resolve value ok" );
strictEqual( value3, 6, "result of filter ok" );
- jQuery.Deferred().reject().pipe(function() {
- ok( false, "pipe should not be called on reject" );
+ jQuery.Deferred().reject().then(function() {
+ ok( false, "then should not be called on reject" );
});
- jQuery.Deferred().resolve().pipe( jQuery.noop ).done(function( value ) {
- strictEqual( value, undefined, "pipe done callback can return undefined/null" );
+ jQuery.Deferred().resolve().then( jQuery.noop ).done(function( value ) {
+ strictEqual( value, undefined, "then done callback can return undefined/null" );
});
});
-test( "jQuery.Deferred.pipe - filtering (fail)", function() {
+test( "jQuery.Deferred.then - filtering (fail)", function() {
expect(4);
var defer = jQuery.Deferred(),
- piped = defer.pipe( null, function( a, b ) {
+ piped = defer.then( null, function( a, b ) {
return a * b;
} ),
value1,
@@ -132,21 +136,21 @@ test( "jQuery.Deferred.pipe - filtering (fail)", function() {
strictEqual( value2, 3, "second reject value ok" );
strictEqual( value3, 6, "result of filter ok" );
- jQuery.Deferred().resolve().pipe( null, function() {
- ok( false, "pipe should not be called on resolve" );
+ jQuery.Deferred().resolve().then( null, function() {
+ ok( false, "then should not be called on resolve" );
} );
- jQuery.Deferred().reject().pipe( null, jQuery.noop ).fail(function( value ) {
- strictEqual( value, undefined, "pipe fail callback can return undefined/null" );
+ jQuery.Deferred().reject().then( null, jQuery.noop ).fail(function( value ) {
+ strictEqual( value, undefined, "then fail callback can return undefined/null" );
});
});
-test( "jQuery.Deferred.pipe - filtering (progress)", function() {
+test( "jQuery.Deferred.then - filtering (progress)", function() {
expect(3);
var defer = jQuery.Deferred(),
- piped = defer.pipe( null, null, function( a, b ) {
+ piped = defer.then( null, null, function( a, b ) {
return a * b;
} ),
value1,
@@ -169,12 +173,12 @@ test( "jQuery.Deferred.pipe - filtering (progress)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "jQuery.Deferred.pipe - deferred (done)", function() {
+test( "jQuery.Deferred.then - deferred (done)", function() {
expect(3);
var defer = jQuery.Deferred(),
- piped = defer.pipe(function( a, b ) {
+ piped = defer.then(function( a, b ) {
return jQuery.Deferred(function( defer ) {
defer.reject( a * b );
});
@@ -199,12 +203,12 @@ test( "jQuery.Deferred.pipe - deferred (done)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "jQuery.Deferred.pipe - deferred (fail)", function() {
+test( "jQuery.Deferred.then - deferred (fail)", function() {
expect(3);
var defer = jQuery.Deferred(),
- piped = defer.pipe( null, function( a, b ) {
+ piped = defer.then( null, function( a, b ) {
return jQuery.Deferred(function( defer ) {
defer.resolve( a * b );
});
@@ -229,12 +233,12 @@ test( "jQuery.Deferred.pipe - deferred (fail)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "jQuery.Deferred.pipe - deferred (progress)", function() {
+test( "jQuery.Deferred.then - deferred (progress)", function() {
expect(3);
var defer = jQuery.Deferred(),
- piped = defer.pipe( null, null, function( a, b ) {
+ piped = defer.then( null, null, function( a, b ) {
return jQuery.Deferred(function( defer ) {
defer.resolve( a * b );
});
@@ -259,13 +263,13 @@ test( "jQuery.Deferred.pipe - deferred (progress)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "jQuery.Deferred.pipe - context", function() {
+test( "jQuery.Deferred.then - context", function() {
expect(4);
var context = {};
- jQuery.Deferred().resolveWith( context, [ 2 ] ).pipe(function( value ) {
+ jQuery.Deferred().resolveWith( context, [ 2 ] ).then(function( value ) {
return value * 3;
}).done(function( value ) {
strictEqual( this, context, "custom context correctly propagated" );
@@ -273,7 +277,7 @@ test( "jQuery.Deferred.pipe - context", function() {
});
var defer = jQuery.Deferred(),
- piped = defer.pipe(function( value ) {
+ piped = defer.then(function( value ) {
return value * 3;
});
View
24 test/unit/support.js
@@ -98,10 +98,6 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"reliableHiddenOffsets":true,
"ajax":true,
"cors":true,
- "doesNotAddBorder":true,
- "doesAddBorderForTableAndCells":false,
- "fixedPosition":true,
- "subtractsBorderForOverflowNotVisible":false,
"doesNotIncludeMarginInBodyOffset":true
};
for ( i in expected ) {
@@ -141,10 +137,6 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"reliableHiddenOffsets":false,
"ajax":true,
"cors":false,
- "doesNotAddBorder":false,
- "doesAddBorderForTableAndCells":true,
- "fixedPosition":true,
- "subtractsBorderForOverflowNotVisible":false,
"doesNotIncludeMarginInBodyOffset":true
};
for ( i in expected ) {
@@ -164,11 +156,8 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"cors": false,
"cssFloat": false,
"deleteExpando": false,
- "doesAddBorderForTableAndCells": true,
- "doesNotAddBorder": true,
"doesNotIncludeMarginInBodyOffset": true,
"enctype": true,
- "fixedPosition": true,
"focusinBubbles": true,
"getSetAttribute": false,
"hrefNormalized": false,
@@ -186,7 +175,6 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"reliableMarginRight": true,
"shrinkWrapBlocks": false,
"submitBubbles": false,
- "subtractsBorderForOverflowNotVisible": false,
"tbody": false,
"style": false
};
@@ -227,10 +215,6 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"reliableHiddenOffsets":false,
"ajax":true,
"cors":false,
- "doesNotAddBorder":true,
- "doesAddBorderForTableAndCells":true,
- "fixedPosition":false,
- "subtractsBorderForOverflowNotVisible":false,
"doesNotIncludeMarginInBodyOffset":true
};
for ( i in expected ) {
@@ -270,10 +254,6 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"reliableHiddenOffsets":true,
"ajax":true,
"cors":true,
- "doesNotAddBorder":true,
- "doesAddBorderForTableAndCells":false,
- "fixedPosition":true,
- "subtractsBorderForOverflowNotVisible":false,
"doesNotIncludeMarginInBodyOffset":true
};
for ( i in expected ) {
@@ -313,10 +293,6 @@ if ( /chrome\/16\.0/i.test(userAgent) ) {
"reliableHiddenOffsets":true,
"ajax":true,
"cors":true,
- "doesNotAddBorder":true,
- "doesAddBorderForTableAndCells":true,
- "fixedPosition":true,
- "subtractsBorderForOverflowNotVisible":false,
"doesNotIncludeMarginInBodyOffset":true
};
for ( i in expected ) {
Something went wrong with that request. Please try again.