mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-30 17:04:14 +00:00
Hooks: further limit where CodeMirror RL modules are loaded
This fixes a preexisting issue where we were loading CodeMirror on pages where it would never be used. We use the EditPage__showEditForm_initial hook so we don't need to check the action. This commit introduces the CodeMirrorContentModels extension attribute, used to limit where CodeMirror is loaded automatically. By default, this includes only CONTENT_MODEL_WIKITEXT ('wikitext'). This extension attribute serves as a stepping stone to CM being used on content types other than just wikitext. Bug: T359206 Change-Id: Ibefc028c5ef6275393202fe773c26162715e1bca
This commit is contained in:
parent
e578597263
commit
894d2c33e9
|
@ -249,7 +249,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Hooks": {
|
"Hooks": {
|
||||||
"BeforePageDisplay": "main",
|
"EditPage::showEditForm:initial": "main",
|
||||||
"GetPreferences": "main",
|
"GetPreferences": "main",
|
||||||
"ResourceLoaderGetConfigVars": "main"
|
"ResourceLoaderGetConfigVars": "main"
|
||||||
},
|
},
|
||||||
|
@ -264,6 +264,9 @@
|
||||||
},
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"CodeMirror": {
|
"CodeMirror": {
|
||||||
|
"ContentModels": [
|
||||||
|
"wikitext"
|
||||||
|
],
|
||||||
"PluginModules": [
|
"PluginModules": [
|
||||||
"ext.CodeMirror.addons"
|
"ext.CodeMirror.addons"
|
||||||
],
|
],
|
||||||
|
|
|
@ -5,17 +5,20 @@ namespace MediaWiki\Extension\CodeMirror;
|
||||||
use ExtensionRegistry;
|
use ExtensionRegistry;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use MediaWiki\Config\Config;
|
use MediaWiki\Config\Config;
|
||||||
|
use MediaWiki\EditPage\EditPage;
|
||||||
use MediaWiki\Extension\Gadgets\GadgetRepo;
|
use MediaWiki\Extension\Gadgets\GadgetRepo;
|
||||||
use MediaWiki\Hook\BeforePageDisplayHook;
|
use MediaWiki\Hook\EditPage__showEditForm_initialHook;
|
||||||
use MediaWiki\Output\OutputPage;
|
use MediaWiki\Output\OutputPage;
|
||||||
use MediaWiki\Preferences\Hook\GetPreferencesHook;
|
use MediaWiki\Preferences\Hook\GetPreferencesHook;
|
||||||
use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
|
use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
|
||||||
use MediaWiki\User\Options\UserOptionsLookup;
|
use MediaWiki\User\Options\UserOptionsLookup;
|
||||||
use MediaWiki\User\User;
|
use MediaWiki\User\User;
|
||||||
use Skin;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
|
||||||
|
*/
|
||||||
class Hooks implements
|
class Hooks implements
|
||||||
BeforePageDisplayHook,
|
EditPage__showEditForm_initialHook,
|
||||||
ResourceLoaderGetConfigVarsHook,
|
ResourceLoaderGetConfigVarsHook,
|
||||||
GetPreferencesHook
|
GetPreferencesHook
|
||||||
{
|
{
|
||||||
|
@ -45,7 +48,6 @@ class Hooks implements
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function shouldLoadCodeMirror( OutputPage $out, ?ExtensionRegistry $extensionRegistry = null ): bool {
|
public function shouldLoadCodeMirror( OutputPage $out, ?ExtensionRegistry $extensionRegistry = null ): bool {
|
||||||
$extensionRegistry = $extensionRegistry ?: ExtensionRegistry::getInstance();
|
|
||||||
// Disable CodeMirror when CodeEditor is active on this page
|
// Disable CodeMirror when CodeEditor is active on this page
|
||||||
// Depends on ext.codeEditor being added by \MediaWiki\EditPage\EditPage::showEditForm:initial
|
// Depends on ext.codeEditor being added by \MediaWiki\EditPage\EditPage::showEditForm:initial
|
||||||
if ( in_array( 'ext.codeEditor', $out->getModules(), true ) ) {
|
if ( in_array( 'ext.codeEditor', $out->getModules(), true ) ) {
|
||||||
|
@ -55,12 +57,16 @@ class Hooks implements
|
||||||
if ( !$this->userOptionsLookup->getOption( $out->getUser(), 'usebetatoolbar' ) ) {
|
if ( !$this->userOptionsLookup->getOption( $out->getUser(), 'usebetatoolbar' ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$extensionRegistry = $extensionRegistry ?: ExtensionRegistry::getInstance();
|
||||||
|
$contentModels = $extensionRegistry->getAttribute( 'CodeMirrorContentModels' );
|
||||||
$isRTL = $out->getTitle()->getPageLanguage()->isRTL();
|
$isRTL = $out->getTitle()->getPageLanguage()->isRTL();
|
||||||
return in_array( $out->getActionName(), [ 'edit', 'submit' ], true ) &&
|
|
||||||
// Disable CodeMirror if we're on an edit page with a conflicting gadget. See T178348.
|
// Disable CodeMirror if we're on an edit page with a conflicting gadget. See T178348.
|
||||||
!$this->conflictingGadgetsEnabled( $extensionRegistry, $out->getUser() ) &&
|
return !$this->conflictingGadgetsEnabled( $extensionRegistry, $out->getUser() ) &&
|
||||||
// CodeMirror 5 on textarea wikitext editors doesn't support RTL (T170001)
|
// CodeMirror 5 on textarea wikitext editors doesn't support RTL (T170001)
|
||||||
( !$isRTL || $this->shouldUseV6( $out ) );
|
( !$isRTL || $this->shouldUseV6( $out ) ) &&
|
||||||
|
// Limit to supported content models that use wikitext.
|
||||||
|
// See https://www.mediawiki.org/wiki/Content_handlers#Extension_content_handlers
|
||||||
|
in_array( $out->getTitle()->getContentModel(), $contentModels );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,15 +95,14 @@ class Hooks implements
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BeforePageDisplay hook handler
|
* Load CodeMirror if necessary.
|
||||||
*
|
*
|
||||||
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/EditPage::showEditForm:initial
|
||||||
*
|
*
|
||||||
|
* @param EditPage $editor
|
||||||
* @param OutputPage $out
|
* @param OutputPage $out
|
||||||
* @param Skin $skin
|
|
||||||
* @return void This hook must not abort, it must return no value
|
|
||||||
*/
|
*/
|
||||||
public function onBeforePageDisplay( $out, $skin ): void {
|
public function onEditPage__showEditForm_initial( $editor, $out ): void {
|
||||||
if ( !$this->shouldLoadCodeMirror( $out ) ) {
|
if ( !$this->shouldLoadCodeMirror( $out ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -163,5 +168,4 @@ class Hooks implements
|
||||||
'section' => 'editing/accessibility',
|
'section' => 'editing/accessibility',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
namespace MediaWiki\Extension\CodeMirror\Tests;
|
namespace MediaWiki\Extension\CodeMirror\Tests;
|
||||||
|
|
||||||
use ExtensionRegistry;
|
use ExtensionRegistry;
|
||||||
|
use Generator;
|
||||||
|
use Language;
|
||||||
use MediaWiki\Context\RequestContext;
|
use MediaWiki\Context\RequestContext;
|
||||||
|
use MediaWiki\EditPage\EditPage;
|
||||||
use MediaWiki\Extension\CodeMirror\Hooks;
|
use MediaWiki\Extension\CodeMirror\Hooks;
|
||||||
use MediaWiki\Extension\Gadgets\Gadget;
|
use MediaWiki\Extension\Gadgets\Gadget;
|
||||||
use MediaWiki\Extension\Gadgets\GadgetRepo;
|
use MediaWiki\Extension\Gadgets\GadgetRepo;
|
||||||
|
@ -14,7 +17,6 @@ use MediaWiki\User\Options\UserOptionsLookup;
|
||||||
use MediaWiki\User\User;
|
use MediaWiki\User\User;
|
||||||
use MediaWikiIntegrationTestCase;
|
use MediaWikiIntegrationTestCase;
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use Skin;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group CodeMirror
|
* @group CodeMirror
|
||||||
|
@ -25,14 +27,14 @@ class HookTest extends MediaWikiIntegrationTestCase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers ::shouldLoadCodeMirror
|
* @covers ::shouldLoadCodeMirror
|
||||||
* @covers ::onBeforePageDisplay
|
* @covers ::onEditPage__showEditForm_initial
|
||||||
* @param bool $useCodeMirrorV6
|
* @param bool $useCodeMirrorV6
|
||||||
* @param int $expectedAddModuleCalls
|
* @param int $expectedAddModuleCalls
|
||||||
* @param string $expectedFirstModule
|
* @param string|null $expectedFirstModule
|
||||||
* @dataProvider provideOnBeforePageDisplay
|
* @dataProvider provideOnEditPageShowEditFormInitial
|
||||||
*/
|
*/
|
||||||
public function testOnBeforePageDisplay(
|
public function testOnEditPageShowEditFormInitial(
|
||||||
bool $useCodeMirrorV6, int $expectedAddModuleCalls, string $expectedFirstModule
|
bool $useCodeMirrorV6, int $expectedAddModuleCalls, ?string $expectedFirstModule
|
||||||
) {
|
) {
|
||||||
$this->overrideConfigValues( [
|
$this->overrideConfigValues( [
|
||||||
'CodeMirrorV6' => $useCodeMirrorV6,
|
'CodeMirrorV6' => $useCodeMirrorV6,
|
||||||
|
@ -46,24 +48,23 @@ class HookTest extends MediaWikiIntegrationTestCase {
|
||||||
$out->expects( $this->exactly( $expectedAddModuleCalls ) )
|
$out->expects( $this->exactly( $expectedAddModuleCalls ) )
|
||||||
->method( 'addModules' )
|
->method( 'addModules' )
|
||||||
->willReturnCallback( function ( $modules ) use ( $expectedFirstModule, &$isFirstCall ) {
|
->willReturnCallback( function ( $modules ) use ( $expectedFirstModule, &$isFirstCall ) {
|
||||||
if ( $isFirstCall ) {
|
if ( $isFirstCall && $modules !== null ) {
|
||||||
$this->assertSame( $expectedFirstModule, $modules );
|
$this->assertSame( $expectedFirstModule, $modules );
|
||||||
}
|
}
|
||||||
$isFirstCall = false;
|
$isFirstCall = false;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
( new Hooks( $userOptionsLookup, $this->getServiceContainer()->getMainConfig() ) )
|
$hooks = new Hooks( $userOptionsLookup, $this->getServiceContainer()->getMainConfig() );
|
||||||
->onBeforePageDisplay( $out, $this->createMock( Skin::class ) );
|
$hooks->onEditPage__showEditForm_initial( $this->createMock( EditPage::class ), $out );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return Generator
|
||||||
*/
|
*/
|
||||||
public function provideOnBeforePageDisplay(): array {
|
public static function provideOnEditPageShowEditFormInitial(): Generator {
|
||||||
return [
|
// useCodeMirrorV6, expectedAddModuleCalls, expectedFirstModule
|
||||||
[ false, 2, 'ext.CodeMirror.WikiEditor' ],
|
yield 'CM5' => [ false, 2, 'ext.CodeMirror.WikiEditor' ];
|
||||||
[ true, 1, 'ext.CodeMirror.v6.WikiEditor' ]
|
yield 'CM6' => [ true, 1, 'ext.CodeMirror.v6.WikiEditor' ];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,9 +82,25 @@ class HookTest extends MediaWikiIntegrationTestCase {
|
||||||
/**
|
/**
|
||||||
* @covers ::shouldLoadCodeMirror
|
* @covers ::shouldLoadCodeMirror
|
||||||
* @dataProvider provideShouldLoadCodeMirror
|
* @dataProvider provideShouldLoadCodeMirror
|
||||||
|
* @param string|null $module
|
||||||
|
* @param string|null $gadget
|
||||||
|
* @param bool $expectation
|
||||||
|
* @param string $contentModel
|
||||||
|
* @param bool $useCodeMirrorV6
|
||||||
|
* @param bool $isRTL
|
||||||
*/
|
*/
|
||||||
public function testShouldLoadCodeMirror( ?string $module, ?string $gadget, bool $expectation ): void {
|
public function testShouldLoadCodeMirror(
|
||||||
$out = $this->getMockOutputPage();
|
?string $module,
|
||||||
|
?string $gadget,
|
||||||
|
bool $expectation,
|
||||||
|
string $contentModel = CONTENT_MODEL_WIKITEXT,
|
||||||
|
bool $useCodeMirrorV6 = false,
|
||||||
|
bool $isRTL = false
|
||||||
|
): void {
|
||||||
|
$this->overrideConfigValues( [
|
||||||
|
'CodeMirrorV6' => $useCodeMirrorV6,
|
||||||
|
] );
|
||||||
|
$out = $this->getMockOutputPage( $contentModel, $isRTL );
|
||||||
$out->method( 'getModules' )->willReturn( $module ? [ $module ] : [] );
|
$out->method( 'getModules' )->willReturn( $module ? [ $module ] : [] );
|
||||||
$userOptionsLookup = $this->createMock( UserOptionsLookup::class );
|
$userOptionsLookup = $this->createMock( UserOptionsLookup::class );
|
||||||
$userOptionsLookup->method( 'getOption' )->willReturn( true );
|
$userOptionsLookup->method( 'getOption' )->willReturn( true );
|
||||||
|
@ -93,6 +110,9 @@ class HookTest extends MediaWikiIntegrationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
$extensionRegistry = $this->getMockExtensionRegistry( (bool)$gadget );
|
$extensionRegistry = $this->getMockExtensionRegistry( (bool)$gadget );
|
||||||
|
$extensionRegistry->method( 'getAttribute' )
|
||||||
|
->with( 'CodeMirrorContentModels' )
|
||||||
|
->willReturn( [ CONTENT_MODEL_WIKITEXT ] );
|
||||||
|
|
||||||
if ( $gadget ) {
|
if ( $gadget ) {
|
||||||
$gadgetMock = $this->createMock( Gadget::class );
|
$gadgetMock = $this->createMock( Gadget::class );
|
||||||
|
@ -117,24 +137,33 @@ class HookTest extends MediaWikiIntegrationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return Generator
|
||||||
*/
|
*/
|
||||||
public function provideShouldLoadCodeMirror(): array {
|
public function provideShouldLoadCodeMirror(): Generator {
|
||||||
return [
|
// module, gadget, expectation, contentModel, shouldUseV6, isRTL
|
||||||
[ null, null, true ],
|
yield 'no modules, no gadgets, wikitext' => [ null, null, true ];
|
||||||
[ 'ext.codeEditor', null, false ],
|
yield 'codeEditor, no gadgets, wikitext' => [ 'ext.codeEditor', null, false ];
|
||||||
[ null, 'wikEd', false ]
|
yield 'no modules, wikEd, wikitext' => [ null, 'wikEd', false ];
|
||||||
];
|
yield 'no modules, no gadgets, CSS' => [ null, null, false, CONTENT_MODEL_CSS ];
|
||||||
|
yield 'CM5 wikitext RTL' => [ null, null, false, CONTENT_MODEL_WIKITEXT, false, true ];
|
||||||
|
yield 'CM6 wikitext RTL' => [ null, null, true, CONTENT_MODEL_WIKITEXT, true, true ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param string $contentModel
|
||||||
|
* @param bool $isRTL
|
||||||
* @return OutputPage|MockObject
|
* @return OutputPage|MockObject
|
||||||
*/
|
*/
|
||||||
private function getMockOutputPage() {
|
private function getMockOutputPage( string $contentModel = CONTENT_MODEL_WIKITEXT, bool $isRTL = false ) {
|
||||||
$out = $this->createMock( OutputPage::class );
|
$out = $this->createMock( OutputPage::class );
|
||||||
$out->method( 'getUser' )->willReturn( $this->createMock( User::class ) );
|
$out->method( 'getUser' )->willReturn( $this->createMock( User::class ) );
|
||||||
$out->method( 'getActionName' )->willReturn( 'edit' );
|
$out->method( 'getActionName' )->willReturn( 'edit' );
|
||||||
$out->method( 'getTitle' )->willReturn( Title::makeTitle( NS_MAIN, __METHOD__ ) );
|
$title = $this->createMock( Title::class );
|
||||||
|
$title->method( 'getContentModel' )->willReturn( $contentModel );
|
||||||
|
$language = $this->createMock( Language::class );
|
||||||
|
$language->method( 'isRTL' )->willReturn( $isRTL );
|
||||||
|
$title->method( 'getPageLanguage' )->willReturn( $language );
|
||||||
|
$out->method( 'getTitle' )->willReturn( $title );
|
||||||
$request = $this->createMock( WebRequest::class );
|
$request = $this->createMock( WebRequest::class );
|
||||||
$request->method( 'getRawVal' )->willReturn( null );
|
$request->method( 'getRawVal' )->willReturn( null );
|
||||||
$out->method( 'getRequest' )->willReturn( $request );
|
$out->method( 'getRequest' )->willReturn( $request );
|
||||||
|
|
Loading…
Reference in a new issue