jenkins-bot has submitted this change and it was merged.

Change subject: resourceloader: Async all the way
......................................................................


resourceloader: Async all the way

Page startup:
* Due to the startup module and top queue being asynchronous now,
  move client-nojs/client-js class handling to OutputPage to ensure
  there is no flashes of wrongly styled or unstyled content.

  To preserve compatibility for unsupported browsers, undo the
  class swap at runtime after the isCompatible() check.

ResourceLoader startup module:
* Load the startup module with <script async>.
* Use DOM methods instead of 'document.write' to create base module request 
(jquery|mediawiki).

mw.loader:
* Drop 'async' parameter from mw.loader.load().
* Remove the now-unused code paths for synchronous requests.

OutputPage:

* Drop '$loadCall' parameter from makeResourceLoaderLink().
  Asynchronous is now the default and only way to load JavaScript.
  This means the 'user' module "conditional document-write scripts"
  are now a simple "mw.loader.load( url )" call.

* Fix incorrect @return of makeResourceLoaderLink(). This returns
  an array not a string.

* Improve documentation of makeResourceLoaderLink().

* Drop '$inHead' parameter from getScriptsForBottomQueue(). No longer used.
  Compatibility with the $wgResourceLoaderExperimentalAsyncLoading
  feature is maintained. It just no longer needs to change the
  way the queue works since it's always asynchronous. The feature
  flag now only controls whether the bottom queue starts at the bottom
  or starts at the top.

* Remove jQuery.ready() optimisation.
  This was mostly there to avoid the setTimeout() loop jQuery does to detect
  dom-ready in IE6/IE7 (which we no longer serve JavaScript at all).
  And for a bug in Firefox with document.write (which is no longer used as of
  this commit).

Bug: T107399
Change-Id: Icba6d7a87b239bf127a221bc6bc432cfa71a4a72
---
M includes/OutputPage.php
M includes/resourceloader/ResourceLoaderStartUpModule.php
M includes/specials/SpecialJavaScriptTest.php
M resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js
M resources/src/mediawiki.page/mediawiki.page.startup.js
M resources/src/mediawiki.page/mediawiki.page.watch.ajax.js
M resources/src/mediawiki/mediawiki.js
M resources/src/startup.js
M tests/phpunit/includes/OutputPageTest.php
9 files changed, 150 insertions(+), 183 deletions(-)

Approvals:
  Ori.livneh: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 07c1a8a..0f0b2f5 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -2703,7 +2703,6 @@
                }
 
                $ret .= $this->buildCssLinks() . "\n";
-
                $ret .= $this->getHeadScripts() . "\n";
 
                foreach ( $this->mHeadItems as $item ) {
@@ -2762,18 +2761,16 @@
        }
 
        /**
-        * @todo Document
+        * Construct neccecary html and loader preset states to load modules on 
a page.
+        *
+        * Use getHtmlFromLoaderLinks() to convert this array to HTML.
+        *
         * @param array|string $modules One or more module names
         * @param string $only ResourceLoaderModule TYPE_ class constant
-        * @param array $extraQuery Array with extra query parameters to add to 
each
-        *   request. array( param => value ).
-        * @param bool $loadCall If true, output an (asynchronous) 
mw.loader.load()
-        *   call rather than a "<script src='...'>" tag.
-        * @return string The html "<script>", "<link>" and "<style>" tags
+        * @param array $extraQuery [optional] Array with extra query 
parameters for the request
+        * @return array A list of HTML strings and array of client loader 
preset states
         */
