/** * Build html output from an array of links from makeResourceLoaderLink. * @param array $links * @return string HTML */ protected static function getHtmlFromLoaderLinks(array $links) { $html = ''; $states = array(); foreach ($links as $link) { if (!is_array($link)) { $html .= $link; } else { $html .= $link['html']; $states += $link['states']; } } if (count($states)) { $html = ResourceLoader::makeInlineScript(ResourceLoader::makeLoaderStateScript($states)) . "\n" . $html; } return $html; }
/** * Build html output from an array of links from makeResourceLoaderLink. * @param array $links * @return string HTML */ protected static function getHtmlFromLoaderLinks(array $links) { $html = array(); $states = array(); foreach ($links as $link) { if (!is_array($link)) { $html[] = $link; } else { $html = array_merge($html, $link['html']); $states += $link['states']; } } // Filter out empty values $html = array_filter($html, 'strlen'); if (count($states)) { array_unshift($html, ResourceLoader::makeInlineScript(ResourceLoader::makeLoaderStateScript($states))); } return WrappedString::join("\n", $html); }
function outputInlineScript($moduleList) { $o = ""; $modules = array(); $resolvedModuleDependencyList = $this->getModuleDependencyList($moduleList); // "Fake" the request headers as ResourceLoaderContext derives it's data for module resolving from them $_GET['only'] = NULL; $_GET['modules'] = ResourceLoader::makePackedModulesString($resolvedModuleDependencyList); $fauxRequest = new WebRequest(); $resourceLoader = new MwEmbedResourceLoader(); foreach ($resolvedModuleDependencyList as $moduleName) { $modules[$moduleName] = $resourceLoader->getModule($moduleName); } $s = $resourceLoader->makeModuleResponse(new MwEmbedResourceLoaderContext($resourceLoader, $fauxRequest), $modules, array()); $o .= 'window.inlineScript = true;'; $o .= $s; $o .= ResourceLoader::makeLoaderStateScript(array_fill_keys($resolvedModuleDependencyList, 'ready')); return $o; }
/** * BeforePageDisplay hook handler. * @param $out OutputPage */ public static function beforePageDisplay($out) { global $wgUser; wfProfileIn(__METHOD__); $gadgets = Gadget::loadList(); if (!$gadgets) { wfProfileOut(__METHOD__); return true; } $lb = new LinkBatch(); $lb->setCaller(__METHOD__); $pages = array(); $stylesModules = array(); foreach ($gadgets as $gadget) { if ($gadget->isEnabled($wgUser) && $gadget->isAllowed($wgUser)) { if ($gadget->hasModule()) { $out->addModules($gadget->getModuleName()); } if ($gadget->hasStylesModule()) { $stylesModules[] = $gadget->getStylesModuleName(); } foreach ($gadget->getLegacyScripts() as $page) { $lb->add(NS_MEDIAWIKI, $page); $pages[] = $page; } } } if (count($stylesModules)) { $out->addHeadItem('ext.gadget', $out->makeResourceLoaderLink($stylesModules, ResourceLoaderModule::TYPE_STYLES) . Html::inlineScript(ResourceLoader::makeLoaderConditionalScript(ResourceLoader::makeLoaderStateScript(array_fill_keys($stylesModules, 'ready'))))); } $lb->execute(__METHOD__); $done = array(); foreach ($pages as $page) { if (isset($done[$page])) { continue; } $done[$page] = true; self::applyScript($page, $out); } wfProfileOut(__METHOD__); return true; }
/** * The order of elements in the head is as follows: * - Inline scripts. * - Stylesheets. * - Async external script-src. * * Reasons: * - Script execution may be blocked on preceeding stylesheets. * - Async scripts are not blocked on stylesheets. * - Inline scripts can't be asynchronous. * - For styles, earlier is better. * * @return string|WrappedStringList HTML */ public function getHeadHtml() { $data = $this->getData(); $chunks = []; // Change "client-nojs" class to client-js. This allows easy toggling of UI components. // This happens synchronously on every page view to avoid flashes of wrong content. // See also #getDocumentAttributes() and /resources/src/startup.js. $chunks[] = Html::inlineScript('document.documentElement.className = document.documentElement.className' . '.replace( /(^|\\s)client-nojs(\\s|$)/, "$1client-js$2" );'); // Inline RLQ: Set page variables if ($this->config) { $chunks[] = ResourceLoader::makeInlineScript(ResourceLoader::makeConfigSetScript($this->config)); } // Inline RLQ: Initial module states $states = array_merge($this->exemptStates, $data['states']); if ($states) { $chunks[] = ResourceLoader::makeInlineScript(ResourceLoader::makeLoaderStateScript($states)); } // Inline RLQ: Embedded modules if ($data['embed']['general']['top']) { $chunks[] = $this->getLoad($data['embed']['general']['top'], ResourceLoaderModule::TYPE_COMBINED); } // Inline RLQ: Load general modules if ($data['general']['top']) { $chunks[] = ResourceLoader::makeInlineScript(Xml::encodeJsCall('mw.loader.load', [$data['general']['top']])); } // Inline RLQ: Load only=scripts if ($data['scripts']['top']) { $chunks[] = $this->getLoad($data['scripts']['top'], ResourceLoaderModule::TYPE_SCRIPTS); } // External stylesheets if ($data['styles']) { $chunks[] = $this->getLoad($data['styles'], ResourceLoaderModule::TYPE_STYLES); } // Inline stylesheets (embedded only=styles) if ($data['embed']['styles']) { $chunks[] = $this->getLoad($data['embed']['styles'], ResourceLoaderModule::TYPE_STYLES); } // Async scripts. Once the startup is loaded, inline RLQ scripts will run. // Pass-through a custom target from OutputPage (T143066). $startupQuery = $this->target ? ['target' => $this->target] : []; $chunks[] = $this->getLoad('startup', ResourceLoaderModule::TYPE_SCRIPTS, $startupQuery); return WrappedStringList::join("\n", $chunks); }
/** * 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 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, false, array(), $inHead); $scripts .= $this->makeResourceLoaderLink($this->getModuleScripts(true, 'bottom'), ResourceLoaderModule::TYPE_SCRIPTS, false, array(), $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 $modules = $this->getModules(true, 'bottom'); if ($modules) { $scripts .= Html::inlineScript(ResourceLoader::makeLoaderConditionalScript(Xml::encodeJsCall('mw.loader.load', array($modules, null, true)))); } // Legacy Scripts $scripts .= "\n" . $this->mScripts; $defaultModules = array(); // Add site JS if enabled if ($wgUseSiteJs) { $scripts .= $this->makeResourceLoaderLink('site', ResourceLoaderModule::TYPE_SCRIPTS, false, array(), $inHead); $defaultModules['site'] = 'loading'; } else { // The wiki is configured to not allow a site module. $defaultModules['site'] = 'missing'; } // Add user JS if enabled if ($wgAllowUserJs) { if ($this->getUser()->isLoggedIn()) { if ($this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview()) { # XXX: additional security check/prompt? // 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()), $inHead); // Load the previewed JS $scripts .= Html::inlineScript("\n" . $this->getRequest()->getText('wpTextbox1') . "\n") . "\n"; // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded // asynchronously and may arrive *after* the inline script here. So the previewed code // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js... } else { // Include the user module normally, i.e., raw to avoid it being wrapped in a closure. $scripts .= $this->makeResourceLoaderLink('user', ResourceLoaderModule::TYPE_SCRIPTS, false, array(), $inHead); } $defaultModules['user'] = '******'; } else { // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid // blocking default gadgets that might depend on it. Although arguably default-enabled // gadgets should not depend on the user module, it's harmless and less error-prone to // handle this case. $defaultModules['user'] = '******'; } } else { // User JS disabled $defaultModules['user'] = '******'; } // Group JS is only enabled if site JS is enabled. if ($wgUseSiteJs) { if ($this->getUser()->isLoggedIn()) { $scripts .= $this->makeResourceLoaderLink('user.groups', ResourceLoaderModule::TYPE_COMBINED, false, array(), $inHead); $defaultModules['user.groups'] = 'loading'; } else { // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to // avoid blocking gadgets that might depend upon the module. $defaultModules['user.groups'] = 'ready'; } } else { // Site (and group JS) disabled $defaultModules['user.groups'] = 'missing'; } $loaderInit = ''; if ($inHead) { // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'. foreach ($defaultModules as $m => $state) { if ($state == 'loading') { unset($defaultModules[$m]); } } } if (count($defaultModules) > 0) { $loaderInit = Html::inlineScript(ResourceLoader::makeLoaderConditionalScript(ResourceLoader::makeLoaderStateScript($defaultModules))) . "\n"; } return $loaderInit . $scripts; }