startup: Add support for module-requirement to RLQ
authorTimo Tijhof <krinklemail@gmail.com>
Tue, 7 Aug 2018 17:28:44 +0000 (18:28 +0100)
committerTimo Tijhof <krinklemail@gmail.com>
Tue, 7 Aug 2018 18:34:21 +0000 (19:34 +0100)
* Add more inline documentation.

* Use [0] instead of needless 'length'. (Optimisation)

* Don't lazy-create NORLQ as empty array only to dereference
  it two statements later. (Optimisation)

* Add support for a secondary signature to RLQ.push.

  The existing signature is a plain callback function that is
  execute immediately after startup.

  The new signature is an array of `[ string|Array, Function ]`
  where the first value is required module(s), and
  the second value is the callback function.

  startup.js will leave the second form in the array, processing
  them instead in 'mediawiki.base'.

This change is needed before I17cd13dffebd6a (T192623), which
will remove the undocumented behaviour of RLQ callbacks firing
only after base modules arrive, which means we need to provide
authors of inline scripts an alternate means of scheduling
callbacks for after 'jquery' and/or 'mediawiki.base' arrive.

Bug: T192623
Change-Id: Ica7bb9c3bdb77d96ca2e01530a76f7ca448acdb8

resources/src/startup/startup.js
tests/qunit/suites/resources/mediawiki/mediawiki.base.test.js

index 7a50d94..c609852 100644 (file)
@@ -3,7 +3,7 @@
  *
  * - Beware: This file MUST parse without errors on even the most ancient of browsers!
  */
-
+/* eslint-disable vars-on-top, no-unmodified-loop-condition */
 /* global mw, isCompatible, $VARS, $CODE */
 
 /**
@@ -79,17 +79,17 @@ window.isCompatible = function ( str ) {
        );
 };
 
-// Conditional script injection
 ( function () {
        var NORLQ, script;
+       // Handle Grade C
        if ( !isCompatible() ) {
-               // Undo class swapping in case of an unsupported browser.
-               // See ResourceLoaderClientHtml::getDocumentAttributes().
+               // Undo speculative Grade A <html> class. See ResourceLoaderClientHtml::getDocumentAttributes().
                document.documentElement.className = document.documentElement.className
                        .replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' );
 
-               NORLQ = window.NORLQ || [];
-               while ( NORLQ.length ) {
+               // Process any callbacks for Grade C
+               NORLQ = window.NORLQ;
+               while ( NORLQ && NORLQ[ 0 ] ) {
                        NORLQ.shift()();
                }
                window.NORLQ = {
@@ -98,9 +98,8 @@ window.isCompatible = function ( str ) {
                        }
                };
 
-               // Clear and disable the other queue
+               // Clear and disable the Grade A queue
                window.RLQ = {
-                       // No-op
                        push: function () {}
                };
 
@@ -117,22 +116,26 @@ window.isCompatible = function ( str ) {
 
                mw.config.set( $VARS.configuration );
 
-               // Must be after mw.config.set because these callbacks may use mw.loader which
-               // needs to have values 'skin', 'debug' etc. from mw.config.
-               // eslint-disable-next-line vars-on-top
-               var RLQ = window.RLQ || [];
-               while ( RLQ.length ) {
-                       RLQ.shift()();
-               }
+               // Process callbacks for Grade A
+               // Must be after registrations and mw.config.set, which mw.loader depends on.
+               var queue = window.RLQ;
                window.RLQ = {
                        push: function ( fn ) {
-                               fn();
+                               if ( typeof fn === 'function' ) {
+                                       fn();
+                               } else {
+                                       // This callback has a requirement.
+                                       mw.loader.using( fn[ 0 ], fn[ 1 ] );
+                               }
                        }
                };
+               while ( queue && queue[ 0 ] ) {
+                       // Re-use our push()
+                       window.RLQ.push( queue.shift() );
+               }
 
-               // Clear and disable the other queue
+               // Clear and disable the Grade C queue
                window.NORLQ = {
-                       // No-op
                        push: function () {}
                };
        }
index c415953..65765f2 100644 (file)
                );
        } );
 
+       QUnit.test( 'RLQ.push', function ( assert ) {
+               /* global RLQ */
+               var loaded = 0,
+                       done = assert.async();
+               mw.loader.testCallback = function () {
+                       loaded++;
+                       delete mw.loader.testCallback;
+               };
+               mw.loader.implement( 'test.rlq-push', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ] );
+
+               RLQ.push( [ 'test.rlq-push', function () {
+                       assert.strictEqual( loaded, 1, 'Load the required module' );
+                       done();
+               } ] );
+       } );
+
 }( mediaWiki ) );