-       public function makeResourceLoaderLink( $modules, $only, array 
$extraQuery = array(),
-               $loadCall = false
-       ) {
+       public function makeResourceLoaderLink( $modules, $only, array 
$extraQuery = array() ) {
                $modules = (array)$modules;
 
                $links = array(
@@ -2796,7 +2793,7 @@
                        if ( ResourceLoader::inDebugMode() ) {
                                // Recursively call us for every item
                                foreach ( $modules as $name ) {
-                                       $link = $this->makeResourceLoaderLink( 
$name, $only );
+                                       $link = $this->makeResourceLoaderLink( 
$name, $only, $extraQuery );
                                        $links['html'] = array_merge( 
$links['html'], $link['html'] );
                                        $links['states'] += $link['states'];
                                }
@@ -2908,29 +2905,18 @@
                                // Automatically select style/script elements
                                if ( $only === 
ResourceLoaderModule::TYPE_STYLES ) {
                                        $link = Html::linkedStyle( $url );
-                               } elseif ( $loadCall ) {
-                                       $link = 
ResourceLoader::makeInlineScript(
-                                               Xml::encodeJsCall( 
'mw.loader.load', array( $url, 'text/javascript', true ) )
-                                       );
                                } else {
-                                       $link = Html::linkedScript( $url );
-                                       if ( !$context->getRaw() && !$isRaw ) {
-                                               // Wrap only=script / 
only=combined requests in a conditional as
-                                               // browsers not supported by 
the startup module would unconditionally
-                                               // execute this module. 
Otherwise users will get "ReferenceError: mw is
-                                               // undefined" or "jQuery is 
undefined" from e.g. a "site" module.
+                                       if ( $context->getRaw() || $isRaw ) {
+                                               // Startup module can't load 
itself, needs to use <script> instead of mw.loader.load
+                                               $link = Html::element( 
'script', array(
+                                                       // In 
SpecialJavaScriptTest, QUnit must load synchronous
+                                                       'async' => !isset( 
$extraQuery['sync'] ),
+                                                       'src' => $url
+                                               ) );
+                                       } else {
                                                $link = 
ResourceLoader::makeInlineScript(
-                                                       Xml::encodeJsCall( 
'document.write', array( $link ) )
+                                                       Xml::encodeJsCall( 
'mw.loader.load', array( $url ) )
                                                );
-                                       }
-
-                                       // For modules requested directly in 
the html via <link> or <script>,
-                                       // tell mw.loader they are being 
loading to prevent duplicate requests.
-                                       foreach ( $grpModules as $key => 
$module ) {
-                                               // Don't output state=loading 
for the startup module..
-                                               if ( $key !== 'startup' ) {
-                                                       $links['states'][$key] 
= 'loading';
-                                               }
                                        }
                                }
 
@@ -2980,8 +2966,20 @@
         * @return string HTML fragment
         */
        function getHeadScripts() {
-               // Startup - this will immediately load jquery and mediawiki 
modules
                $links = array();
+
+               // Client profile classes for <html>. Allows for easy 
hiding/showing of UI components.
+               // Must be done synchronously on every page to avoid flashes of 
wrong content.
+               // Note: This class distinguishes MediaWiki-supported 
JavaScript from the rest.
+               // The "rest" includes browsers that support JavaScript but not 
supported by our runtime.
+               // For the performance benefit of the majority, this is added 
unconditionally here and is
+               // then fixed up by the startup module for unsupported browsers.
+               $links[] = Html::inlineScript(
+                       'document.documentElement.className = 
document.documentElement.className'
+                       . '.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" 
);'
+               );
+
+               // Startup - this provides the client with the module manifest 
and loads jquery and mediawiki base modules
                $links[] = $this->makeResourceLoaderLink( 'startup', 
ResourceLoaderModule::TYPE_SCRIPTS );
 
                // Load config before anything else
@@ -3013,7 +3011,7 @@
                );
 
                if ( $this->getConfig()->get( 
'ResourceLoaderExperimentalAsyncLoading' ) ) {
-                       $links[] = $this->getScriptsForBottomQueue( true );
+                       $links[] = $this->getScriptsForBottomQueue();
                }
 
                return self::getHtmlFromLoaderLinks( $links );
@@ -3026,23 +3024,21 @@
         * 'bottom', legacy scripts ($this->mScripts), user preferences, site JS
         * and user JS.
         *
-        * @param bool $inHead If true, this HTML goes into the "<head>",
-        *   if false it goes into the "<body>".
+        * @param bool $unused Previously used to let this method change its 
output based
+        *  on whether it was called by getHeadScripts() or getBottomScripts().
         * @return string
         */
-       function getScriptsForBottomQueue( $inHead ) {
+       function getScriptsForBottomQueue( $unused = null ) {
                // Scripts "only" requests marked for bottom inclusion
                // If we're in the <head>, use load() calls rather than <script 
src="..."> tags
                $links = array();
 
                $links[] = $this->makeResourceLoaderLink( 
$this->getModuleScripts( true, 'bottom' ),
-                       ResourceLoaderModule::TYPE_SCRIPTS, /* $extraQuery = */ 
array(),
-                       /* $loadCall = */ $inHead
+                       ResourceLoaderModule::TYPE_SCRIPTS
                );
 
                $links[] = $this->makeResourceLoaderLink( 
$this->getModuleStyles( true, 'bottom' ),
-                       ResourceLoaderModule::TYPE_STYLES, /* $extraQuery = */ 
array(),
-                       /* $loadCall = */ $inHead
+                       ResourceLoaderModule::TYPE_STYLES
                );
 
                // Modules requests - let the client calculate dependencies and 
batch requests as it likes
@@ -3050,7 +3046,7 @@
                $modules = $this->getModules( true, 'bottom' );
                if ( $modules ) {
                        $links[] = ResourceLoader::makeInlineScript(
-                               Xml::encodeJsCall( 'mw.loader.load', array( 
$modules, null, true ) )
+                               Xml::encodeJsCall( 'mw.loader.load', array( 
$modules ) )
                        );
                }
 
@@ -3069,7 +3065,7 @@
                        // We're on a preview of a JS subpage. Exclude this 
page from the user module (T28283)
                        // and include the draft contents as a raw script 
instead.
                        $links[] = $this->makeResourceLoaderLink( 'user', 
ResourceLoaderModule::TYPE_COMBINED,
-                               array( 'excludepage' => 
$this->getTitle()->getPrefixedDBkey() ), $inHead
+                               array( 'excludepage' => 
$this->getTitle()->getPrefixedDBkey() )
                        );
                        // Load the previewed JS
                        $links[] = ResourceLoader::makeInlineScript(
@@ -3093,15 +3089,11 @@
                        // the excluded subpage.
                } else {
                        // Include the user module normally, i.e., raw to avoid 
it being wrapped in a closure.
-                       $links[] = $this->makeResourceLoaderLink( 'user', 
ResourceLoaderModule::TYPE_COMBINED,
-                               /* $extraQuery = */ array(), /* $loadCall = */ 
$inHead
-                       );
+                       $links[] = $this->makeResourceLoaderLink( 'user', 
ResourceLoaderModule::TYPE_COMBINED );
                }
 
                // Group JS is only enabled if site JS is enabled.
-               $links[] = $this->makeResourceLoaderLink( 'user.groups', 
ResourceLoaderModule::TYPE_COMBINED,
-                       /* $extraQuery = */ array(), /* $loadCall = */ $inHead
-               );
+               $links[] = $this->makeResourceLoaderLink( 'user.groups', 
ResourceLoaderModule::TYPE_COMBINED );
 
                return self::getHtmlFromLoaderLinks( $links );
        }
@@ -3114,17 +3106,11 @@
                // In case the skin wants to add bottom CSS
                $this->getSkin()->setupSkinUserCss( $this );
 
-               // Optimise jQuery ready event cross-browser.
-               // This also enforces $.isReady to be true at </body> which 
fixes the
-               // mw.loader bug in Firefox with using document.write between 
</body>
-               // and the DOMContentReady event (bug 47457).
-               $html = Html::inlineScript( 'if(window.jQuery)jQuery.ready();' 
);
-
-               if ( !$this->getConfig()->get( 
'ResourceLoaderExperimentalAsyncLoading' ) ) {
-                       $html .= $this->getScriptsForBottomQueue( false );
+               if ( $this->getConfig()->get( 
'ResourceLoaderExperimentalAsyncLoading' ) ) {
+                       // Already handled by getHeadScripts()
+                       return '';
                }
-
-               return $html;
+               return  $this->getScriptsForBottomQueue();
        }
 
        /**
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php 
b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 16424a0..bdf1b21 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -335,7 +335,7 @@
                }, array(
                        '$VARS.wgLegacyJavaScriptGlobals' => 
$this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
                        '$VARS.configuration' => $this->getConfigSettings( 
$context ),
-                       '$VARS.baseModulesScript' => Html::linkedScript( 
self::getStartupModulesUrl( $context ) ),
+                       '$VARS.baseModulesUri' => self::getStartupModulesUrl( 
$context ),
                ) );
                $pairs['$CODE.registrations()'] = str_replace( "\n", "\n\t", 
trim( $this->getModuleRegistrations( $context ) ) );
 
diff --git a/includes/specials/SpecialJavaScriptTest.php 
b/includes/specials/SpecialJavaScriptTest.php
index 442b764..b5f3815 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -210,7 +210,20 @@
                $query['only'] = 'scripts';
                $startupContext = new ResourceLoaderContext( $rl, new 
FauxRequest( $query ) );
 
+               $query['raw'] = true;
+
                $modules = $rl->getTestModuleNames( 'qunit' );
+
+               // Disable autostart because we load modules asynchronously. By 
default, QUnit would start
+               // at domready when there are no tests loaded and also fire 
'QUnit.done' which then instructs
+               // Karma to end the run before the tests even started.
+               $qunitConfig = 'QUnit.config.autostart = false;'
+                       . 'if (window.__karma__) {'
+                       // karma-qunit's use of autostart=false and QUnit.start 
conflicts with ours.
+                       // Hack around this by replacing 'karma.loaded' with a 
no-op and call it ourselves later.
+                       // See 
<https://github.com/karma-runner/karma-qunit/issues/27>.
+                       . 'window.__karma__.loaded = function () {};'
+                       . '}';
 
                // The below is essentially a pure-javascript version of 
OutputPage::getHeadScripts.
                $startup = $rl->makeModuleResponse( $startupContext, array(
@@ -225,35 +238,39 @@
                        'user.options' => $rl->getModule( 'user.options' ),
                        'user.tokens' => $rl->getModule( 'user.tokens' ),
                ) );
-               $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules ) 
);
+               // Catch exceptions (such as "dependency missing" or "unknown 
module") so that we
+               // always start QUnit. Re-throw so that they are caught and 
reported as global exceptions
+               // by QUnit and Karma.
+               $code .= '(function () {'
+                       . 'var start = window.__karma__ ? 
window.__karma__.start : QUnit.start;'
+                       . 'try {'
+                       . 'mw.loader.using( ' . Xml::encodeJsVar( $modules ) . 
' ).always( start );'
+                       . '} catch ( e ) { start(); throw e; }'
+                       . '}());';
 
                header( 'Content-Type: text/javascript; charset=utf-8' );
                header( 'Cache-Control: private, no-cache, must-revalidate' );
                header( 'Pragma: no-cache' );
+               echo $qunitConfig;
                echo $startup;
-               echo "\n";
-               // Note: The following has to be wrapped in a script tag 
because the startup module also
-               // writes a script tag (the one loading mediawiki.js). Script 
tags are synchronous, block
-               // each other, and run in order. But they don't nest. The code 
appended after the startup
-               // module runs before the added script tag is parsed and 
executed.
-               echo Xml::encodeJsCall( 'document.write', array( 
Html::inlineScript( $code ) ) );
+               // The following has to be deferred via RLQ because the startup 
module is asynchronous.
+               echo ResourceLoader::makeLoaderConditionalScript( $code );
        }
 
        private function plainQUnit() {
                $out = $this->getOutput();
                $out->disable();
 
-               $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
-                       'debug' => ResourceLoader::inDebugMode() ? 'true' : 
'false',
-               ) );
-
                $styles = $out->makeResourceLoaderLink( 'jquery.qunit',
                        ResourceLoaderModule::TYPE_STYLES
                );
-               // Use 'raw' since this is a plain HTML page without 
ResourceLoader
+
+               // Use 'raw' because QUnit loads before ResourceLoader 
initialises (omit mw.loader.state call)
+               // Use 'test' to ensure OutputPage doesn't use the "async" 
attribute because QUnit must
+               // load before qunit/export.
                $scripts = $out->makeResourceLoaderLink( 'jquery.qunit',
                        ResourceLoaderModule::TYPE_SCRIPTS,
-                       array( 'raw' => 'true' )
+                       array( 'raw' => true, 'sync' => true )
                );
 
                $head = implode( "\n", array_merge( $styles['html'], 
$scripts['html'] ) );
@@ -265,6 +282,10 @@
 $summary
 <div id="qunit"></div>
 HTML;
+
+               $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
+                       'debug' => ResourceLoader::inDebugMode() ? 'true' : 
'false',
+               ) );
                $html .= "\n" . Html::linkedScript( $url );
 
                header( 'Content-Type: text/html; charset=utf-8' );
diff --git a/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js 
b/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js
index cc72e16..3da1d14 100644
--- a/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js
+++ b/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js
@@ -17,7 +17,7 @@
                        var $spinner, href, rcid, apiRequest;
 
                        // Start preloading the notification module (normally 
loaded by mw.notify())
-                       mw.loader.load( ['mediawiki.notification'], null, true 
);
+                       mw.loader.load( 'mediawiki.notification' );
 
                        // Hide the link and create a spinner to show it inside 
the brackets.
                        $spinner = $.createSpinner( {
diff --git a/resources/src/mediawiki.page/mediawiki.page.startup.js 
b/resources/src/mediawiki.page/mediawiki.page.startup.js
index ddd4f0c..708dcb5 100644
--- a/resources/src/mediawiki.page/mediawiki.page.startup.js
+++ b/resources/src/mediawiki.page/mediawiki.page.startup.js
@@ -1,12 +1,11 @@
 ( function ( mw, $ ) {
 
-       mw.page = {};
+       // Support: MediaWiki < 1.26
+       // Cached HTML will not yet have this from OutputPage::getHeadScripts.
+       document.documentElement.className = document.documentElement.className
+               .replace( /(^|\s)client-nojs(\s|$)/, '$1client-js$2' );
 
-       // Client profile classes for <html>
-       // Allows for easy hiding/showing of JS or no-JS-specific UI elements
-       $( document.documentElement )
-               .addClass( 'client-js' )
-               .removeClass( 'client-nojs' );
+       mw.page = {};
 
        $( function () {
                mw.util.init();
diff --git a/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js 
b/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js
index 50f280a..cc1ffb7 100644
--- a/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js
+++ b/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js
@@ -116,7 +116,7 @@
                        var action, api, $link;
 
                        // Start preloading the notification module (normally 
loaded by mw.notify())
-                       mw.loader.load( ['mediawiki.notification'], null, true 
);
+                       mw.loader.load( 'mediawiki.notification' );
 
                        action = mwUriGetAction( this.href );
 
diff --git a/resources/src/mediawiki/mediawiki.js 
b/resources/src/mediawiki/mediawiki.js
index 7825f22..3c5a5f5 100644
--- a/resources/src/mediawiki/mediawiki.js
+++ b/resources/src/mediawiki/mediawiki.js
@@ -1111,39 +1111,24 @@
                        }
 
                        /**
-                        * Adds a script tag to the DOM, either using 
document.write or low-level DOM manipulation,
-                        * depending on whether document-ready has occurred yet 
and whether we are in async mode.
+                        * Load and execute a script with callback.
                         *
                         * @private
                         * @param {string} src URL to script, will be used as 
the src attribute in the script tag
                         * @param {Function} [callback] Callback which will be 
run when the script is done
-                        * @param {boolean} [async=false] Whether to load 
modules asynchronously.
-                        *  Ignored (and defaulted to `true`) if the 
document-ready event has already occurred.
                         */
-                       function addScript( src, callback, async ) {
-                               // Using isReady directly instead of storing it 
locally from a $().ready callback (bug 31895)
-                               if ( $.isReady || async ) {
-                                       $.ajax( {
-                                               url: src,
-                                               dataType: 'script',
-                                               // Force jQuery behaviour to be 
for crossDomain. Otherwise jQuery would use
-                                               // XHR for a same domain 
request instead of <script>, which changes the request
-                                               // headers (potentially missing 
a cache hit), and reduces caching in general
-                                               // since browsers cache XHR 
much less (if at all). And XHR means we retreive
-                                               // text, so we'd need to 
$.globalEval, which then messes up line numbers.
-                                               crossDomain: true,
-                                               cache: true,
-                                               async: true
-                                       } ).always( callback );
-                               } else {
-                                       /*jshint evil:true */
-                                       document.write( mw.html.element( 
'script', { 'src': src }, '' ) );
-                                       if ( callback ) {
-                                               // Document.write is 
synchronous, so this is called when it's done.
-                                               // FIXME: That's a lie. 
doc.write isn't actually synchronous.
-                                               callback();
-                                       }
-                               }
+                       function addScript( src, callback ) {
+                               $.ajax( {
+                                       url: src,
+                                       dataType: 'script',
+                                       // Force jQuery behaviour to be for 
crossDomain. Otherwise jQuery would use
+                                       // XHR for a same domain request 
instead of <script>, which changes the request
+                                       // headers (potentially missing a cache 
hit), and reduces caching in general
+                                       // since browsers cache XHR much less 
(if at all). And XHR means we retreive
+                                       // text, so we'd need to $.globalEval, 
which then messes up line numbers.
+                                       crossDomain: true,
+                                       cache: true
+                               } ).always( callback );
                        }
 
                        /**
@@ -1196,7 +1181,7 @@
                                                        registry[module].state 
= 'ready';
                                                        handlePending( module );
                                                };
-                                               nestedAddScript = function ( 
arr, callback, async, i ) {
+                                               nestedAddScript = function ( 
arr, callback, i ) {
                                                        // Recursively call 
addScript() in its own callback
                                                        // for each element of 
arr.
                                                        if ( i >= arr.length ) {
@@ -1206,12 +1191,12 @@
                                                        }
 
                                                        addScript( arr[i], 
function () {
-                                                               
nestedAddScript( arr, callback, async, i + 1 );
-                                                       }, async );
+                                                               
nestedAddScript( arr, callback, i + 1 );
+                                                       } );
                                                };
 
                                                if ( $.isArray( script ) ) {
-                                                       nestedAddScript( 
script, markModuleReady, registry[module].async, 0 );
+                                                       nestedAddScript( 
script, markModuleReady, 0 );
                                                } else if ( $.isFunction( 
script ) ) {
                                                        // Pass jQuery twice so 
that the signature of the closure which wraps
                                                        // the script can bind 
both '$' and 'jQuery'.
@@ -1261,37 +1246,29 @@
                                        mw.templates.set( module, 
registry[module].templates );
                                }
 
-                               if ( $.isReady || registry[module].async ) {
-                                       // Make sure we don't run the scripts 
until all (potentially asynchronous)
-                                       // stylesheet insertions have completed.
-                                       ( function () {
-                                               var pending = 0;
-                                               checkCssHandles = function () {
-                                                       // cssHandlesRegistered 
ensures we don't take off too soon, e.g. when
-                                                       // one of the 
cssHandles is fired while we're still creating more handles.
-                                                       if ( 
cssHandlesRegistered && pending === 0 && runScript ) {
-                                                               runScript();
-                                                               runScript = 
undefined; // Revoke
+                               // Make sure we don't run the scripts until all 
stylesheet insertions have completed.
+                               ( function () {
+                                       var pending = 0;
+                                       checkCssHandles = function () {
+                                               // cssHandlesRegistered ensures 
we don't take off too soon, e.g. when
+                                               // one of the cssHandles is 
fired while we're still creating more handles.
+                                               if ( cssHandlesRegistered && 
pending === 0 && runScript ) {
+                                                       runScript();
+                                                       runScript = undefined; 
// Revoke
+                                               }
+                                       };
+                                       cssHandle = function () {
+                                               var check = checkCssHandles;
+                                               pending++;
+                                               return function () {
+                                                       if ( check ) {
+                                                               pending--;
+                                                               check();
+                                                               check = 
undefined; // Revoke
                                                        }
                                                };
-                                               cssHandle = function () {
-                                                       var check = 
checkCssHandles;
-                                                       pending++;
-                                                       return function () {
-                                                               if ( check ) {
-                                                                       
pending--;
-                                                                       check();
-                                                                       check = 
undefined; // Revoke
-                                                               }
-                                                       };
-                                               };
-                                       }() );
-                               } else {
-                                       // We are in blocking mode, and so we 
can't afford to wait for CSS
-                                       cssHandle = function () {};
-                                       // Run immediately
-                                       checkCssHandles = runScript;
-                               }
+                                       };
+                               }() );
 
                                // Process styles (see also mw.loader.implement)
                                // * back-compat: { <media>: css }
@@ -1358,10 +1335,8 @@
                         * @param {string|string[]} dependencies Module name or 
array of string module names
                         * @param {Function} [ready] Callback to execute when 
all dependencies are ready
                         * @param {Function} [error] Callback to execute when 
any dependency fails
-                        * @param {boolean} [async=false] Whether to load 
modules asynchronously.
-                        *  Ignored (and defaulted to `true`) if the 
document-ready event has already occurred.
                         */
-                       function request( dependencies, ready, error, async ) {
+                       function request( dependencies, ready, error ) {
                                // Allow calling by single module name
                                if ( typeof dependencies === 'string' ) {
                                        dependencies = [dependencies];
@@ -1392,9 +1367,6 @@
                                                        return;
                                                }
                                                queue.push( module );
-                                               if ( async ) {
-                                                       registry[module].async 
= true;
-                                               }
                                        }
                                } );
 
@@ -1435,25 +1407,22 @@
                        }
 
                        /**
-                        * Asynchronously append a script tag to the end of the 
body
-                        * that invokes load.php
+                        * Load modules from load.php
                         * @private
                         * @param {Object} moduleMap Module map, see 
#buildModulesString
                         * @param {Object} currReqBase Object with other 
parameters (other than 'modules') to use in the request
                         * @param {string} sourceLoadScript URL of load.php
-                        * @param {boolean} async Whether to load modules 
asynchronously.
-                        *  Ignored (and defaulted to `true`) if the 
document-ready event has already occurred.
                         */
-                       function doRequest( moduleMap, currReqBase, 
sourceLoadScript, async ) {
+                       function doRequest( moduleMap, currReqBase, 
sourceLoadScript ) {
                                var request = $.extend(
                                        { modules: buildModulesString( 
moduleMap ) },
                                        currReqBase
                                );
                                request = sortQuery( request );
                                // Support: IE6
-                               // Append &* to satisfy load.php's 
WebRequest::checkUrlExtension test. This script
-                               // isn't actually used in IE6, but MediaWiki 
enforces it in general.
-                               addScript( sourceLoadScript + '?' + $.param( 
request ) + '&*', null, async );
+                               // Append &* to satisfy load.php's 
WebRequest::checkUrlExtension test.
+                               // This script isn't actually used in IE6, but 
MediaWiki enforces it in general.
+                               addScript( sourceLoadScript + '?' + $.param( 
request ) + '&*' );
                        }
 
                        /**
@@ -1502,7 +1471,7 @@
                                        var     reqBase, splits, 
maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
                                                source, concatSource, 
origBatch, group, i, modules, sourceLoadScript,
                                                currReqBase, currReqBaseLength, 
moduleMap, l,
-                                               lastDotIndex, prefix, suffix, 
bytesAdded, async;
+                                               lastDotIndex, prefix, suffix, 
bytesAdded;
 
                                        // Build a list of request parameters 
common to all requests.
                                        reqBase = {
@@ -1615,7 +1584,6 @@
                                                                
currReqBase.user = mw.config.get( 'wgUserName' );
                                                        }
                                                        currReqBaseLength = 
$.param( currReqBase ).length;
-                                                       async = true;
                                                        // We may need to split 
up the request to honor the query string length limit,
                                                        // so build it piece by 
piece.
                                                        l = currReqBaseLength + 
9; // '&modules='.length == 9
@@ -1639,9 +1607,8 @@
                                                                if ( 
maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > 
maxQueryLength ) {
                                                                        // This 
request would become too long, create a new one
                                                                        // and 
fire off the old one
-                                                                       
doRequest( moduleMap, currReqBase, sourceLoadScript, async );
+                                                                       
doRequest( moduleMap, currReqBase, sourceLoadScript );
                                                                        
moduleMap = {};
-                                                                       async = 
true;
                                                                        l = 
currReqBaseLength + 9;
                                                                        
mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
                                                                }
@@ -1649,17 +1616,11 @@
                                                                        
moduleMap[prefix] = [];
                                                                }
                                                                
moduleMap[prefix].push( suffix );
-                                                               if ( 
!registry[modules[i]].async ) {
-                                                                       // If 
this module is blocking, make the entire request blocking
-                                                                       // This 
is slightly suboptimal, but in practice mixing of blocking
-                                                                       // and 
async modules will only occur in debug mode.
-                                                                       async = 
false;
-                                                               }
                                                                l += bytesAdded;
                                                        }
                                                        // If there's anything 
left in moduleMap, request that too
                                                        if ( !$.isEmptyObject( 
moduleMap ) ) {
-                                                               doRequest( 
moduleMap, currReqBase, sourceLoadScript, async );
+                                                               doRequest( 
moduleMap, currReqBase, sourceLoadScript );
                                                        }
                                                }
                                        }
@@ -1888,11 +1849,8 @@
                                 * @param {string} [type='text/javascript'] 
MIME type to use if calling with a URL of an
                                 *  external script or style; acceptable values 
are "text/css" and
                                 *  "text/javascript"; if no type is provided, 
text/javascript is assumed.
-                                * @param {boolean} [async] Whether to load 
modules asynchronously.
-                                *  Ignored (and defaulted to `true`) if the 
document-ready event has already occurred.
-                                *  Defaults to `true` if loading a URL, 
`false` otherwise.
                                 */
-                               load: function ( modules, type, async ) {
+                               load: function ( modules, type ) {
                                        var filtered, l;
 
                                        // Validate input
@@ -1902,10 +1860,6 @@
                                        // Allow calling with an external url 
or single dependency as a string
                                        if ( typeof modules === 'string' ) {
                                                if ( /^(https?:)?\/\//.test( 
modules ) ) {
-                                                       if ( async === 
undefined ) {
-                                                               // Assume async 
for bug 34542
-                                                               async = true;
-                                                       }
                                                        if ( type === 
'text/css' ) {
                                                                // Support: IE 
7-8
                                                                // Use 
properties instead of attributes as IE throws security
@@ -1918,7 +1872,7 @@
                                                                return;
                                                        }
                                                        if ( type === 
'text/javascript' || type === undefined ) {
-                                                               addScript( 
modules, null, async );
+                                                               addScript( 
modules );
                                                                return;
                                                        }
                                                        // Unknown type
@@ -1948,7 +1902,7 @@
                                                return;
                                        }
                                        // Since some modules are not yet 
ready, queue up a request.
-                                       request( filtered, undefined, 
undefined, async );
+                                       request( filtered, undefined, undefined 
);
                                },
 
                                /**
diff --git a/resources/src/startup.js b/resources/src/startup.js
index 97fa134..d4cfa02 100644
--- a/resources/src/startup.js
+++ b/resources/src/startup.js
@@ -93,5 +93,14 @@
 
 // Conditional script injection
 if ( isCompatible() ) {
-       document.write( $VARS.baseModulesScript );
+       ( function () {
+               var script = document.createElement( 'script' );
+               script.src = $VARS.baseModulesUri;
+               document.getElementsByTagName( 'head' )[0].appendChild( script 
);
+       }() );
+} else {
+       // Undo class swapping in case of an unsupported browser.
+       // See OutputPage::getHeadScripts().
+       document.documentElement.className = document.documentElement.className
+               .replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' );
 }
diff --git a/tests/phpunit/includes/OutputPageTest.php 
b/tests/phpunit/includes/OutputPageTest.php
index 69f55c3..6a25d8b 100644
--- a/tests/phpunit/includes/OutputPageTest.php
+++ b/tests/phpunit/includes/OutputPageTest.php
@@ -142,15 +142,13 @@
                        array(
                                array( 'test.foo', 
ResourceLoaderModule::TYPE_SCRIPTS ),
                                "<script>var RLQ = RLQ || []; RLQ.push( 
function () {\n"
-                                       . 'document.write("\u003Cscript 
src=\"http://127.0.0.1:8080/w/load.php?'
-                                       . 
'debug=false\u0026amp;lang=en\u0026amp;modules=test.foo\u0026amp;only'
-                                       . 
'=scripts\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+                                       . 
'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback\u0026*";);'
                                        . "\n} );</script>"
                        ),
                        array(
                                // Don't condition wrap raw modules (like the 
startup module)
                                array( 'test.raw', 
ResourceLoaderModule::TYPE_SCRIPTS ),
-                               '<script 
src="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.raw&amp;only=scripts&amp;skin=fallback&amp;*";></script>'
+                               '<script async 
src="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.raw&amp;only=scripts&amp;skin=fallback&amp;*";></script>'
                        ),
                        // Load module styles only
                        // This also tests the order the modules are put into 
the url
@@ -188,10 +186,10 @@
                        array(
                                array( array( 'test.group.foo', 
'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ),
                                "<script>var RLQ = RLQ || []; RLQ.push( 
function () {\n"
-                                       . 'document.write("\u003Cscript 
src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.bar\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E";);'
+                                       . 
'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback\u0026*";);'
                                        . "\n} );</script>\n"
                                        . "<script>var RLQ = RLQ || []; 
RLQ.push( function () {\n"
-                                       . 'document.write("\u003Cscript 
src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.foo\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E";);'
+                                       . 
'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback\u0026*";);'
                                        . "\n} );</script>"
                        ),
                );

-- 
To view, visit https://gerrit.wikimedia.org/r/227627
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Icba6d7a87b239bf127a221bc6bc432cfa71a4a72
Gerrit-PatchSet: 21
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Krinkle <[email protected]>
Gerrit-Reviewer: Edokter <[email protected]>
Gerrit-Reviewer: Jack Phoenix <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to