From e86e5f8460b922cadac231142a495f3259c67b43 Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sun, 15 Mar 2015 19:45:17 -0700 Subject: [PATCH] Optimize order of styles and scripts The current ordering of scripts and stylesheets in causes all major browsers to serialize and defer requests that could be performed in parallel. The problem is that external stylesheets are loaded before inline scripts. As Steven Souders explains, "all major browsers preserve the order of CSS and JavaScript. The stylesheet has to be fully downloaded, parsed, and applied before the inline script is executed. And the inline script must be executed before the remaining resources can be downloaded. Therefore, resources that follow a stylesheet and inline script are blocked from downloading."[1] In other words: the browser could start loading body images, but it refuses to do that until it has executed inline scripts in head. And it refuses to execute those scripts until the external CSS is downloaded, parsed and applied. You can see the effect of this in this image, showing the request waterfall for [[en:Gothic Alphabet]]: [2]. Notice how no images were requested before the browser had finished processing the three load.php requests at the top. To fix this, we want to move the inline scripts above the external CSS. This is a little bit tricky, because the inline scripts depend on mw.loader, which is loaded via an external script. If we move the external script so that it too is above the external stylesheet, we force the browser to serialize requests, because the browser will not retrieve the external CSS until it has retrieved and executed the external JS code. So what we want is to move the inline scripts above the external stylesheet, but keep the external script (which the inline scripts depend on) below the external stylesheet. We can do this by wrapping the inline script code in a closure (which binds 'mw') and enqueuing the closure in a global array which will be processed by the startup module at just the right time. Net result: external CSS and JS is retrieved in parallel, retrieval of images (and other external assets) is unblocked, but the order in which code is evaluated remains the same. [1]: [2]: (excerpted from . Change-Id: I98d383a6299ffbd10210431544a505338ca8643f --- includes/EditPage.php | 2 +- includes/OutputPage.php | 88 +++++++++---------- includes/debug/MWDebug.php | 6 +- includes/resourceloader/ResourceLoader.php | 15 ++++ .../ResourceLoaderStartUpModule.php | 11 ++- includes/skins/Skin.php | 4 +- includes/specials/SpecialJavaScriptTest.php | 16 ++-- tests/phpunit/includes/OutputPageTest.php | 52 ++++++----- 8 files changed, 110 insertions(+), 84 deletions(-) diff --git a/includes/EditPage.php b/includes/EditPage.php index a8a17cf90b..5eb07d1a9a 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -3693,7 +3693,7 @@ HTML } $script .= '});'; - $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) ); + $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) ); $toolbar = '
'; diff --git a/includes/OutputPage.php b/includes/OutputPage.php index edeae0d139..5ad33fad0f 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -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 + ' ' ), array( // Don't condition wrap raw modules (like the startup module) array( 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ), - ' + ' ' ), // 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 ), - ' + ' ' ), // Load private module (only=scripts) array( array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ), - ' + ' ' ), // Load private module (combined) array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), - ' + ' ' ), // Load module script with ESI array( array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ), - ' + ' ' ), // Load module styles with ESI array( array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), - ' + ' ', ), // 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 ), - ' + ' ' ), // Load two modules in separate groups array( array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ), - ' - + ' + ' ), ); -- 2.20.1