Merge "Optimize order of styles and scripts"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 17 Mar 2015 21:31:04 +0000 (21:31 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 17 Mar 2015 21:31:04 +0000 (21:31 +0000)
includes/EditPage.php
includes/OutputPage.php
includes/debug/MWDebug.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/skins/Skin.php
includes/specials/SpecialJavaScriptTest.php
tests/phpunit/includes/OutputPageTest.php

index a8a17cf..5eb07d1 100644 (file)
@@ -3693,7 +3693,7 @@ HTML
                }
 
                $script .= '});';
-               $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
+               $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
 
                $toolbar = '<div id="toolbar"></div>';
 
index edeae0d..5ad33fa 100644 (file)
@@ -2674,10 +2674,12 @@ class OutputPage extends ContextSource {
                        $ret .= $item . "\n";
                }
 
+               $ret .= $this->getInlineHeadScript();
+
                // No newline after buildCssLinks since makeResourceLoaderLink did that already
                $ret .= $this->buildCssLinks();
 
-               $ret .= $this->getHeadScripts() . "\n";
+               $ret .= $this->getHeadScripts();
 
                foreach ( $this->mHeadItems as $item ) {
                        $ret .= $item . "\n";
@@ -2854,10 +2856,8 @@ class OutputPage extends ContextSource {
                                                        $resourceLoader->makeModuleResponse( $context, $grpModules )
                                                );
                                        } else {
-                                               $links['html'] .= Html::inlineScript(
-                                                       ResourceLoader::makeLoaderConditionalScript(
-                                                               $resourceLoader->makeModuleResponse( $context, $grpModules )
-                                                       )
+                                               $links['html'] .= ResourceLoader::makeInlineScript(
+                                                       $resourceLoader->makeModuleResponse( $context, $grpModules )
                                                );
                                        }
                                        $links['html'] .= "\n";
@@ -2896,10 +2896,8 @@ class OutputPage extends ContextSource {
                                        if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
                                                $link = Html::linkedStyle( $url );
                                        } elseif ( $loadCall ) {
-                                               $link = Html::inlineScript(
-                                                       ResourceLoader::makeLoaderConditionalScript(
-                                                               Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
-                                                       )
+                                               $link = ResourceLoader::makeInlineScript(
+                                                       Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
                                                );
                                        } else {
                                                $link = Html::linkedScript( $url );
@@ -2908,10 +2906,8 @@ class OutputPage extends ContextSource {
                                                        // 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.
-                                                       $link = Html::inlineScript(
-                                                               ResourceLoader::makeLoaderConditionalScript(
-                                                                       Xml::encodeJsCall( 'document.write', array( $link ) )
-                                                               )
+                                                       $link = ResourceLoader::makeInlineScript(
+                                                               Xml::encodeJsCall( 'document.write', array( $link ) )
                                                        );
                                                }
 
@@ -2955,16 +2951,44 @@ class OutputPage extends ContextSource {
                }
 
                if ( count( $states ) ) {
-                       $html = Html::inlineScript(
-                               ResourceLoader::makeLoaderConditionalScript(
-                                       ResourceLoader::makeLoaderStateScript( $states )
-                               )
+                       $html = ResourceLoader::makeInlineScript(
+                               ResourceLoader::makeLoaderStateScript( $states )
                        ) . "\n" . $html;
                }
 
                return $html;
        }
 
+       /**
+        * Get <script> tags for <head> whose source is inline.
+        *
+        * @since 1.25
+        * @return string HTML fragment
+        */
+       public function getInlineHeadScript() {
+               // Load config before anything else.
+               $html = ResourceLoader::makeInlineScript(
+                       ResourceLoader::makeConfigSetScript( $this->getJSVars() )
+               );
+
+               // Load embeddable private modules before any loader links.
+               $inlineModulesLink = $this->makeResourceLoaderLink(
+                       array( 'user.options', 'user.tokens' ), ResourceLoaderModule::TYPE_COMBINED
+               );
+               $html .= "\n" . self::getHtmlFromLoaderLinks( array( $inlineModulesLink ) );
+
+               // Construct mw.loader.load() call for top-loaded modules.
+               // Client-side code will request these modules and their dependencies.
+               $topModules = $this->getModules( true, 'top' );
+               if ( $topModules ) {
+                       $html .= ResourceLoader::makeInlineScript(
+                               Xml::encodeJsCall( 'mw.loader.load', array( $topModules ) )
+                       ) . "\n";
+               }
+
+               return $html;
+       }
+
        /**
         * JS stuff to put in the "<head>". This is the startup module, config
         * vars and modules marked with position 'top'
@@ -2976,19 +3000,6 @@ class OutputPage extends ContextSource {
                $links = array();
                $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
 
-               // Load config before anything else
-               $links[] = Html::inlineScript(
-                       ResourceLoader::makeLoaderConditionalScript(
-                               ResourceLoader::makeConfigSetScript( $this->getJSVars() )
-                       )
-               );
-
-               // Load embeddable private modules before any loader links
-               // This needs to be TYPE_COMBINED so these modules are properly wrapped
-               // in mw.loader.implement() calls and deferred until mw.user is available
-               $embedScripts = array( 'user.options', 'user.tokens' );
-               $links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
-
                // Scripts and messages "only" requests marked for top inclusion
                // Messages should go first
                $links[] = $this->makeResourceLoaderLink(
@@ -3000,17 +3011,6 @@ class OutputPage extends ContextSource {
                        ResourceLoaderModule::TYPE_SCRIPTS
                );
 
-               // Modules requests - let the client calculate dependencies and batch requests as it likes
-               // Only load modules that have marked themselves for loading at the top
-               $modules = $this->getModules( true, 'top' );
-               if ( $modules ) {
-                       $links[] = Html::inlineScript(
-                               ResourceLoader::makeLoaderConditionalScript(
-                                       Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
-                               )
-                       );
-               }
-
                if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
                        $links[] = $this->getScriptsForBottomQueue( true );
                }
@@ -3047,10 +3047,8 @@ class OutputPage extends ContextSource {
                // Only load modules that have marked themselves for loading at the bottom
                $modules = $this->getModules( true, 'bottom' );
                if ( $modules ) {
-                       $links[] = Html::inlineScript(
-                               ResourceLoader::makeLoaderConditionalScript(
-                                       Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
-                               )
+                       $links[] = ResourceLoader::makeInlineScript(
+                               Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
                        );
                }
 
index c4c6cf3..48d4e8d 100644 (file)
@@ -419,10 +419,8 @@ class MWDebug {
 
                        // Cannot use OutputPage::addJsConfigVars because those are already outputted
                        // by the time this method is called.
-                       $html = Html::inlineScript(
-                               ResourceLoader::makeLoaderConditionalScript(
-                                       ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) )
-                               )
+                       $html = ResourceLoader::makeInlineScript(
+                               ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) )
                        );
                }
 
index 5eab3cb..803afbf 100644 (file)
@@ -1326,6 +1326,7 @@ MESSAGE;
         * Returns JS code which runs given JS code if the client-side framework is
         * present.
         *
+        * @deprecated since 1.25; use makeInlineScript instead
         * @param string $script JavaScript code
         * @return string
         */
@@ -1333,6 +1334,20 @@ MESSAGE;
                return "if(window.mw){\n" . trim( $script ) . "\n}";
        }
 
+       /**
+        * Construct an inline script tag with given JS code.
+        *
+        * The code will be wrapped in a closure, and it will be executed by ResourceLoader
+        * only if the client has adequate support for MediaWiki JavaScript code.
+        *
+        * @param string $script JavaScript code
+        * @return string HTML
+        */
+       public static function makeInlineScript( $script ) {
+               $js = 'var _mwq = _mwq || []; _mwq.push( function ( mw ) { ' . $script . ' } );';
+               return Html::inlineScript( $js );
+       }
+
        /**
         * Returns JS code which will set the MediaWiki configuration array to
         * the given value.
index 48b3576..6ee098c 100644 (file)
@@ -357,11 +357,20 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                                ResourceLoader::inDebugMode()
                        );
 
+                       // Process the deferred inline script queue, and ensure that any
+                       // functions enqueued after this point are executed immediately.
+                       $mwqJs = (
+                               'window._mwq = window._mwq || [];' .
+                               'while ( _mwq.length ) _mwq.shift()( mw );' .
+                               '_mwq.push = function ( f ) { f( mw ); };'
+                       );
+
                        $out .= "var startUp = function () {\n" .
                                "\tmw.config = new " .
                                $mwMapJsCall . "\n" .
                                "\t$registrations\n" .
-                               "\t" . $mwConfigSetJsCall .
+                               "\t" . $mwConfigSetJsCall . "\n" .
+                               "\t" . $mwqJs . "\n" .
                                "};\n";
 
                        // Conditional script injection
index 48bce67..d649c2e 100644 (file)
@@ -360,8 +360,8 @@ abstract class Skin extends ContextSource {
         */
        static function makeVariablesScript( $data ) {
                if ( $data ) {
-                       return Html::inlineScript(
-                               ResourceLoader::makeLoaderConditionalScript( ResourceLoader::makeConfigSetScript( $data ) )
+                       return ResourceLoader::makeInlineScript(
+                               ResourceLoader::makeConfigSetScript( $data )
                        );
                } else {
                        return '';
index ecb166a..e9639e1 100644 (file)
@@ -168,15 +168,13 @@ HTML;
                // The testrunner configures QUnit and essentially depends on it. However, test suites
                // are reusable in environments that preload QUnit (or a compatibility interface to
                // another framework). Therefore we have to load it ourselves.
-               $out->addHtml( Html::inlineScript(
-                       ResourceLoader::makeLoaderConditionalScript(
-                               Xml::encodeJsCall( 'mw.loader.using', array(
-                                       array( 'jquery.qunit', 'jquery.qunit.completenessTest' ),
-                                       new XmlJsCode(
-                                               'function () {' . Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}'
-                                       )
-                               ) )
-                       )
+               $out->addHtml( ResourceLoader::makeInlineScript(
+                       Xml::encodeJsCall( 'mw.loader.using', array(
+                               array( 'jquery.qunit', 'jquery.qunit.completenessTest' ),
+                               new XmlJsCode(
+                                       'function () {' . Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}'
+                               )
+                       ) )
                ) );
        }
 
index 77a572d..1f35ead 100644 (file)
@@ -141,52 +141,56 @@ class OutputPageTest extends MediaWikiTestCase {
                        // Load module script only
                        array(
                                array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ),
-                               '<script>if(window.mw){
-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");
-}</script>
+                               '<script>var _mwq = _mwq || []; _mwq.push( function ( mw ) {' .
+                               ' 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\u0026' .
+                               'amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); } );</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 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
                        array(
                                array( array( 'test.baz', 'test.foo', 'test.bar' ), ResourceLoaderModule::TYPE_STYLES ),
-                               '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.bar%2Cbaz%2Cfoo&amp;only=styles&amp;skin=fallback&amp;*">
+                               '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&amp;' .
+                               'lang=en&amp;modules=test.bar%2Cbaz%2Cfoo&amp;only=styles&amp;skin=fallback&amp;*">
 '
                        ),
                        // Load private module (only=scripts)
                        array(
                                array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ),
-                               '<script>if(window.mw){
-mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"});
-
-}</script>
+                               '<script>var _mwq = _mwq || []; _mwq.push( function ( mw ) {' .
+                               ' mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"});
+' . ' } );</script>
 '
                        ),
                        // Load private module (combined)
                        array(
                                array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ),
-                               '<script>if(window.mw){
-mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]},{},{});
-
-}</script>
+                               '<script>var _mwq = _mwq || []; _mwq.push( function ( mw ) {' .
+                               ' mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});}' .
+                               ',{"css":[".mw-icon{transition:none}\n"]},{},{});
+' . ' } );</script>
 '
                        ),
                        // Load module script with ESI
                        array(
                                array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ),
