Check page_len in ResourceLoaderWikiModule::isKnownEmpty() for 'user' modules
In most cases, we just check whether the pages exist before saying the module is not empty to avoid generating cached HTML without the appropriate <script> or <link> tags. However, for modules in the 'user' group, normal users cannot delete their personal JavaScript/CSS pages, causing needless extra requests, even though we know the pages are empty. ResourceLoader::isKnownEmpty() now checks the page_len field for modules in the 'user' group to check that there is some actual content. Bug: 68488 Change-Id: I0570f62887fd4642fd60367ae0b51d7dc19488ca
This commit is contained in:
parent
08e50bcecd
commit
8968d8787f
4 changed files with 113 additions and 14 deletions
|
|
@ -36,8 +36,8 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
|
||||||
# Origin is user-supplied code
|
# Origin is user-supplied code
|
||||||
protected $origin = self::ORIGIN_USER_SITEWIDE;
|
protected $origin = self::ORIGIN_USER_SITEWIDE;
|
||||||
|
|
||||||
// In-object cache for title mtimes
|
// In-object cache for title info
|
||||||
protected $titleMtimes = array();
|
protected $titleInfo = array();
|
||||||
|
|
||||||
/* Abstract Protected Methods */
|
/* Abstract Protected Methods */
|
||||||
|
|
||||||
|
|
@ -171,8 +171,11 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
|
||||||
*/
|
*/
|
||||||
public function getModifiedTime( ResourceLoaderContext $context ) {
|
public function getModifiedTime( ResourceLoaderContext $context ) {
|
||||||
$modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
|
$modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
|
||||||
$mtimes = $this->getTitleMtimes( $context );
|
$titleInfo = $this->getTitleInfo( $context );
|
||||||
if ( count( $mtimes ) ) {
|
if ( count( $titleInfo ) ) {
|
||||||
|
$mtimes = array_map( function( $value ) {
|
||||||
|
return $value['timestamp'];
|
||||||
|
}, $titleInfo );
|
||||||
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
|
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
|
||||||
}
|
}
|
||||||
$modifiedTime = max(
|
$modifiedTime = max(
|
||||||
|
|
@ -201,16 +204,35 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isKnownEmpty( ResourceLoaderContext $context ) {
|
public function isKnownEmpty( ResourceLoaderContext $context ) {
|
||||||
return count( $this->getTitleMtimes( $context ) ) == 0;
|
$titleInfo = $this->getTitleInfo( $context );
|
||||||
|
// Bug 68488: For modules in the "user" group, we should actually
|
||||||
|
// check that the pages are empty (page_len == 0), but for other
|
||||||
|
// groups, just check the pages exist so that we don't end up
|
||||||
|
// caching temporarily-blank pages without the appropriate
|
||||||
|
// <script> or <link> tag.
|
||||||
|
if ( $this->getGroup() !== 'user' ) {
|
||||||
|
return count( $titleInfo ) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $titleInfo as $info ) {
|
||||||
|
if ( $info['length'] !== 0 ) {
|
||||||
|
// At least one non-0-lenth page, not empty
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All pages are 0-length, so it's empty
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the modification times of all titles that would be loaded for
|
* Get the modification times of all titles that would be loaded for
|
||||||
* a given context.
|
* a given context.
|
||||||
* @param ResourceLoaderContext $context Context object
|
* @param ResourceLoaderContext $context Context object
|
||||||
* @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped
|
* @return array keyed by page dbkey, with value is an array with 'length' and 'timestamp'
|
||||||
|
* keys, where the timestamp is a unix one
|
||||||
*/
|
*/
|
||||||
protected function getTitleMtimes( ResourceLoaderContext $context ) {
|
protected function getTitleInfo( ResourceLoaderContext $context ) {
|
||||||
$dbr = $this->getDB();
|
$dbr = $this->getDB();
|
||||||
if ( !$dbr ) {
|
if ( !$dbr ) {
|
||||||
// We're dealing with a subclass that doesn't have a DB
|
// We're dealing with a subclass that doesn't have a DB
|
||||||
|
|
@ -218,11 +240,11 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
$hash = $context->getHash();
|
$hash = $context->getHash();
|
||||||
if ( isset( $this->titleMtimes[$hash] ) ) {
|
if ( isset( $this->titleInfo[$hash] ) ) {
|
||||||
return $this->titleMtimes[$hash];
|
return $this->titleInfo[$hash];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->titleMtimes[$hash] = array();
|
$this->titleInfo[$hash] = array();
|
||||||
$batch = new LinkBatch;
|
$batch = new LinkBatch;
|
||||||
foreach ( $this->getPages( $context ) as $titleText => $options ) {
|
foreach ( $this->getPages( $context ) as $titleText => $options ) {
|
||||||
$batch->addObj( Title::newFromText( $titleText ) );
|
$batch->addObj( Title::newFromText( $titleText ) );
|
||||||
|
|
@ -230,16 +252,18 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
|
||||||
|
|
||||||
if ( !$batch->isEmpty() ) {
|
if ( !$batch->isEmpty() ) {
|
||||||
$res = $dbr->select( 'page',
|
$res = $dbr->select( 'page',
|
||||||
array( 'page_namespace', 'page_title', 'page_touched' ),
|
array( 'page_namespace', 'page_title', 'page_touched', 'page_len' ),
|
||||||
$batch->constructSet( 'page', $dbr ),
|
$batch->constructSet( 'page', $dbr ),
|
||||||
__METHOD__
|
__METHOD__
|
||||||
);
|
);
|
||||||
foreach ( $res as $row ) {
|
foreach ( $res as $row ) {
|
||||||
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
|
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
|
||||||
$this->titleMtimes[$hash][$title->getPrefixedDBkey()] =
|
$this->titleInfo[$hash][$title->getPrefixedDBkey()] = array(
|
||||||
wfTimestamp( TS_UNIX, $row->page_touched );
|
'timestamp' => wfTimestamp( TS_UNIX, $row->page_touched ),
|
||||||
|
'length' => $row->page_len,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $this->titleMtimes[$hash];
|
return $this->titleInfo[$hash];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ $wgAutoloadClasses += array(
|
||||||
'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||||
'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||||
'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||||
|
'ResourceLoaderWikiModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||||
'TestUser' => "$testDir/phpunit/includes/TestUser.php",
|
'TestUser' => "$testDir/phpunit/includes/TestUser.php",
|
||||||
'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
|
'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,10 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
|
||||||
|
|
||||||
class ResourceLoaderFileModuleTestModule extends ResourceLoaderFileModule {
|
class ResourceLoaderFileModuleTestModule extends ResourceLoaderFileModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ResourceLoaderWikiModuleTestModule extends ResourceLoaderWikiModule {
|
||||||
|
// Override expected via PHPUnit mocks and stubs
|
||||||
|
protected function getPages( ResourceLoaderContext $context ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ResourceLoaderWikiModule::isKnownEmpty
|
||||||
|
* @dataProvider provideIsKnownEmpty
|
||||||
|
*/
|
||||||
|
public function testIsKnownEmpty( $titleInfo, $group, $expected ) {
|
||||||
|
$module = $this->getMockBuilder( 'ResourceLoaderWikiModuleTestModule' )
|
||||||
|
->setMethods( array( 'getTitleInfo', 'getGroup' ) )
|
||||||
|
->getMock();
|
||||||
|
$module->expects( $this->any() )
|
||||||
|
->method( 'getTitleInfo' )
|
||||||
|
->will( $this->returnValue( $titleInfo ) );
|
||||||
|
$module->expects( $this->any() )
|
||||||
|
->method( 'getGroup' )
|
||||||
|
->will( $this->returnValue( $group ) );
|
||||||
|
$context = $this->getMockBuilder( 'ResourceLoaderContext' )
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$this->assertEquals( $expected, $module->isKnownEmpty( $context ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideIsKnownEmpty() {
|
||||||
|
return array(
|
||||||
|
// No valid pages
|
||||||
|
array( array(), 'test1', true ),
|
||||||
|
// 'site' module with a non-empty page
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'MediaWiki:Common.js' => array(
|
||||||
|
'timestamp' => 123456789,
|
||||||
|
'length' => 1234
|
||||||
|
)
|
||||||
|
), 'site', false,
|
||||||
|
),
|
||||||
|
// 'site' module with an empty page
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'MediaWiki:Monobook.js' => array(
|
||||||
|
'timestamp' => 987654321,
|
||||||
|
'length' => 0,
|
||||||
|
),
|
||||||
|
), 'site', false,
|
||||||
|
),
|
||||||
|
// 'user' module with a non-empty page
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'User:FooBar/common.js' => array(
|
||||||
|
'timestamp' => 246813579,
|
||||||
|
'length' => 25,
|
||||||
|
),
|
||||||
|
), 'user', false,
|
||||||
|
),
|
||||||
|
// 'user' module with an empty page
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'User:FooBar/monobook.js' => array(
|
||||||
|
'timestamp' => 1357924680,
|
||||||
|
'length' => 0,
|
||||||
|
),
|
||||||
|
), 'user', true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue