* @param $only String ResourceLoaderModule TYPE_ class constant
* @param $useESI boolean
* @param $extraQuery Array with extra query parameters to add to each request. array( param => value )
+ * @param $loadCall boolean If true, output a mw.loader.load() call rather than a <script src="..."> tag
* @return string html <script> and <style> tags
*/
- protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array() ) {
+ protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
global $wgResourceLoaderUseESI, $wgResourceLoaderInlinePrivateModules;
if ( !count( $modules ) ) {
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
+ } else if ( $loadCall ) {
+ $link = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $url ) )
+ )
+ );
} else {
$link = Html::linkedScript( $url );
}
* @return String: HTML fragment
*/
function getHeadScripts() {
+ global $wgResourceLoaderExperimentalAsyncLoading;
+
// Startup - this will immediately load jquery and mediawiki modules
$scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
if ( $modules ) {
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
+ "mw.loader.setBlocking( true );\n" .
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) .
+ "\nmw.loader.setBlocking( false );"
)
);
}
+
+ if ( $wgResourceLoaderExperimentalAsyncLoading ) {
+ $scripts .= $this->getScriptsForBottomQueue( true );
+ }
return $scripts;
}
/**
- * JS stuff to put at the bottom of the <body>: modules marked with position 'bottom',
- * legacy scripts ($this->mScripts), user preferences, site JS and user JS
+ * JS stuff to put at the 'bottom', which can either be the bottom of the <body>
+ * or the bottom of the <head> depending on $wgResourceLoaderExperimentalAsyncLoading:
+ * modules marked with position 'bottom', legacy scripts ($this->mScripts),
+ * user preferences, site JS and user JS
*
+ * @param $inHead boolean If true, this HTML goes into the <head>, if false it goes into the <body>
* @return string
*/
- function getBottomScripts() {
+ function getScriptsForBottomQueue( $inHead ) {
global $wgUseSiteJs, $wgAllowUserJs;
// Script and Messages "only" requests marked for bottom inclusion
+ // If we're in the <head>, use load() calls rather than <script src="..."> tags
// Messages should go first
- $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), ResourceLoaderModule::TYPE_MESSAGES );
- $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
+ /* $loadCall = */ $inHead
+ );
+ $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
+ /* $loadCall = */ $inHead
+ );
// Modules requests - let the client calculate dependencies and batch requests as it likes
// Only load modules that have marked themselves for loading at the bottom
// Add site JS if enabled
if ( $wgUseSiteJs ) {
- $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
if( $this->getUser()->isLoggedIn() ){
$userScripts[] = 'user.groups';
}
// We're on a preview of a JS subpage
// Exclude this page from the user module in case it's in there (bug 26283)
$scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
);
// Load the previewed JS
$scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
// Include the user module normally
// We can't do $userScripts[] = 'user'; because the user module would end up
// being wrapped in a closure, so load it raw like 'site'
- $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
}
}
- $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED );
+ $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
return $scripts;
}
+ /**
+ * JS stuff to put at the bottom of the <body>
+ */
+ function getBottomScripts() {
+ global $wgResourceLoaderExperimentalAsyncLoading;
+ if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
+ return $this->getScriptsForBottomQueue( false );
+ } else {
+ return '';
+ }
+ }
+
/**
* Add one or more variables to be set in mw.config in JavaScript.
*
queue = [],
// List of callback functions waiting for modules to be ready to be called
jobs = [],
- // Flag inidicating that document ready has occured
+ // Flag indicating that document ready has occured
ready = false,
+ // Whether we should try to load scripts in a blocking way
+ // Set with setBlocking()
+ blocking = false,
// Selector cache for the marker element. Use getMarker() to get/use the marker!
$marker = null;
}
/**
- * Adds a script tag to the body, either using document.write or low-level DOM manipulation,
- * depending on whether document-ready has occured yet.
+ * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
+ * depending on whether document-ready has occured yet and whether we are in blocking mode.
*
* @param src String: URL to script, will be used as the src attribute in the script tag
* @param callback Function: Optional callback which will be run when the script is done
*/
function addScript( src, callback ) {
- var done = false, script;
- if ( ready ) {
+ var done = false, script, head;
+ if ( ready || !blocking ) {
// jQuery's getScript method is NOT better than doing this the old-fashioned way
// because jQuery will eval the script's code, and errors will not have sane
// line numbers.
}
};
}
- document.body.appendChild( script );
+ // IE-safe way of getting the <head> . document.documentElement.head doesn't
+ // work in scripts that run in the <head>
+ head = document.getElementsByTagName( 'head' )[0];
+ // Append to the <body> if available, to the <head> otherwise
+ (document.body || head).appendChild( script );
} else {
document.write( mw.html.element(
'script', { 'type': 'text/javascript', 'src': src }, ''
) );
if ( $.isFunction( 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();
}
}
return key;
} );
},
+
+ /**
+ * Enable or disable blocking. If blocking is enabled and
+ * document ready has not yet occurred, scripts will be loaded
+ * in a blocking way (using document.write) rather than
+ * asynchronously using DOM manipulation
+ *
+ * @param b {Boolean} True to enable blocking, false to disable it
+ */
+ setBlocking: function( b ) {
+ blocking = b;
+ },
/**
* For backwards-compatibility with Squid-cached pages. Loads mw.user