-                               '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.foo&amp;only=scripts&amp;skin=fallback&amp;*" /></script>
+                               '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&amp;' .
+                               'lang=en&amp;modules=test.foo&amp;only=scripts&amp;skin=fallback&amp;*" /></script>
 '
                        ),
                        // Load module styles with ESI
                        array(
                                array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ),
-                               '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.foo&amp;only=styles&amp;skin=fallback&amp;*" /></style>
+                               '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&amp;' .
+                               'lang=en&amp;modules=test.foo&amp;only=styles&amp;skin=fallback&amp;*" /></style>
 ',
                        ),
                        // Load no modules
@@ -197,18 +201,22 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"
                        // noscript group
                        array(
                                array( 'test.noscript', ResourceLoaderModule::TYPE_STYLES ),
-                               '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.noscript&amp;only=styles&amp;skin=fallback&amp;*"></noscript>
+                               '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=' .
+                               'false&amp;lang=en&amp;modules=test.noscript&amp;only=styles&amp;skin=fallback' .
+                               '&amp;*"></noscript>
 '
                        ),
                        // Load two modules in separate groups
                        array(
                                array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ),
-                               '<script>if(window.mw){
-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");
-}</script>
-<script>if(window.mw){
-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");
-}</script>
+                               '<script>var _mwq = _mwq || []; _mwq.push( function ( mw ) { ' .
+                               '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\u0026' .
+                               'amp;*\"\u003E\u003C/script\u003E"); } );</script>
+<script>var _mwq = _mwq || []; _mwq.push( function ( mw ) { 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"); } );</script>
 '
                        ),
                );