diff --git a/Gruntfile.js b/Gruntfile.js
index 012ce95dba..3b6f0d476c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -108,6 +108,7 @@ module.exports = function( grunt ) {
"deprecated",
"dimensions",
"effects",
+ "effects-nostubs",
"event",
"manipulation",
"offset",
diff --git a/src/effects.js b/src/effects.js
index 46ad988ada..cc113683d0 100644
--- a/src/effects.js
+++ b/src/effects.js
@@ -27,24 +27,31 @@ var
rfxtypes = /^(?:toggle|show|hide)$/,
rrun = /queueHooks$/;
-function schedule() {
+function schedule( timestamp ) {
if ( inProgress ) {
- if ( document.hidden === false && window.requestAnimationFrame ) {
+ if ( document.hidden === false ) {
window.requestAnimationFrame( schedule );
} else {
window.setTimeout( schedule, jQuery.fx.interval );
}
- jQuery.fx.tick();
+ jQuery.fx.tick( timestamp );
}
}
+// We need to be using Date.now() or performance.now() consistently as they return different
+// values: performance.now() counter starts on page load.
+// Support: IE <10, Safari <8.0, iOS <9, Android <4.4, Node with jsdom 9.4
+function getTimestamp() {
+ return window.performance.now();
+}
+
// Animations created synchronously will run synchronously
function createFxNow() {
window.setTimeout( function() {
fxNow = undefined;
} );
- return ( fxNow = Date.now() );
+ return ( fxNow = getTimestamp() );
}
// Generate parameters to create a standard animation
@@ -644,12 +651,12 @@ jQuery.each( {
} );
jQuery.timers = [];
-jQuery.fx.tick = function() {
+jQuery.fx.tick = function( timestamp ) {
var timer,
i = 0,
timers = jQuery.timers;
- fxNow = Date.now();
+ fxNow = timestamp || getTimestamp();
for ( ; i < timers.length; i++ ) {
timer = timers[ i ];
diff --git a/test/data/effects/matchingTimestamps.html b/test/data/effects/matchingTimestamps.html
new file mode 100644
index 0000000000..2677b36707
--- /dev/null
+++ b/test/data/effects/matchingTimestamps.html
@@ -0,0 +1,38 @@
+
+
+
+
+ Effects: matching timestamps
+
+
+
+
+
+
+
+
diff --git a/test/unit/animation.js b/test/unit/animation.js
index 4af1f7f983..875c7a942d 100644
--- a/test/unit/animation.js
+++ b/test/unit/animation.js
@@ -5,10 +5,9 @@ if ( !jQuery.fx ) {
return;
}
-var oldRaf = window.requestAnimationFrame,
- defaultPrefilter = jQuery.Animation.prefilters[ 0 ],
- defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ],
- startTime = 505877050;
+var defaultPrefilter = jQuery.Animation.prefilters[ 0 ];
+var defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ];
+var startTime = 505877050;
// This module tests jQuery.Animation and the corresponding 1.8+ effects APIs
QUnit.module( "animation", {
@@ -26,7 +25,6 @@ QUnit.module( "animation", {
this.sandbox.restore();
jQuery.fx.stop();
jQuery.fx.interval = this._oldInterval;
- window.requestAnimationFrame = oldRaf;
return moduleTeardown.apply( this, arguments );
}
} );
diff --git a/test/unit/effects.js b/test/unit/effects.js
index 13190dc5bb..7b20b34774 100644
--- a/test/unit/effects.js
+++ b/test/unit/effects.js
@@ -5,29 +5,42 @@ if ( !jQuery.fx ) {
return;
}
-var oldRaf = window.requestAnimationFrame,
- hideOptions = {
- inline: function() { jQuery.style( this, "display", "none" ); },
- cascade: function() { this.className = "hidden"; }
- };
+var hideOptions = {
+ inline: function() { jQuery.style( this, "display", "none" ); },
+ cascade: function() { this.className = "hidden"; }
+};
QUnit.module( "effects", {
beforeEach: function() {
this.sandbox = sinon.createSandbox();
this.clock = this.sandbox.useFakeTimers( 505877050 );
+
this._oldInterval = jQuery.fx.interval;
- window.requestAnimationFrame = null;
jQuery.fx.step = {};
jQuery.fx.interval = 10;
+
+ // Simulate rAF using the mocked setTimeout as otherwise we couldn't test
+ // the rAF + performance.now code path.
+ this.sandbox
+ .stub( window, "requestAnimationFrame" )
+ .callsFake( function( callback ) {
+ return window.setTimeout( callback, jQuery.fx.interval,
+ Date.now() - 99989.6394 );
+ } );
+
+ // Fake performance.now() returning lower values than Date.now()
+ // and that its values are fractional.
+ this.sandbox.stub( window.performance, "now" ).callsFake( function() {
+ return Date.now() - 99999.6394;
+ } );
},
afterEach: function() {
this.sandbox.restore();
jQuery.fx.stop();
jQuery.fx.interval = this._oldInterval;
- window.requestAnimationFrame = oldRaf;
return moduleTeardown.apply( this, arguments );
}
-} );
+}, function() {
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "sanity check", function( assert ) {
assert.expect( 1 );
@@ -2623,4 +2636,21 @@ QUnit.test( "jQuery.speed() - durations", function( assert ) {
jQuery.fx.off = false;
} );
+} );
+
+QUnit.module( "effects-nostubs", function() {
+
+testIframe(
+ "matching timestamp",
+ "effects/matchingTimestamps.html",
+ function( assert, framejQuery, frameWin, frameDoc, success, failureReason ) {
+ assert.expect( 1 );
+
+ assert.ok( success, failureReason );
+ }
+);
+
+} );
+
} )();
+
diff --git a/test/unit/tween.js b/test/unit/tween.js
index 9367978774..2e77436c99 100644
--- a/test/unit/tween.js
+++ b/test/unit/tween.js
@@ -5,14 +5,11 @@ if ( !jQuery.fx ) {
return;
}
-var oldRaf = window.requestAnimationFrame;
-
QUnit.module( "tween", {
beforeEach: function() {
this.sandbox = sinon.createSandbox();
this.clock = this.sandbox.useFakeTimers( 505877050 );
this._oldInterval = jQuery.fx.interval;
- window.requestAnimationFrame = null;
jQuery.fx.step = {};
jQuery.fx.interval = 10;
},
@@ -20,7 +17,6 @@ QUnit.module( "tween", {
this.sandbox.restore();
jQuery.fx.stop();
jQuery.fx.interval = this._oldInterval;
- window.requestAnimationFrame = oldRaf;
return moduleTeardown.apply( this, arguments );
}
} );