From a68e641f9d4f6f71cfb6ef6584a0c9cdaf17287f Mon Sep 17 00:00:00 2001 From: Roman Stolar Date: Tue, 24 Aug 2021 15:17:12 +0300 Subject: [PATCH] Move Content::getParserOutput & AbstractContent::fillParserOutput to ContentHandler Update/Create override classes of ContentHandler. Soft-deprecate and remove method from Content and classes that override them. Bug: T287158 Change-Id: Idfcfbfe1a196cd69a04ca357281d08bb3d097ce2 --- RELEASE-NOTES-1.38 | 6 +- includes/MediaWikiServices.php | 9 ++ includes/ServiceWiring.php | 5 + includes/content/AbstractContent.php | 79 ++++++----- includes/content/Content.php | 3 +- includes/content/ContentHandler.php | 133 ++++++++++++++++++ includes/content/CssContent.php | 10 -- includes/content/CssContentHandler.php | 39 +++++ includes/content/FallbackContent.php | 16 --- includes/content/FallbackContentHandler.php | 21 +++ includes/content/JavaScriptContent.php | 10 -- includes/content/JavaScriptContentHandler.php | 52 +++++++ includes/content/JsonContent.php | 24 +--- includes/content/JsonContentHandler.php | 25 ++++ includes/content/MessageContent.php | 39 +---- .../content/Renderer/ContentParseParams.php | 67 +++++++++ includes/content/Renderer/ContentRenderer.php | 49 +++++++ includes/content/TextContent.php | 56 -------- includes/content/TextContentHandler.php | 58 ++++++++ includes/content/WikitextContent.php | 61 ++------ includes/content/WikitextContentHandler.php | 45 ++++++ includes/language/Message.php | 3 +- includes/parser/Parser.php | 2 +- tests/phpunit/MediaWikiTestCaseTrait.php | 1 - .../includes/content/MessageContentTest.php | 20 +-- .../parser/ParserObserverIntegrationTest.php | 6 +- .../mocks/content/DummyContentForTesting.php | 29 ---- .../content/DummyContentHandlerForTesting.php | 19 +++ .../mocks/content/DummyNonTextContent.php | 29 ---- .../content/DummyNonTextContentHandler.php | 18 +++ .../structure/ContentHandlerSanityTest.php | 10 +- 31 files changed, 625 insertions(+), 319 deletions(-) create mode 100644 includes/content/Renderer/ContentParseParams.php create mode 100644 includes/content/Renderer/ContentRenderer.php diff --git a/RELEASE-NOTES-1.38 b/RELEASE-NOTES-1.38 index dbf40fcb749..c3d997123b2 100644 --- a/RELEASE-NOTES-1.38 +++ b/RELEASE-NOTES-1.38 @@ -149,7 +149,11 @@ because of Phabricator reports. * The following methods from the User class were hard deprecated: - ::blockedBy - ::getBlockId - +* Content::getParserOutput() was deprecated. + Use ContentRenderer::getParserOutput and override + ContentHandler::fillParserOutput instead. +* MessageContent class was hard-deprecated. +* Message::content() was hard-deprecated. * … === Other changes in 1.38 === diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index d45c51566d2..90f329be419 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -43,6 +43,7 @@ use MediaWiki\Cache\LinkBatchFactory; use MediaWiki\Collation\CollationFactory; use MediaWiki\Config\ConfigRepository; use MediaWiki\Content\IContentHandlerFactory; +use MediaWiki\Content\Renderer\ContentRenderer; use MediaWiki\Content\Transform\ContentTransformer; use MediaWiki\EditPage\SpamChecker; use MediaWiki\Export\WikiExporterFactory; @@ -820,6 +821,14 @@ class MediaWikiServices extends ServiceContainer { return $this->getService( 'ContentModelStore' ); } + /** + * @since 1.38 + * @return ContentRenderer + */ + public function getContentRenderer(): ContentRenderer { + return $this->getService( 'ContentRenderer' ); + } + /** * @since 1.37 * @return ContentTransformer diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 140e88f7f54..92de100de36 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -65,6 +65,7 @@ use MediaWiki\Config\ConfigRepository; use MediaWiki\Config\ServiceOptions; use MediaWiki\Content\ContentHandlerFactory; use MediaWiki\Content\IContentHandlerFactory; +use MediaWiki\Content\Renderer\ContentRenderer; use MediaWiki\Content\Transform\ContentTransformer; use MediaWiki\EditPage\Constraint\EditConstraintFactory; use MediaWiki\EditPage\SpamChecker; @@ -392,6 +393,10 @@ return [ return $services->getNameTableStoreFactory()->getContentModels(); }, + 'ContentRenderer' => static function ( MediaWikiServices $services ): ContentRenderer { + return new ContentRenderer( $services->getContentHandlerFactory() ); + }, + 'ContentTransformer' => static function ( MediaWikiServices $services ): ContentTransformer { return new ContentTransformer( $services->getContentHandlerFactory() ); }, diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php index 23212b1d3ce..4f1e436a31d 100644 --- a/includes/content/AbstractContent.php +++ b/includes/content/AbstractContent.php @@ -27,6 +27,7 @@ */ use MediaWiki\Content\IContentHandlerFactory; +use MediaWiki\Content\Renderer\ContentParseParams; use MediaWiki\Content\Transform\PreloadTransformParamsValue; use MediaWiki\Content\Transform\PreSaveTransformParamsValue; use MediaWiki\MediaWikiServices; @@ -517,10 +518,9 @@ abstract class AbstractContent implements Content { * Subclasses that override getParserOutput() itself should take care to call the * ContentGetParserOutput hook. * - * @stable to override - * * @since 1.24 - * + * @deprecated since 1.38. Use ContentRenderer::getParserOutput instead. + * Extensions defining a content model should override ContentHandler::fillParserOutput. * @param Title $title Context title for parsing * @param int|null $revId Revision ID being rendered * @param ParserOptions|null $options @@ -531,34 +531,47 @@ abstract class AbstractContent implements Content { public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true ) { - if ( $options === null ) { - $options = ParserOptions::newCanonical( 'canonical' ); + $detectGPODeprecatedOverride = MWDebug::detectDeprecatedOverride( + $this, + self::class, + 'getParserOutput' + ); + $detectFPODeprecatedOverride = MWDebug::detectDeprecatedOverride( + $this, + self::class, + 'fillParserOutput' + ); + + if ( $detectGPODeprecatedOverride || $detectFPODeprecatedOverride ) { + if ( $options === null ) { + $options = ParserOptions::newCanonical( 'canonical' ); + } + + $po = new ParserOutput(); + $options->registerWatcher( [ $po, 'recordOption' ] ); + + if ( Hooks::runner()->onContentGetParserOutput( + $this, $title, $revId, $options, $generateHtml, $po ) + ) { + // Save and restore the old value, just in case something is reusing + // the ParserOptions object in some weird way. + $oldRedir = $options->getRedirectTarget(); + $options->setRedirectTarget( $this->getRedirectTarget() ); + $this->fillParserOutput( $title, $revId, $options, $generateHtml, $po ); + $options->setRedirectTarget( $oldRedir ); + } + + Hooks::runner()->onContentAlterParserOutput( $this, $title, $po ); + $options->registerWatcher( null ); + + return $po; } - $output = new ParserOutput(); - $options->registerWatcher( [ $output, 'recordOption' ] ); - - if ( Hooks::runner()->onContentGetParserOutput( - $this, $title, $revId, $options, $generateHtml, $output ) - ) { - // Save and restore the old value, just in case something is reusing - // the ParserOptions object in some weird way. - $oldRedir = $options->getRedirectTarget(); - $options->setRedirectTarget( $this->getRedirectTarget() ); - $this->fillParserOutput( $title, $revId, $options, $generateHtml, $output ); - MediaWikiServices::getInstance()->get( '_ParserObserver' )->notifyParse( - $title, - $revId, - $options, - $output - ); - $options->setRedirectTarget( $oldRedir ); - } - - Hooks::runner()->onContentAlterParserOutput( $this, $title, $output ); - $options->registerWatcher( null ); - - return $output; + $cpoParams = new ContentParseParams( $title, $revId, $options, $generateHtml ); + return $this->getContentHandler()->getParserOutput( + $this, + $cpoParams + ); } /** @@ -571,10 +584,8 @@ abstract class AbstractContent implements Content { * * This placeholder implementation always throws an exception. * - * @stable to override - * * @since 1.24 - * + * @deprecated since 1.37. Use ContentHandler::fillParserOutput instead. * @param Title $title Context title for parsing * @param int|null $revId ID of the revision being rendered. * See Parser::parse() for the ramifications. @@ -587,7 +598,7 @@ abstract class AbstractContent implements Content { protected function fillParserOutput( Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output ) { - // Don't make abstract, so subclasses that override getParserOutput() directly don't fail. - throw new MWException( 'Subclasses of AbstractContent must override fillParserOutput!' ); + $cpoParams = new ContentParseParams( $title, $revId, $options, $generateHtml ); + return $this->getContentHandler()->fillParserOutputInternal( $this, $cpoParams, $output ); } } diff --git a/includes/content/Content.php b/includes/content/Content.php index 37e85b33cef..9dfb01746f0 100644 --- a/includes/content/Content.php +++ b/includes/content/Content.php @@ -268,7 +268,8 @@ interface Content { * @note To control which options are used in the cache key for the * generated parser output, implementations of this method * may call ParserOutput::recordOption() on the output object. - * + * @deprecated since 1.38. Use ContentRenderer::getParserOutput + * and override ContentHandler::fillParserOutput. * @param Title $title The page title to use as a context for rendering. * @param int|null $revId ID of the revision being rendered. * See Parser::parse() for the ramifications. (default: null) diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php index dcd5f950063..e5ebc3cc3f9 100644 --- a/includes/content/ContentHandler.php +++ b/includes/content/ContentHandler.php @@ -26,6 +26,7 @@ * @author Daniel Kinzler */ +use MediaWiki\Content\Renderer\ContentParseParams; use MediaWiki\Content\Transform\PreloadTransformParams; use MediaWiki\Content\Transform\PreSaveTransformParams; use MediaWiki\HookContainer\ProtectedHookAccessorTrait; @@ -1606,6 +1607,116 @@ abstract class ContentHandler { ); } + /** + * Returns a ParserOutput object containing information derived from this content. + * Most importantly, unless $cpoParams->getGenerateHtml was false, the return value contains an + * HTML representation of the content. + * + * Subclasses that want to control the parser output may override + * fillParserOutput() instead. + * + * + * + * @since 1.38 + * + * @param Content $content + * @param ContentParseParams $cpoParams + * @return ParserOutput Containing information derived from this content. + */ + public function getParserOutput( + Content $content, + ContentParseParams $cpoParams + ) { + $detectGPODeprecatedOverride = MWDebug::detectDeprecatedOverride( + $content, + AbstractContent::class, + 'getParserOutput' + ); + $detectFPODeprecatedOverride = MWDebug::detectDeprecatedOverride( + $content, + AbstractContent::class, + 'fillParserOutput' + ); + if ( $detectGPODeprecatedOverride || $detectFPODeprecatedOverride ) { + return $this->callDeprecatedContentGPO( $content, $cpoParams ); + } + + $services = MediaWikiServices::getInstance(); + $title = $services->getTitleFactory()->castFromPageReference( $cpoParams->getPage() ); + $parserOptions = $cpoParams->getParserOptions(); + + $po = new ParserOutput(); + $parserOptions->registerWatcher( [ $po, 'recordOption' ] ); + if ( Hooks::runner()->onContentGetParserOutput( + $content, $title, $cpoParams->getRevId(), $parserOptions, $cpoParams->getGenerateHtml(), $po ) + ) { + // Save and restore the old value, just in case something is reusing + // the ParserOptions object in some weird way. + $oldRedir = $parserOptions->getRedirectTarget(); + $parserOptions->setRedirectTarget( $content->getRedirectTarget() ); + $this->fillParserOutput( + $content, + $cpoParams, + $po + ); + MediaWikiServices::getInstance()->get( '_ParserObserver' )->notifyParse( + $title, + $cpoParams->getRevId(), + $parserOptions, + $po + ); + $parserOptions->setRedirectTarget( $oldRedir ); + } + + Hooks::runner()->onContentAlterParserOutput( $content, $title, $po ); + $parserOptions->registerWatcher( null ); + + return $po; + } + + /** + * A temporary layer to move AbstractContent::fillParserOutput to ContentHandler::fillParserOutput + * + * @internal only core AbstractContent::fillParserOutput implementations need to call this. + * @since 1.38 + * @param Content $content + * @param ContentParseParams $cpoParams + * @param ParserOutput &$output The output object to fill (reference). + */ + public function fillParserOutputInternal( + Content $content, + ContentParseParams $cpoParams, + ParserOutput &$output + ) { + $this->fillParserOutput( $content, $cpoParams, $output ); + } + + /** + * Fills the provided ParserOutput with information derived from the content. + * Unless $generateHtml was false, this includes an HTML representation of the content. + * + * Subclasses are expected to override this method. + * + * This placeholder implementation always throws an exception. + * + * @stable to override + * + * @since 1.38 + * @param Content $content + * @param ContentParseParams $cpoParams + * @param ParserOutput &$output The output object to fill (reference). + * + * @throws MWException + */ + protected function fillParserOutput( + Content $content, + ContentParseParams $cpoParams, + ParserOutput &$output + ) { + // Subclasses must override fillParserOutput() to directly don't fail. + throw new MWException( 'Subclasses of ContentHandler must override fillParserOutput!' ); + } + /** * Check if we need to provide content overrides deprecated Content method. * @@ -1672,4 +1783,26 @@ abstract class ContentHandler { $params->getParams() ); } + + /** + * If provided content overrides deprecated Content::getParserOutput, + * call it and return. + * @internal only core ContentHandler implementations need to call this. + * @param Content $content + * @param ContentParseParams $cpoParams + * @return ParserOutput + */ + protected function callDeprecatedContentGPO( + Content $content, + ContentParseParams $cpoParams + ) { + $services = MediaWikiServices::getInstance(); + $legacyTitle = $services->getTitleFactory()->castFromPageReference( $cpoParams->getPage() ); + return $content->getParserOutput( + $legacyTitle, + $cpoParams->getRevId(), + $cpoParams->getParserOptions(), + $cpoParams->getGenerateHtml() + ); + } } diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php index 1ad294a088f..d62edf87ca7 100644 --- a/includes/content/CssContent.php +++ b/includes/content/CssContent.php @@ -47,16 +47,6 @@ class CssContent extends TextContent { parent::__construct( $text, $modelId ); } - /** - * @return string CSS wrapped in a
 tag.
-	 */
-	protected function getHtml() {
-		return Html::element( 'pre',
-			[ 'class' => 'mw-code mw-css', 'dir' => 'ltr' ],
-			"\n" . $this->getText() . "\n"
-		) . "\n";
-	}
-
 	/**
 	 * @param Title $target
 	 * @return CssContent
diff --git a/includes/content/CssContentHandler.php b/includes/content/CssContentHandler.php
index 06bc41c609f..49f3bf4cced 100644
--- a/includes/content/CssContentHandler.php
+++ b/includes/content/CssContentHandler.php
@@ -21,6 +21,7 @@
  * @ingroup Content
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\Content\Transform\PreSaveTransformParams;
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Minify\CSSMin;
@@ -99,4 +100,42 @@ class CssContentHandler extends CodeContentHandler {
 		$class = $this->getContentClass();
 		return new $class( $pst );
 	}
+
+	/**
+	 * @inheritDoc
+	 */
+	protected function fillParserOutput(
+		Content $content,
+		ContentParseParams $cpoParams,
+		ParserOutput &$output
+	) {
+		global $wgTextModelsToParse;
+		'@phan-var CssContent $content';
+		if ( in_array( $content->getModel(), $wgTextModelsToParse ) ) {
+			// parse just to get links etc into the database, HTML is replaced below.
+			$output = MediaWikiServices::getInstance()->getParser()
+				->parse(
+					$content->getText(),
+					$cpoParams->getPage(),
+					$cpoParams->getParserOptions(),
+					true,
+					true,
+					$cpoParams->getRevId()
+				);
+		}
+
+		if ( $cpoParams->getGenerateHtml() ) {
+			// Return CSS wrapped in a 
 tag.
+			$html = Html::element(
+				'pre',
+				[ 'class' => 'mw-code mw-css', 'dir' => 'ltr' ],
+				"\n" . $content->getText() . "\n"
+			) . "\n";
+		} else {
+			$html = '';
+		}
+
+		$output->clearWrapperDivClass();
+		$output->setText( $html );
+	}
 }
diff --git a/includes/content/FallbackContent.php b/includes/content/FallbackContent.php
index 7cf3694aaff..78ddbc6088e 100644
--- a/includes/content/FallbackContent.php
+++ b/includes/content/FallbackContent.php
@@ -129,22 +129,6 @@ class FallbackContent extends AbstractContent {
 		return false;
 	}
 
-	/**
-	 * Fills the ParserOutput with an error message.
-	 * @param Title $title
-	 * @param int $revId
-	 * @param ParserOptions $options
-	 * @param bool $generateHtml
-	 * @param ParserOutput &$output
-	 */
-	protected function fillParserOutput( Title $title, $revId,
-		ParserOptions $options, $generateHtml, ParserOutput &$output
-	) {
-		$msg = wfMessage( 'unsupported-content-model', [ $this->getModel() ] );
-		$html = Html::rawElement( 'div', [ 'class' => 'error' ], $msg->inContentLanguage()->parse() );
-		$output->setText( $html );
-	}
-
 	/**
 	 * @param string $toModel
 	 * @param string $lossy
diff --git a/includes/content/FallbackContentHandler.php b/includes/content/FallbackContentHandler.php
index 4ba9e5113bf..11d3cd75c36 100644
--- a/includes/content/FallbackContentHandler.php
+++ b/includes/content/FallbackContentHandler.php
@@ -23,6 +23,8 @@
  * @ingroup Content
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
+
 /**
  * Content handler implementation for unknown content.
  *
@@ -104,6 +106,25 @@ class FallbackContentHandler extends ContentHandler {
 		return false;
 	}
 
+	/**
+	 * Fills the ParserOutput with an error message.
+	 * @since 1.38
+	 * @param Content $content
+	 * @param ContentParseParams $cpoParams
+	 * @param ParserOutput &$output The output object to fill (reference).
+	 *
+	 */
+	protected function fillParserOutput(
+		Content $content,
+		ContentParseParams $cpoParams,
+		ParserOutput &$output
+	) {
+		'@phan-var FallbackContent $content';
+		$msg = wfMessage( 'unsupported-content-model', [ $content->getModel() ] );
+		$html = Html::rawElement( 'div', [ 'class' => 'error' ], $msg->inContentLanguage()->parse() );
+		$output->setText( $html );
+	}
+
 	/**
 	 * @param IContextSource $context
 	 *
diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php
index e053b82b2f7..893764970db 100644
--- a/includes/content/JavaScriptContent.php
+++ b/includes/content/JavaScriptContent.php
@@ -47,16 +47,6 @@ class JavaScriptContent extends TextContent {
 		parent::__construct( $text, $modelId );
 	}
 
-	/**
-	 * @return string JavaScript wrapped in a 
 tag.
-	 */
-	protected function getHtml() {
-		return Html::element( 'pre',
-			[ 'class' => 'mw-code mw-js', 'dir' => 'ltr' ],
-			"\n" . $this->getText() . "\n"
-		) . "\n";
-	}
-
 	/**
 	 * If this page is a redirect, return the content
 	 * if it should redirect to $target instead
diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php
index c236d5b5450..3482439f55d 100644
--- a/includes/content/JavaScriptContentHandler.php
+++ b/includes/content/JavaScriptContentHandler.php
@@ -18,6 +18,7 @@
  * @file
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\Content\Transform\PreSaveTransformParams;
 use MediaWiki\MediaWikiServices;
 
@@ -101,4 +102,55 @@ class JavaScriptContentHandler extends CodeContentHandler {
 		$contentClass = $this->getContentClass();
 		return new $contentClass( $pst );
 	}
+
+	/**
+	 * Fills the provided ParserOutput object with information derived from the content.
+	 * Unless $cpo->getGenerateHtml was false, this includes an HTML representation of the content.
+	 *
+	 * For content models listed in $wgTextModelsToParse, this method will call the MediaWiki
+	 * wikitext parser on the text to extract any (wikitext) links, magic words, etc.
+	 *
+	 * Subclasses may override this to provide custom content processing..
+	 *
+	 * @stable to override
+	 *
+	 * @since 1.38
+	 * @param Content $content
+	 * @param ContentParseParams $cpoParams
+	 * @param ParserOutput &$output The output object to fill (reference).
+	 */
+	protected function fillParserOutput(
+		Content $content,
+		ContentParseParams $cpoParams,
+		ParserOutput &$output
+	) {
+		global $wgTextModelsToParse;
+		'@phan-var TextContent $content';
+		if ( in_array( $content->getModel(), $wgTextModelsToParse ) ) {
+			// parse just to get links etc into the database, HTML is replaced below.
+			$output = MediaWikiServices::getInstance()->getParser()
+				->parse(
+					$content->getText(),
+					$cpoParams->getPage(),
+					$cpoParams->getParserOptions(),
+					true,
+					true,
+					$cpoParams->getRevId()
+				);
+		}
+
+		if ( $cpoParams->getGenerateHtml() ) {
+			// Return JavaScript wrapped in a 
 tag.
+			$html = Html::element(
+				'pre',
+				[ 'class' => 'mw-code mw-js', 'dir' => 'ltr' ],
+				"\n" . $content->getText() . "\n"
+			) . "\n";
+		} else {
+			$html = '';
+		}
+
+		$output->clearWrapperDivClass();
+		$output->setText( $html );
+	}
 }
diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php
index 1e1a6e88174..115abd0b23c 100644
--- a/includes/content/JsonContent.php
+++ b/includes/content/JsonContent.php
@@ -61,28 +61,6 @@ class JsonContent extends TextContent {
 		return FormatJson::encode( $this->getData()->getValue(), true, FormatJson::UTF8_OK );
 	}
 
-	/**
-	 * Set the HTML and add the appropriate styles.
-	 *
-	 * @param Title $title
-	 * @param int $revId
-	 * @param ParserOptions $options
-	 * @param bool $generateHtml
-	 * @param ParserOutput &$output
-	 */
-	protected function fillParserOutput( Title $title, $revId,
-		ParserOptions $options, $generateHtml, ParserOutput &$output
-	) {
-		// FIXME: WikiPage::doEditContent generates parser output before validation.
-		// As such, native data may be invalid (though output is discarded later in that case).
-		if ( $generateHtml && $this->isValid() ) {
-			$output->setText( $this->rootValueTable( $this->getData()->getValue() ) );
-			$output->addModuleStyles( 'mediawiki.content.json' );
-		} else {
-			$output->setText( '' );
-		}
-	}
-
 	/**
 	 * Construct HTML table representation of any JSON value.
 	 *
@@ -91,7 +69,7 @@ class JsonContent extends TextContent {
 	 * @param mixed $val
 	 * @return string HTML.
 	 */
-	protected function rootValueTable( $val ) {
+	public function rootValueTable( $val ) {
 		if ( is_object( $val ) ) {
 			return $this->objectTable( $val );
 		}
diff --git a/includes/content/JsonContentHandler.php b/includes/content/JsonContentHandler.php
index 962c838a313..566888034b6 100644
--- a/includes/content/JsonContentHandler.php
+++ b/includes/content/JsonContentHandler.php
@@ -18,6 +18,7 @@
  * @file
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\Content\Transform\PreSaveTransformParams;
 
 /**
@@ -74,4 +75,28 @@ class JsonContentHandler extends CodeContentHandler {
 		$contentClass = $this->getContentClass();
 		return new $contentClass( JsonContent::normalizeLineEndings( $content->beautifyJSON() ) );
 	}
+
+	/**
+	 * Set the HTML and add the appropriate styles.
+	 *
+	 * @since 1.38
+	 * @param Content $content
+	 * @param ContentParseParams $cpoParams
+	 * @param ParserOutput &$output The output object to fill (reference).
+	 */
+	protected function fillParserOutput(
+		Content $content,
+		ContentParseParams $cpoParams,
+		ParserOutput &$output
+	) {
+		'@phan-var JsonContent $content';
+		// FIXME: WikiPage::doEditContent generates parser output before validation.
+		// As such, native data may be invalid (though output is discarded later in that case).
+		if ( $cpoParams->getGenerateHtml() && $content->isValid() ) {
+			$output->setText( $content->rootValueTable( $content->getData()->getValue() ) );
+			$output->addModuleStyles( 'mediawiki.content.json' );
+		} else {
+			$output->setText( '' );
+		}
+	}
 }
diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php
index 24c7987f22d..b0baaa919bb 100644
--- a/includes/content/MessageContent.php
+++ b/includes/content/MessageContent.php
@@ -18,6 +18,7 @@
  * http://www.gnu.org/copyleft/gpl.html
  *
  * @since 1.21
+ * @deprecated since 1.38.
  *
  * @file
  * @ingroup Content
@@ -28,7 +29,7 @@
 /**
  * Wrapper allowing us to handle a system message as a Content object.
  * Note that this is generally *not* used to represent content from the
- * MediaWiki namespace, and that there is no MessageContentHandler.
+ * MediaWiki namespace.
  * MessageContent is just intended as glue for wrapping a message programmatically.
  *
  * @ingroup Content
@@ -45,6 +46,7 @@ class MessageContent extends AbstractContent {
 	 * @param string[]|null $params An optional array of message parameters.
 	 */
 	public function __construct( $msg, $params = null ) {
+		wfDeprecated( __CLASS__, '1.38' );
 		# XXX: messages may be wikitext, html or plain text! and maybe even something else entirely.
 		parent::__construct( CONTENT_MODEL_WIKITEXT );
 
@@ -59,15 +61,6 @@ class MessageContent extends AbstractContent {
 		}
 	}
 
-	/**
-	 * Fully parse the text from wikitext to HTML.
-	 *
-	 * @return string Parsed HTML.
-	 */
-	public function getHtml() {
-		return $this->mMessage->parse();
-	}
-
 	/**
 	 * Returns the message text. {{-transformation is done.
 	 *
@@ -159,30 +152,4 @@ class MessageContent extends AbstractContent {
 	public function isCountable( $hasLinks = null ) {
 		return false;
 	}
-
-	/**
-	 * @param Title $title Unused.
-	 * @param int|null $revId Unused.
-	 * @param ParserOptions|null $options Unused.
-	 * @param bool $generateHtml Whether to generate HTML (default: true).
-	 *
-	 * @return ParserOutput
-	 *
-	 * @see Content::getParserOutput
-	 */
-	public function getParserOutput( Title $title, $revId = null,
-		ParserOptions $options = null, $generateHtml = true ) {
-		if ( $generateHtml ) {
-			$html = $this->getHtml();
-		} else {
-			$html = '';
-		}
-
-		$po = new ParserOutput( $html );
-		// Message objects are in the user language.
-		$po->recordOption( 'userlang' );
-
-		return $po;
-	}
-
 }
diff --git a/includes/content/Renderer/ContentParseParams.php b/includes/content/Renderer/ContentParseParams.php
new file mode 100644
index 00000000000..718206e4ad3
--- /dev/null
+++ b/includes/content/Renderer/ContentParseParams.php
@@ -0,0 +1,67 @@
+page = $page;
+		$this->parserOptions = $parserOptions ?? ParserOptions::newCanonical( 'canonical' );
+		$this->revId = $revId;
+		$this->generateHtml = $generateHtml;
+	}
+
+	/**
+	 *
+	 * @return PageReference
+	 */
+	public function getPage(): PageReference {
+		return $this->page;
+	}
+
+	/**
+	 *
+	 * @return int|null
+	 */
+	public function getRevId(): ?int {
+		return $this->revId;
+	}
+
+	/**
+	 *
+	 * @return ParserOptions
+	 */
+	public function getParserOptions(): ParserOptions {
+		return $this->parserOptions;
+	}
+
+	/**
+	 *
+	 * @return bool
+	 */
+	public function getGenerateHtml(): bool {
+		return $this->generateHtml;
+	}
+}
diff --git a/includes/content/Renderer/ContentRenderer.php b/includes/content/Renderer/ContentRenderer.php
new file mode 100644
index 00000000000..1f3c50251f3
--- /dev/null
+++ b/includes/content/Renderer/ContentRenderer.php
@@ -0,0 +1,49 @@
+contentHandlerFactory = $contentHandlerFactory;
+	}
+
+	/**
+	 * Returns a ParserOutput object containing information derived from this content.
+	 *
+	 * @param Content $content
+	 * @param PageReference $page
+	 * @param int|null $revId
+	 * @param ParserOptions|null $parserOptions
+	 * @param bool $generateHtml
+	 *
+	 * @return ParserOutput
+	 */
+	public function getParserOutput(
+		Content $content,
+		PageReference $page,
+		?int $revId = null,
+		?ParserOptions $parserOptions = null,
+		bool $generateHtml = true
+	): ParserOutput {
+		$contentHandler = $this->contentHandlerFactory->getContentHandler( $content->getModel() );
+		$cpoParams = new ContentParseParams( $page, $revId, $parserOptions, $generateHtml );
+
+		return $contentHandler->getParserOutput( $content, $cpoParams );
+	}
+}
diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php
index ee532c4d694..9c3f61aff8f 100644
--- a/includes/content/TextContent.php
+++ b/includes/content/TextContent.php
@@ -239,62 +239,6 @@ class TextContent extends AbstractContent {
 		return $diff;
 	}
 
