Anomie has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/347441 )
Change subject: Parser: Allow embedding ResourceLoader style modules inline,
with deduplication
......................................................................
Parser: Allow embedding ResourceLoader style modules inline, with deduplication
It would be advantageous from a performance perspective if some vague
unspecified set of extensions, including TemplateStyles, were able to
embed their stylesheets in the page content with deduplication. This
implements that by using ResourceLoader style modules.
It also adds a new flag to ParserOptions::setCurrentRevisionCallback()
so users of that method can indicate when the replacements they're
making are safe to be used to replace the content of stylesheets loaded
from wiki pages.
Bug: T160563
Change-Id: Ibc3fc3723d163b91709d9dd31e4a4c7d9df83804
---
M RELEASE-NOTES-1.29
M includes/parser/Parser.php
M includes/parser/ParserOptions.php
3 files changed, 130 insertions(+), 1 deletion(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/41/347441/1
diff --git a/RELEASE-NOTES-1.29 b/RELEASE-NOTES-1.29
index a72008c..8b6ebac 100644
--- a/RELEASE-NOTES-1.29
+++ b/RELEASE-NOTES-1.29
@@ -64,6 +64,9 @@
* (T112474) Generalized the ResourceLoader mechanism for overriding modules
using a particular page during edit previews.
* Added 'ApiParseOutputPageForHeadHtml' hook.
+* (T160563) The PHP parser now supports embedding ResourceLoader style modules
+ inline in the parsed HTML, with deduplication if the same ResourceLoader
+ module is embedded multiple times.
=== External library changes in 1.29 ===
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 953f021..20dab90 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -20,7 +20,9 @@
* @file
* @ingroup Parser
*/
+
use MediaWiki\Linker\LinkRenderer;
+use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use Wikimedia\ScopedCallback;
@@ -258,6 +260,9 @@
*/
protected $mLinkRenderer;
+ /** @var DerivativeResourceLoaderContext|null */
+ protected $mResourceLoaderContext;
+
/**
* @param array $conf
*/
@@ -384,6 +389,8 @@
}
$this->mProfiler = new SectionProfiler();
+
+ $this->mResourceLoaderContext = null;
// Avoid PHP 7.1 warning from passing $this by reference
$parser = $this;
@@ -948,6 +955,112 @@
}
/**
+ * Get a ResourceLoaderContext to use for embedding stylesheets
+ * @since 1.29
+ * @return DerivativeResourceLoaderContext
+ */
+ private function getResourceLoaderContext() {
+ if ( !$this->mResourceLoaderContext ) {
+ $query = ResourceLoader::makeLoaderQuery(
+ [], // modules; not relevant
+ $this->getTargetLanguage()->getCode(),
+ 'fallback', // skin; not relevant
+ null, // user; not relevant
+ null, // version; not relevant
+ ResourceLoader::inDebugMode(),
+ ResourceLoaderModule::TYPE_STYLES,
+ $this->getOptions()->getIsPrintable()
+ );
+ $this->mResourceLoaderContext = new
DerivativeResourceLoaderContext( new ResourceLoaderContext(
+ new ResourceLoader(
+ RequestContext::getMain()->getConfig(),
+ LoggerFactory::getInstance(
'resourceloader' )
+ ),
+ new FauxRequest( $query )
+ ) );
+
$this->mResourceLoaderContext->setContentOverrideCallback( function ( Title
$title ) {
+ if (
$this->getOptions()->getCurrentRevisionCallbackIsSafe() ) {
+ return
$this->fetchCurrentRevisionOfTitle( $title ) ?: null;
+ } else {
+ return static::statelessFetchRevision(
$title, $this ) ?: null;
+ }
+ return null;
+ } );
+ }
+
+ return $this->mResourceLoaderContext;
+ }
+
+ /**
+ * Return a strip marker for an embedded stylesheet
+ * @since 1.29
+ * @param string $module ResourceLoader module name
+ * @return string
+ */
+ public function getEmbeddedStyleModuleStripItem( $module ) {
+ $context = $this->getResourceLoaderContext();
+ $context->setModules( [ $module ] );
+
+ // We use a <link> tag here to serve two purposes:
+ // 1. If self::embedStyleModules() somehow doesn't get called,
the
+ // styles will still be loaded (just less efficiently).
+ // 2. We could do <style> here and then deduplicate in
self::embedStyleModules(),
+ // but that would require actually processing the RL modules
every
+ // time here and storing their content in the StripState,
only for
+ // it to be removed later.
+ $text = Html::element( 'link', [
+ 'rel' => 'stylesheet',
+ 'href' =>
$context->getResourceLoader()->createLoaderURL( 'local', $context ),
+ 'data-mw-embed-module' => $module,
+ ] );
+
+ $marker = self::MARKER_PREFIX .
"-embeddedstyle-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
+ $this->mMarkerIndex++;
+ $this->mStripState->addGeneral( $marker, $text );
+ return $marker;
+
+ }
+
+ /**
+ * Actually embed style modules
+ * @since 1.29
+ * @param string $text HTML text to process
+ * @return string Processed HTML text
+ */
+ protected function embedStyleModules( $text ) {
+ $context = $this->getResourceLoaderContext();
+ $rl = $context->getResourceLoader();
+ $seen = [];
+ return preg_replace_callback(
+ '!<link rel="stylesheet" href="[^"]*"
data-mw-embed-module="([^"]*)"/>!i',
+ function ( $m ) use ( $context, $rl, &$seen ) {
+ $moduleName = $m[1];
+ $content = '';
+ if ( !isset( $seen[$moduleName] ) ) {
+ $seen[$moduleName] = true;
+ $module = $rl->getModule( $moduleName );
+ if ( $module && $module->getSource()
=== 'local' ) {
+ $context->setModules( [
$moduleName ] );
+ $content =
$rl->makeModuleResponse( $context, [ $moduleName => $module ] );
+ $content = strtr( $content, [
+ '<' => '\3C ',
+ // CDATA end tag for
good measure
+ ']]>' => '\5D\5D\3E '
+ ] );
+ if ( preg_match( '/[<&]/',
$content ) ) {
+ $content =
"/*<![CDATA[*/$content/*]]>*/";
+ }
+ }
+ }
+ return Html::rawElement( 'style', [
+ 'data-mw-embedded-module' =>
$moduleName,
+ ], $content );
+ },
+ $text
+ );
+ }
+
+ /**
* Replaces all occurrences of HTML-style comments and the given tags
* in the text with a random marker and returns the next text. The
output
* parameter $matches will be an associative array filled with data in
@@ -1388,6 +1501,7 @@
$text = $this->mStripState->unstripNoWiki( $text );
if ( $isMain ) {
+ $text = $this->embedStyleModules( $text );
Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
}
diff --git a/includes/parser/ParserOptions.php
b/includes/parser/ParserOptions.php
index 2cdd8c7..9ac6f14 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -124,6 +124,11 @@
[ 'Parser', 'statelessFetchRevision' ];
/**
+ * @var bool Whether it's safe to use $this->mCurrentRevisionCallback
to load JavaScript
+ */
+ private $mCurrentRevisionCallbackIsSafe = false;
+
+ /**
* @var callable Callback for template fetching; first argument to
call_user_func().
*/
private $mTemplateCallback =
@@ -332,6 +337,11 @@
/* @since 1.24 */
public function getCurrentRevisionCallback() {
return $this->mCurrentRevisionCallback;
+ }
+
+ /* @since 1.29 */
+ public function getCurrentRevisionCallbackIsSafe() {
+ return $this->mCurrentRevisionCallbackIsSafe;
}
public function getTemplateCallback() {
@@ -551,7 +561,9 @@
}
/* @since 1.24 */
- public function setCurrentRevisionCallback( $x ) {
+ /* @since 1.29 Added the $isSafe parameter */
+ public function setCurrentRevisionCallback( $x, $isSafe = false ) {
+ $this->mCurrentRevisionCallbackIsSafe = (bool)$isSafe;
return wfSetVar( $this->mCurrentRevisionCallback, $x );
}
--
To view, visit https://gerrit.wikimedia.org/r/347441
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibc3fc3723d163b91709d9dd31e4a4c7d9df83804
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits