*
* Loosely based on https://w3c.github.io/requestidlecallback/
*/
-( function ( mw, $ ) {
- var tasks = [],
- maxIdleDuration = 50,
- timeout = null;
-
- function schedule( trigger ) {
- clearTimeout( timeout );
- timeout = setTimeout( trigger, 700 );
- }
-
- function triggerIdle() {
- var elapsed,
- start = mw.now();
-
- while ( tasks.length ) {
- elapsed = mw.now() - start;
- if ( elapsed < maxIdleDuration ) {
- tasks.shift().callback();
- } else {
- // Idle moment expired, try again later
- schedule( triggerIdle );
- break;
- }
- }
- }
+( function ( mw ) {
+ var maxBusy = 50;
mw.requestIdleCallbackInternal = function ( callback ) {
- var task = { callback: callback };
- tasks.push( task );
-
- $( function () { schedule( triggerIdle ); } );
+ setTimeout( function () {
+ var start = mw.now();
+ callback( {
+ didTimeout: false,
+ timeRemaining: function () {
+ return Math.max( 0, maxBusy - ( mw.now() - start ) );
+ }
+ } );
+ }, 1 );
};
/**
* @param {Function} callback
*/
mw.requestIdleCallback = window.requestIdleCallback
- ? function ( callback ) {
- window.requestIdleCallback( callback );
- }
+ // Bind because it throws TypeError if context is not window
+ ? window.requestIdleCallback.bind( window )
: mw.requestIdleCallbackInternal;
}( mediaWiki, jQuery ) );
( function ( mw ) {
QUnit.module( 'mediawiki.requestIdleCallback', QUnit.newMwEnvironment( {
setup: function () {
- var time = mw.now(),
- clock = this.clock = this.sandbox.useFakeTimers();
+ var clock = this.clock = this.sandbox.useFakeTimers();
- this.tick = function ( forward ) {
- time += forward;
- clock.tick( forward );
- };
this.sandbox.stub( mw, 'now', function () {
- return time;
+ return +new Date();
} );
- // Don't test the native version (if available)
- this.mwRIC = mw.requestIdleCallback;
- mw.requestIdleCallback = mw.requestIdleCallbackInternal;
- },
- teardown: function () {
- mw.requestIdleCallback = this.mwRIC;
+ this.tick = function ( forward ) {
+ return clock.tick( forward || 1 );
+ };
+
+ // Always test the polyfill, not native
+ this.sandbox.stub( mw, 'requestIdleCallback', mw.requestIdleCallbackInternal );
}
} ) );
- // Basic scheduling of callbacks
- QUnit.test( 'callback', 3, function ( assert ) {
- var sequence,
- tick = this.tick;
+ QUnit.test( 'callback', function ( assert ) {
+ var sequence;
mw.requestIdleCallback( function () {
sequence.push( 'x' );
- tick( 30 );
} );
mw.requestIdleCallback( function () {
- tick( 5 );
sequence.push( 'y' );
- tick( 30 );
} );
- // Task Z is not run in the first sequence because the
- // first two tasks consumed the available 50ms budget.
mw.requestIdleCallback( function () {
sequence.push( 'z' );
- tick( 30 );
} );
sequence = [];
- tick( 1000 );
- assert.deepEqual( sequence, [ 'x', 'y' ] );
-
- sequence = [];
- tick( 1000 );
- assert.deepEqual( sequence, [ 'z' ] );
-
- sequence = [];
- tick( 1000 );
- assert.deepEqual( sequence, [] );
+ this.tick();
+ assert.deepEqual( sequence, [ 'x', 'y', 'z' ] );
} );
- // Schedule new callbacks within a callback that tick
- // the clock. If the budget is exceeded, the newly scheduled
- // task is delayed until the next idle period.
- QUnit.test( 'nest-tick', 3, function ( assert ) {
- var sequence,
- tick = this.tick;
+ QUnit.test( 'nested', function ( assert ) {
+ var sequence;
mw.requestIdleCallback( function () {
sequence.push( 'x' );
- tick( 30 );
} );
// Task Y is a task that schedules another task.
mw.requestIdleCallback( function () {
function other() {
sequence.push( 'y' );
- tick( 35 );
}
mw.requestIdleCallback( other );
} );
mw.requestIdleCallback( function () {
sequence.push( 'z' );
- tick( 30 );
} );
sequence = [];
- tick( 1000 );
+ this.tick();
assert.deepEqual( sequence, [ 'x', 'z' ] );
sequence = [];
- tick( 1000 );
+ this.tick();
assert.deepEqual( sequence, [ 'y' ] );
-
- sequence = [];
- tick( 1000 );
- assert.deepEqual( sequence, [] );
} );
- // Schedule new callbacks within a callback that run quickly.
- // Note how the newly scheduled task gets to run as part of the
- // current idle period (budget allowing).
- QUnit.test( 'nest-quick', 2, function ( assert ) {
+ QUnit.test( 'timeRemaining', function ( assert ) {
var sequence,
- tick = this.tick;
-
- mw.requestIdleCallback( function () {
- sequence.push( 'x' );
- mw.requestIdleCallback( function () {
- sequence.push( 'x-expand' );
- } );
- } );
- mw.requestIdleCallback( function () {
- sequence.push( 'y' );
+ tick = this.tick,
+ jobs = [
+ { time: 10, key: 'a' },
+ { time: 20, key: 'b' },
+ { time: 10, key: 'c' },
+ { time: 20, key: 'd' },
+ { time: 10, key: 'e' }
+ ];
+
+ mw.requestIdleCallback( function doWork( deadline ) {
+ var job;
+ while ( jobs[ 0 ] && deadline.timeRemaining() > 15 ) {
+ job = jobs.shift();
+ tick( job.time );
+ sequence.push( job.key );
+ }
+ if ( jobs[ 0 ] ) {
+ mw.requestIdleCallback( doWork );
+ }
} );
sequence = [];
- tick( 1000 );
- assert.deepEqual( sequence, [ 'x', 'y', 'x-expand' ] );
+ tick();
+ assert.deepEqual( sequence, [ 'a', 'b', 'c' ] );
sequence = [];
- tick( 1000 );
- assert.deepEqual( sequence, [] );
+ tick();
+ assert.deepEqual( sequence, [ 'd', 'e' ] );
} );
+ if ( window.requestIdleCallback ) {
+ QUnit.test( 'native', function ( assert ) {
+ var done = assert.async();
+ // Remove polyfill
+ mw.requestIdleCallback.restore();
+ mw.requestIdleCallback( function () {
+ assert.expect( 0 );
+ done();
+ } );
+ } );
+ }
+
}( mediaWiki ) );