-	/**
-	 * Fills the provided ParserOutput object with information derived from the content.
-	 * Unless $generateHtml was false, this includes an HTML representation of the content
-	 * provided by getHtml().
-	 *
-	 * For content models listed in $wgTextModelsToParse, this method will call the MediaWiki
-	 * wikitext parser on the text to extract any (wikitext) links, magic words, etc.
-	 *
-	 * Subclasses may override this to provide custom content processing.
-	 * For custom HTML generation alone, it is sufficient to override getHtml().
-	 *
-	 * @stable to override
-	 *
-	 * @param Title $title Context title for parsing
-	 * @param int $revId Revision ID (for {{REVISIONID}})
-	 * @param ParserOptions $options
-	 * @param bool $generateHtml Whether or not to generate HTML
-	 * @param ParserOutput &$output The output object to fill (reference).
-	 */
-	protected function fillParserOutput( Title $title, $revId,
-		ParserOptions $options, $generateHtml, ParserOutput &$output
-	) {
-		global $wgTextModelsToParse;
-
-		if ( in_array( $this->getModel(), $wgTextModelsToParse ) ) {
-			// parse just to get links etc into the database, HTML is replaced below.
-			$output = MediaWikiServices::getInstance()->getParser()
-				->parse( $this->getText(), $title, $options, true, true, $revId );
-		}
-
-		if ( $generateHtml ) {
-			$html = $this->getHtml();
-		} else {
-			$html = '';
-		}
-
-		$output->clearWrapperDivClass();
-		$output->setText( $html );
-	}
-
-	/**
-	 * Generates an HTML version of the content, for display. Used by
-	 * fillParserOutput() to provide HTML for the ParserOutput object.
-	 *
-	 * Subclasses may override this to provide a custom HTML rendering.
-	 * If further information is to be derived from the content (such as
-	 * categories), the fillParserOutput() method can be overridden instead.
-	 *
-	 * @stable to override
-	 *
-	 * @return string An HTML representation of the content
-	 */
-	protected function getHtml() {
-		return htmlspecialchars( $this->getText() );
-	}
-
 	/**
 	 * This implementation provides lossless conversion between content models based
 	 * on TextContent.
diff --git a/includes/content/TextContentHandler.php b/includes/content/TextContentHandler.php
index 9c6c7f8a187..b9f99e6dd71 100644
--- a/includes/content/TextContentHandler.php
+++ b/includes/content/TextContentHandler.php
@@ -23,7 +23,9 @@
  * @ingroup Content
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\Content\Transform\PreSaveTransformParams;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Base content handler implementation for flat text contents.
@@ -187,4 +189,60 @@ class TextContentHandler extends ContentHandler {
 		$contentClass = $this->getContentClass();
 		return ( $text === $pst ) ? $content : new $contentClass( $pst, $content->getModel() );
 	}
+
+	/**
+	 * Fills the provided ParserOutput object with information derived from the content.
+	 * Unless $generateHtml was false, this includes an HTML representation of the content
+	 * provided by getHtml().
+	 *
+	 * For content models listed in $wgTextModelsToParse, this method will call the MediaWiki
+	 * wikitext parser on the text to extract any (wikitext) links, magic words, etc.
+	 *
+	 * Subclasses may override this to provide custom content processing.
+	 * For custom HTML generation alone, it is sufficient to override getHtml().
+	 *
+	 * @stable to override
+	 *
+	 * @since 1.38
+	 * @param Content $content
+	 * @param ContentParseParams $cpoParams
+	 * @param ParserOutput &$output The output object to fill (reference).
+	 */
+	protected function fillParserOutput(
+		Content $content,
+		ContentParseParams $cpoParams,
+		ParserOutput &$output
+	) {
+		global $wgTextModelsToParse;
+		'@phan-var TextContent $content';
+		if ( in_array( $content->getModel(), $wgTextModelsToParse ) ) {
+			// parse just to get links etc into the database, HTML is replaced below.
+			$output = MediaWikiServices::getInstance()->getParser()
+				->parse(
+					$content->getText(),
+					$cpoParams->getPage(),
+					$cpoParams->getParserOptions(),
+					true,
+					true,
+					$cpoParams->getRevId()
+				);
+		}
+
+		if ( $cpoParams->getGenerateHtml() ) {
+			// Temporary changes as getHtml() is deprecated, we are working on removing usage of it.
+			if ( method_exists( $content, 'getHtml' ) ) {
+				$method = new ReflectionMethod( 'TextContent', 'getHtml' );
+				$method->setAccessible( true );
+				$html = $method->invoke( $content );
+			} else {
+				// Return an HTML representation of the content
+				$html = htmlspecialchars( $content->getText() );
+			}
+		} else {
+			$html = '';
+		}
+
+		$output->clearWrapperDivClass();
+		$output->setText( $html );
+	}
 }
diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php
index e7c2b1ecb04..99d54573b2c 100644
--- a/includes/content/WikitextContent.php
+++ b/includes/content/WikitextContent.php
@@ -144,7 +144,7 @@ class WikitextContent extends TextContent {
 	 *
 	 * @return array List of two elements: Title|null and string.
 	 */
-	protected function getRedirectTargetAndText() {
+	public function getRedirectTargetAndText() {
 		global $wgMaxRedirects;
 
 		if ( $this->redirectTargetAndText !== null ) {
@@ -279,56 +279,6 @@ class WikitextContent extends TextContent {
 		return $truncatedtext;
 	}
 
-	/**
-	 * Returns a ParserOutput object resulting from parsing the content's text
-	 * using the global Parser service.
-	 *
-	 * @param Title $title
-	 * @param int|null $revId ID of the revision being rendered.
-	 *  See Parser::parse() for the ramifications. (default: null)
-	 * @param ParserOptions $options (default: null)
-	 * @param bool $generateHtml (default: true)
-	 * @param ParserOutput &$output ParserOutput representing the HTML form of the text,
-	 *           may be manipulated or replaced.
-	 */
-	protected function fillParserOutput( Title $title, $revId,
-			ParserOptions $options, $generateHtml, ParserOutput &$output
-	) {
-		list( $redir, $text ) = $this->getRedirectTargetAndText();
-		$output = MediaWikiServices::getInstance()->getParser()
-			->parse( $text, $title, $options, true, true, $revId );
-
-		// Add redirect indicator at the top
-		if ( $redir ) {
-			// Make sure to include the redirect link in pagelinks
-			$output->addLink( $redir );
-			if ( $generateHtml ) {
-				$chain = $this->getRedirectChain();
-				$output->setText(
-					Article::getRedirectHeaderHtml( $title->getPageLanguage(), $chain, false ) .
-					$output->getRawText()
-				);
-				$output->addModuleStyles( 'mediawiki.action.view.redirectPage' );
-			}
-		}
-
-		// Pass along user-signature flag
-		if ( in_array( 'user-signature', $this->preSaveTransformFlags ) ) {
-			$output->setFlag( 'user-signature' );
-		}
-	}
-
-	/**
-	 * @throws MWException
-	 */
-	protected function getHtml() {
-		// @phan-suppress-previous-line PhanPluginNeverReturnMethod
-		throw new MWException(
-			"getHtml() not implemented for wikitext. "
-				. "Use getParserOutput()->getText()."
-		);
-	}
-
 	/**
 	 * This implementation calls $word->match() on the this TextContent object's text.
 	 *
@@ -350,4 +300,13 @@ class WikitextContent extends TextContent {
 	public function setPreSaveTransformFlags( array $flags ) {
 		$this->preSaveTransformFlags = $flags;
 	}
+
+	/**
+	 * Records flags set by preSaveTransform
+	 * @internal for use by WikitextContentHandler
+	 * @return string[]
+	 */
+	public function getPreSaveTransformFlags() {
+		return $this->preSaveTransformFlags;
+	}
 }
diff --git a/includes/content/WikitextContentHandler.php b/includes/content/WikitextContentHandler.php
index 66d94a80b78..8455487752f 100644
--- a/includes/content/WikitextContentHandler.php
+++ b/includes/content/WikitextContentHandler.php
@@ -23,6 +23,7 @@
  * @ingroup Content
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\Content\Transform\PreloadTransformParams;
 use MediaWiki\Content\Transform\PreSaveTransformParams;
 use MediaWiki\Languages\LanguageNameUtils;
@@ -266,4 +267,48 @@ class WikitextContentHandler extends TextContentHandler {
 		$contentClass = $this->getContentClass();
 		return new $contentClass( $plt );
 	}
+
+	/**
+	 * Returns a ParserOutput object resulting from parsing the content's text
+	 * using the global Parser service.
+	 *
+	 * @since 1.38
+	 * @param Content $content
+	 * @param ContentParseParams $cpoParams
+	 * @param ParserOutput &$output The output object to fill (reference).
+	 */
+	protected function fillParserOutput(
+		Content $content,
+		ContentParseParams $cpoParams,
+		ParserOutput &$output
+	) {
+		'@phan-var WikitextContent $content';
+		$services = MediaWikiServices::getInstance();
+		$title = $services->getTitleFactory()->castFromPageReference( $cpoParams->getPage() );
+		$parserOptions = $cpoParams->getParserOptions();
+		$revId = $cpoParams->getRevId();
+
+		list( $redir, $text ) = $content->getRedirectTargetAndText();
+		$output = $services->getParser()
+			->parse( $text, $title, $parserOptions, true, true, $revId );
+
+		// Add redirect indicator at the top
+		if ( $redir ) {
+			// Make sure to include the redirect link in pagelinks
+			$output->addLink( $redir );
+			if ( $cpoParams->getGenerateHtml() ) {
+				$chain = $content->getRedirectChain();
+				$output->setText(
+					Article::getRedirectHeaderHtml( $title->getPageLanguage(), $chain, false ) .
+					$output->getRawText()
+				);
+				$output->addModuleStyles( 'mediawiki.action.view.redirectPage' );
+			}
+		}
+
+		// Pass along user-signature flag
+		if ( in_array( 'user-signature', $content->getPreSaveTransformFlags() ) ) {
+			$output->setFlag( 'user-signature' );
+		}
+	}
 }
diff --git a/includes/language/Message.php b/includes/language/Message.php
index a42edc1b410..80e757e6d1b 100644
--- a/includes/language/Message.php
+++ b/includes/language/Message.php
@@ -870,10 +870,11 @@ class Message implements MessageSpecifier, Serializable {
 
 	/**
 	 * Returns the message as a Content object.
-	 *
+	 * @deprecated since 1.38, MessageContent class is hard-deprecated.
 	 * @return Content
 	 */
 	public function content() {
+		wfDeprecated( __METHOD__, '1.38' );
 		if ( !$this->content ) {
 			$this->content = new MessageContent( $this );
 		}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index cc15da290cb..6c9968130b4 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -3697,8 +3697,8 @@ class Parser {
 					$text = false;
 					break;
 				}
-				$content = $message->content();
 				$text = $message->plain();
+				break;
 			} else {
 				break;
 			}
diff --git a/tests/phpunit/MediaWikiTestCaseTrait.php b/tests/phpunit/MediaWikiTestCaseTrait.php
index 337f38c03f5..4b6bc4e245b 100644
--- a/tests/phpunit/MediaWikiTestCaseTrait.php
+++ b/tests/phpunit/MediaWikiTestCaseTrait.php
@@ -296,7 +296,6 @@ trait MediaWikiTestCaseTrait {
 		$msg->method( 'useDatabase' )->willReturn( $msg );
 		$msg->method( 'setContext' )->willReturn( $msg );
 		$msg->method( 'exists' )->willReturn( true );
-		$msg->method( 'content' )->willReturn( new MessageContent( $msg ) );
 		return $msg;
 	}
 }
diff --git a/tests/phpunit/includes/content/MessageContentTest.php b/tests/phpunit/includes/content/MessageContentTest.php
index 5df7cca4448..ab915f602ad 100644
--- a/tests/phpunit/includes/content/MessageContentTest.php
+++ b/tests/phpunit/includes/content/MessageContentTest.php
@@ -6,15 +6,9 @@
  */
 class MessageContentTest extends MediaWikiLangTestCase {
 
-	public function testGetHtml() {
-		$msg = new Message( 'about' );
-		$cnt = new MessageContent( $msg );
-
-		$this->assertSame( $msg->parse(), $cnt->getHtml() );
-	}
-
 	public function testGetWikitext() {
 		$msg = new Message( 'about' );
+		$this->hideDeprecated( 'MessageContent' );
 		$cnt = new MessageContent( $msg );
 
 		$this->assertSame( $msg->text(), $cnt->getWikitext() );
@@ -22,28 +16,22 @@ class MessageContentTest extends MediaWikiLangTestCase {
 
 	public function testGetMessage() {
 		$msg = new Message( 'about' );
+		$this->hideDeprecated( 'MessageContent' );
 		$cnt = new MessageContent( $msg );
 
 		$this->assertEquals( $msg, $cnt->getMessage() );
 	}
 
-	public function testGetParserOutput() {
-		$msg = new Message( 'about' );
-		$cnt = new MessageContent( $msg );
-
-		$title = Title::makeTitle( NS_MEDIAWIKI, 'about' );
-
-		$this->assertSame( $msg->parse(), $cnt->getParserOutput( $title )->getText() );
-	}
-
 	public function testSerialize() {
 		$msg = new Message( 'about' );
+		$this->hideDeprecated( 'MessageContent' );
 		$cnt = new MessageContent( $msg );
 
 		$this->assertSame( $msg->plain(), $cnt->serialize() );
 	}
 
 	public function testEquals() {
+		$this->hideDeprecated( 'MessageContent' );
 		$msg1 = new Message( 'about' );
 		$cnt1 = new MessageContent( $msg1 );
 
diff --git a/tests/phpunit/integration/includes/parser/ParserObserverIntegrationTest.php b/tests/phpunit/integration/includes/parser/ParserObserverIntegrationTest.php
index ee4dd3ec2e5..72dfcd4b925 100644
--- a/tests/phpunit/integration/includes/parser/ParserObserverIntegrationTest.php
+++ b/tests/phpunit/integration/includes/parser/ParserObserverIntegrationTest.php
@@ -18,12 +18,12 @@ class ParserObserverIntegrationTest extends MediaWikiIntegrationTestCase {
 		$logger = new TestLogger( true );
 		$observer = new ParserObserver( $logger );
 		$this->setService( '_ParserObserver', $observer );
-
+		$contentRenderer = $this->getServiceContainer()->getContentRenderer();
 		// Create a test page. Parse it twice if a duplicate is desired, or once otherwise.
 		$page = $this->getExistingTestPage();
-		$page->getContent()->getParserOutput( $page->getTitle() );
+		$contentRenderer->getParserOutput( $page->getContent(), $page->getTitle() );
 		if ( $duplicate ) {
-			$page->getContent()->getParserOutput( $page->getTitle() );
+			$contentRenderer->getParserOutput( $page->getContent(), $page->getTitle() );
 		}
 
 		$this->assertCount( $count, $logger->getBuffer() );
diff --git a/tests/phpunit/mocks/content/DummyContentForTesting.php b/tests/phpunit/mocks/content/DummyContentForTesting.php
index 27e68726e90..8f24c8e4fb6 100644
--- a/tests/phpunit/mocks/content/DummyContentForTesting.php
+++ b/tests/phpunit/mocks/content/DummyContentForTesting.php
@@ -91,33 +91,4 @@ class DummyContentForTesting extends AbstractContent {
 	public function isCountable( $hasLinks = null ) {
 		return false;
 	}
-
-	/**
-	 * @param Title $title
-	 * @param int|null $revId Unused.
-	 * @param null|ParserOptions $options
-	 * @param bool $generateHtml Whether to generate Html (default: true). If false, the result
-	 *  of calling getText() on the ParserOutput object returned by this method is undefined.
-	 *
-	 * @return ParserOutput
-	 */
-	public function getParserOutput( Title $title, $revId = null,
-		ParserOptions $options = null, $generateHtml = true
-	) {
-		return new ParserOutput( $this->data );
-	}
-
-	/**
-	 * @see AbstractContent::fillParserOutput()
-	 *
-	 * @param Title $title Context title for parsing
-	 * @param int|null $revId Revision ID (for {{REVISIONID}})
-	 * @param ParserOptions $options
-	 * @param bool $generateHtml Whether or not to generate HTML
-	 * @param ParserOutput &$output The output object to fill (reference).
-	 */
-	protected function fillParserOutput( Title $title, $revId,
-			ParserOptions $options, $generateHtml, ParserOutput &$output ) {
-		$output = new ParserOutput( $this->data );
-	}
 }
diff --git a/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
index 11183627adc..a7aa2c01bb8 100644
--- a/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
+++ b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
@@ -1,5 +1,7 @@
 getNativeData() );
+	}
 }
diff --git a/tests/phpunit/mocks/content/DummyNonTextContent.php b/tests/phpunit/mocks/content/DummyNonTextContent.php
index bdfa8d072b9..5e44796bedc 100644
--- a/tests/phpunit/mocks/content/DummyNonTextContent.php
+++ b/tests/phpunit/mocks/content/DummyNonTextContent.php
@@ -89,33 +89,4 @@ class DummyNonTextContent extends AbstractContent {
 	public function isCountable( $hasLinks = null ) {
 		return false;
 	}
-
-	/**
-	 * @param Title $title
-	 * @param int|null $revId Unused.
-	 * @param null|ParserOptions $options
-	 * @param bool $generateHtml Whether to generate Html (default: true). If false, the result
-	 *  of calling getText() on the ParserOutput object returned by this method is undefined.
-	 *
-	 * @return ParserOutput
-	 */
-	public function getParserOutput( Title $title, $revId = null,
-		ParserOptions $options = null, $generateHtml = true
-	) {
-		return new ParserOutput( $this->serialize() );
-	}
-
-	/**
-	 * @see AbstractContent::fillParserOutput()
-	 *
-	 * @param Title $title Context title for parsing
-	 * @param int|null $revId Revision ID (for {{REVISIONID}})
-	 * @param ParserOptions $options
-	 * @param bool $generateHtml Whether or not to generate HTML
-	 * @param ParserOutput &$output The output object to fill (reference).
-	 */
-	protected function fillParserOutput( Title $title, $revId,
-			ParserOptions $options, $generateHtml, ParserOutput &$output ) {
-		$output = new ParserOutput( $this->serialize() );
-	}
 }
diff --git a/tests/phpunit/mocks/content/DummyNonTextContentHandler.php b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php
index 3294953643d..4038803cb84 100644
--- a/tests/phpunit/mocks/content/DummyNonTextContentHandler.php
+++ b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php
@@ -1,5 +1,7 @@
 serialize() );
+	}
 }
diff --git a/tests/phpunit/structure/ContentHandlerSanityTest.php b/tests/phpunit/structure/ContentHandlerSanityTest.php
index 0423e28fd47..73d0b0a3c59 100644
--- a/tests/phpunit/structure/ContentHandlerSanityTest.php
+++ b/tests/phpunit/structure/ContentHandlerSanityTest.php
@@ -18,6 +18,7 @@
  * @file
  */
 
+use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\Content\Transform\PreloadTransformParamsValue;
 use MediaWiki\Content\Transform\PreSaveTransformParamsValue;
 use MediaWiki\MediaWikiServices;
@@ -68,13 +69,20 @@ class ContentHandlerSanityTest extends MediaWikiIntegrationTestCase {
 	 * @dataProvider provideModels
 	 */
 	public function testGetParserOutput( string $model ) {
+		$this->filterDeprecated( '/Use of AbstractContent::getParserOutput was deprecated/' );
+
 		$handler = $this->getServiceContainer()->getContentHandlerFactory()
 			->getContentHandler( $model );
 
 		$title = $this->getExistingTestPage()->getTitle();
 		$content = $handler->makeEmptyContent();
-
 		$this->assertInstanceOf( ParserOutput::class, $content->getParserOutput( $title ) );
+
+		$gpoParams = new ContentParseParams( $title );
+		$this->assertInstanceOf(
+			ParserOutput::class,
+			$handler->getParserOutput( $content, $gpoParams )
+		);
 	}
 
 	/**