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 ); } } );