}
$ret .= $this->buildCssLinks() . "\n";
-
$ret .= $this->getHeadScripts() . "\n";
foreach ( $this->mHeadItems as $item ) {
}
/**
- * @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
- */
- public function makeResourceLoaderLink( $modules, $only, array $extraQuery = array(),
- $loadCall = false
- ) {
+ * @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() ) {
$modules = (array)$modules;
$links = array(
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'];
}
// 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';
- }
- }
}
if ( $group == 'noscript' ) {
* @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
);
if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $links[] = $this->getScriptsForBottomQueue( true );
+ $links[] = $this->getScriptsForBottomQueue();
}
return self::getHtmlFromLoaderLinks( $links );
* '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
$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 ) )
);
}
// 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(
// 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 );
}
// 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();
}
/**
}, 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 ) ) );
$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(
'startup' => $rl->getModule( 'startup' ),
'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'] ) );
$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' );
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( {
( 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();
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 );
}
/**
- * 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 );
}
/**
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 ) {
}
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'.
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 }
* @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];
return;
}
queue.push( module );
- if ( async ) {
- registry[module].async = true;
- }
}
} );
}
/**
- * 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 ) + '&*' );
}
/**
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 = {
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
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 } );
}
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 );
}
}
}
* @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
// 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
return;
}
if ( type === 'text/javascript' || type === undefined ) {
- addScript( modules, null, async );
+ addScript( modules );
return;
}
// Unknown type
return;
}
// Since some modules are not yet ready, queue up a request.
- request( filtered, undefined, undefined, async );
+ request( filtered, undefined, undefined );
},
/**
// 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' );
}
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&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script>'
+ '<script async src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script>'
),
// Load module styles only
// This also tests the order the modules are put into the url
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>"
),
);