2010-09-04 04:00:09 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
2010-09-04 12:53:01 +00:00
|
|
|
*
|
2010-09-05 13:31:34 +00:00
|
|
|
* @file
|
2010-09-04 04:00:09 +00:00
|
|
|
* @author Trevor Parscal
|
|
|
|
|
* @author Roan Kattouw
|
|
|
|
|
*/
|
|
|
|
|
|
2022-05-06 09:09:56 +00:00
|
|
|
namespace MediaWiki\ResourceLoader;
|
|
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
|
use FileContentsHasher;
|
|
|
|
|
use LogicException;
|
2023-09-20 07:54:42 +00:00
|
|
|
use MediaWiki\Config\Config;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
use MediaWiki\HookContainer\HookContainer;
|
2022-04-10 15:34:45 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2016-11-22 23:39:22 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2023-10-03 01:22:48 +00:00
|
|
|
use Peast\Peast;
|
|
|
|
|
use Peast\Syntax\Exception as PeastSyntaxException;
|
2015-11-13 00:04:12 +00:00
|
|
|
use Psr\Log\LoggerAwareInterface;
|
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
use Psr\Log\NullLogger;
|
2022-05-06 09:09:56 +00:00
|
|
|
use RuntimeException;
|
2018-01-21 04:33:38 +00:00
|
|
|
use Wikimedia\RelPath;
|
2022-02-01 01:11:09 +00:00
|
|
|
use Wikimedia\RequestTimeout\TimeoutException;
|
2015-11-13 00:04:12 +00:00
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
2015-10-28 03:24:40 +00:00
|
|
|
* Abstraction for ResourceLoader modules, with name registration and maxage functionality.
|
2019-09-14 04:32:54 +00:00
|
|
|
*
|
2020-11-27 02:47:45 +00:00
|
|
|
* @see $wgResourceModules for the available options when registering a module.
|
2020-07-13 09:00:30 +00:00
|
|
|
* @stable to extend
|
2019-09-14 04:32:54 +00:00
|
|
|
* @ingroup ResourceLoader
|
|
|
|
|
* @since 1.17
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
abstract class Module implements LoggerAwareInterface {
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var Config */
|
|
|
|
|
protected $config;
|
|
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
protected $logger;
|
2011-02-04 16:39:17 +00:00
|
|
|
|
2019-08-01 19:49:38 +00:00
|
|
|
/**
|
|
|
|
|
* Script and style modules form a hierarchy of trustworthiness, with core modules
|
|
|
|
|
* like skins and jQuery as most trustworthy, and user scripts as least trustworthy. We can
|
|
|
|
|
* limit the types of scripts and styles we allow to load on, say, sensitive special
|
|
|
|
|
* pages like Special:UserLogin and Special:Preferences
|
|
|
|
|
* @var int
|
|
|
|
|
*/
|
2011-02-04 16:39:17 +00:00
|
|
|
protected $origin = self::ORIGIN_CORE_SITEWIDE;
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string|null Module name */
|
2010-09-04 04:00:09 +00:00
|
|
|
protected $name = null;
|
2021-12-24 19:32:14 +00:00
|
|
|
/** @var string[]|null Skin names */
|
|
|
|
|
protected $skins = null;
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var array Map of (variant => indirect file dependencies) */
|
2016-02-17 09:09:32 +00:00
|
|
|
protected $fileDeps = [];
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var array Map of (language => in-object cache for message blob) */
|
2016-02-17 09:09:32 +00:00
|
|
|
protected $msgBlobs = [];
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var array Map of (context hash => cached module version hash) */
|
2016-02-17 09:09:32 +00:00
|
|
|
protected $versionHash = [];
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var array Map of (context hash => cached module content) */
|
2016-02-17 09:09:32 +00:00
|
|
|
protected $contents = [];
|
2010-09-04 12:53:01 +00:00
|
|
|
|
2020-06-14 18:40:02 +00:00
|
|
|
/** @var HookRunner|null */
|
|
|
|
|
private $hookRunner;
|
|
|
|
|
|
2019-06-29 04:50:31 +00:00
|
|
|
/** @var callback Function of (module name, variant) to get indirect file dependencies */
|
|
|
|
|
private $depLoadCallback;
|
|
|
|
|
/** @var callback Function of (module name, variant) to get indirect file dependencies */
|
|
|
|
|
private $depSaveCallback;
|
|
|
|
|
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string|bool Deprecation string or true if deprecated; false otherwise */
|
2016-07-14 22:09:06 +00:00
|
|
|
protected $deprecated = false;
|
|
|
|
|
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string Scripts only */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const TYPE_SCRIPTS = 'scripts';
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string Styles only */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const TYPE_STYLES = 'styles';
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string Scripts and styles */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const TYPE_COMBINED = 'combined';
|
2019-08-01 19:49:38 +00:00
|
|
|
|
2022-01-12 14:34:02 +00:00
|
|
|
/** @var string */
|
|
|
|
|
public const GROUP_SITE = 'site';
|
|
|
|
|
/** @var string */
|
|
|
|
|
public const GROUP_USER = 'user';
|
|
|
|
|
/** @var string */
|
|
|
|
|
public const GROUP_PRIVATE = 'private';
|
|
|
|
|
/** @var string */
|
|
|
|
|
public const GROUP_NOSCRIPT = 'noscript';
|
|
|
|
|
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string Module only has styles (loaded via <style> or <link rel=stylesheet>) */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const LOAD_STYLES = 'styles';
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var string Module may have other resources (loaded via mw.loader from a script) */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const LOAD_GENERAL = 'general';
|
2019-08-01 19:49:38 +00:00
|
|
|
|
|
|
|
|
/** @var int Sitewide core module like a skin file or jQuery component */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const ORIGIN_CORE_SITEWIDE = 1;
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var int Per-user module generated by the software */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const ORIGIN_CORE_INDIVIDUAL = 2;
|
2015-11-13 00:04:12 +00:00
|
|
|
/**
|
2019-08-01 19:49:38 +00:00
|
|
|
* Sitewide module generated from user-editable files, like MediaWiki:Common.js,
|
|
|
|
|
* or modules accessible to multiple users, such as those generated by the Gadgets extension.
|
|
|
|
|
* @var int
|
2015-11-13 00:04:12 +00:00
|
|
|
*/
|
2020-05-15 21:36:51 +00:00
|
|
|
public const ORIGIN_USER_SITEWIDE = 3;
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var int Per-user module generated from user-editable files, like User:Me/vector.js */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const ORIGIN_USER_INDIVIDUAL = 4;
|
2019-08-01 19:49:38 +00:00
|
|
|
/** @var int An access constant; make sure this is kept as the largest number in this group */
|
2020-05-15 21:36:51 +00:00
|
|
|
public const ORIGIN_ALL = 10;
|
2015-11-13 00:04:12 +00:00
|
|
|
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
/** @var int Cache version for user-script JS validation errors from validateScriptFile(). */
|
2024-03-07 16:52:06 +00:00
|
|
|
private const USERJSPARSE_CACHE_VERSION = 3;
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
|
|
|
|
* Get this module's name. This is set when the module is registered
|
|
|
|
|
* with ResourceLoader::register()
|
2010-09-05 13:31:34 +00:00
|
|
|
*
|
2014-04-20 21:33:05 +00:00
|
|
|
* @return string|null Name (string) or null if no name was set
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
|
|
|
|
public function getName() {
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
2010-09-04 12:53:01 +00:00
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
2012-11-13 04:18:59 +00:00
|
|
|
* Set this module's name. This is called by ResourceLoader::register()
|
2010-09-04 04:00:09 +00:00
|
|
|
* when registering the module. Other code should not call this.
|
2010-09-05 13:31:34 +00:00
|
|
|
*
|
2017-12-28 15:06:10 +00:00
|
|
|
* @param string $name
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
|
|
|
|
public function setName( $name ) {
|
|
|
|
|
$this->name = $name;
|
|
|
|
|
}
|
2010-09-04 12:53:01 +00:00
|
|
|
|
2021-08-11 18:53:15 +00:00
|
|
|
/**
|
|
|
|
|
* Provide overrides for skinStyles to modules that support that.
|
|
|
|
|
*
|
|
|
|
|
* This MUST be called after self::setName().
|
|
|
|
|
*
|
|
|
|
|
* @since 1.37
|
|
|
|
|
* @see $wgResourceModuleSkinStyles
|
|
|
|
|
* @param array $moduleSkinStyles
|
|
|
|
|
*/
|
|
|
|
|
public function setSkinStylesOverride( array $moduleSkinStyles ): void {
|
|
|
|
|
// Stub, only supported by FileModule currently.
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 04:50:31 +00:00
|
|
|
/**
|
|
|
|
|
* Inject the functions that load/save the indirect file path dependency list from storage
|
|
|
|
|
*
|
|
|
|
|
* @param callable $loadCallback Function of (module name, variant)
|
|
|
|
|
* @param callable $saveCallback Function of (module name, variant, current paths, stored paths)
|
|
|
|
|
* @since 1.35
|
|
|
|
|
*/
|
|
|
|
|
public function setDependencyAccessCallbacks( callable $loadCallback, callable $saveCallback ) {
|
|
|
|
|
$this->depLoadCallback = $loadCallback;
|
|
|
|
|
$this->depSaveCallback = $saveCallback;
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-04 16:39:17 +00:00
|
|
|
/**
|
|
|
|
|
* Get this module's origin. This is set when the module is registered
|
|
|
|
|
* with ResourceLoader::register()
|
|
|
|
|
*
|
2023-03-27 23:21:06 +00:00
|
|
|
* @return int Module class constant, the subclass default if not set manually
|
2011-02-04 16:39:17 +00:00
|
|
|
*/
|
|
|
|
|
public function getOrigin() {
|
|
|
|
|
return $this->origin;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-04 10:53:57 +00:00
|
|
|
/**
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2011-02-18 00:33:45 +00:00
|
|
|
* @return bool
|
2010-09-04 10:53:57 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getFlip( Context $context ) {
|
2018-07-29 12:24:54 +00:00
|
|
|
return MediaWikiServices::getInstance()->getContentLanguage()->getDir() !==
|
|
|
|
|
$context->getDirection();
|
2010-09-04 10:53:57 +00:00
|
|
|
}
|
2010-09-04 12:53:01 +00:00
|
|
|
|
2016-07-14 22:09:06 +00:00
|
|
|
/**
|
|
|
|
|
* Get JS representing deprecation information for the current module if available
|
|
|
|
|
*
|
2023-08-02 03:40:54 +00:00
|
|
|
* @deprecated since 1.41 use getDeprecationWarning()
|
|
|
|
|
*
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2016-07-14 22:09:06 +00:00
|
|
|
* @return string JavaScript code
|
|
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getDeprecationInformation( Context $context ) {
|
2023-08-02 03:40:54 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.41' );
|
|
|
|
|
$warning = $this->getDeprecationWarning();
|
|
|
|
|
if ( $warning === null ) {
|
2016-07-14 22:09:06 +00:00
|
|
|
return '';
|
|
|
|
|
}
|
2023-08-02 03:40:54 +00:00
|
|
|
return 'mw.log.warn(' . $context->encodeJson( $warning ) . ');';
|
|
|
|
|
}
|
2022-06-22 02:41:41 +00:00
|
|
|
|
2023-08-02 03:40:54 +00:00
|
|
|
/**
|
|
|
|
|
* Get the deprecation warning, if any
|
|
|
|
|
*
|
|
|
|
|
* @since 1.41
|
|
|
|
|
* @return string|null
|
|
|
|
|
*/
|
|
|
|
|
public function getDeprecationWarning() {
|
|
|
|
|
if ( !$this->deprecated ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2022-06-22 02:41:41 +00:00
|
|
|
$name = $this->getName();
|
|
|
|
|
$warning = 'This page is using the deprecated ResourceLoader module "' . $name . '".';
|
2023-08-02 03:40:54 +00:00
|
|
|
if ( is_string( $this->deprecated ) ) {
|
|
|
|
|
$warning .= "\n" . $this->deprecated;
|
2022-06-22 02:41:41 +00:00
|
|
|
}
|
2023-08-02 03:40:54 +00:00
|
|
|
return $warning;
|
2016-07-14 22:09:06 +00:00
|
|
|
}
|
|
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
|
|
|
|
* Get all JS for this module for a given language and skin.
|
|
|
|
|
* Includes all relevant JS except loader scripts.
|
2010-09-05 13:31:34 +00:00
|
|
|
*
|
2023-07-24 05:37:04 +00:00
|
|
|
* For multi-file modules where require() is used to load one file from
|
|
|
|
|
* another file, this should return an array structured as follows:
|
ResourceLoader: Add support for packageFiles
Package files are files that are part of a module, but are not
immediately executed when the module executes. Instead, they are
lazy-excecuted when require() is called on them. Package files can be
scripts (JS) or data (JSON), and can be real files on the file system,
or virtual files generated by a callback.
Using virtual data files, server-side data and config variables can be
bundled with a module. Support for file-based require() allows us to
import npm modules into ResourceLoader more easily.
The require function passed to each script execution context, which was
previously a reference to the global mw.loader.require() function, is
changed to one that is scoped to the module and the file being executed.
This is needed to support relative paths: require( '../foo.js' ) can
mean a different file depending on the path of the calling file.
The results of require()ing each file (i.e. the value of module.exports
after executing it) are stored, and calling require() on the same file a
second time won't execute it again, but will return the stored value.
Miscellaneous changes:
- Add XmlJsCode::encodeObject(), which combines an associative array of
XmlJsCode objects into one larger XmlJsCode object. This is needed for
encoding the packageFiles parameter in mw.loader.implement() calls.
Bug: T133462
Change-Id: I78cc86e626de0720397718cd2bed8ed279579112
2018-11-03 00:53:17 +00:00
|
|
|
* [
|
|
|
|
|
* 'files' => [
|
|
|
|
|
* 'file1.js' => [ 'type' => 'script', 'content' => 'JS code' ],
|
|
|
|
|
* 'file2.js' => [ 'type' => 'script', 'content' => 'JS code' ],
|
|
|
|
|
* 'data.json' => [ 'type' => 'data', 'content' => array ]
|
|
|
|
|
* ],
|
|
|
|
|
* 'main' => 'file1.js'
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2023-07-24 05:37:04 +00:00
|
|
|
* For plain concatenated scripts, this can either return a string, or an
|
|
|
|
|
* associative array similar to the one used for package files:
|
|
|
|
|
* [
|
|
|
|
|
* 'plainScripts' => [
|
|
|
|
|
* [ 'content' => 'JS code' ],
|
|
|
|
|
* [ 'content' => 'JS code' ],
|
|
|
|
|
* ],
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2023-07-24 05:37:04 +00:00
|
|
|
* @return string|array JavaScript code (string), or multi-file array with the
|
|
|
|
|
* following keys:
|
|
|
|
|
* - files: An associative array mapping file name to file info structure
|
|
|
|
|
* - main: The name of the main script, a key in the files array
|
|
|
|
|
* - plainScripts: An array of file info structures to be concatenated and
|
|
|
|
|
* executed when the module is loaded.
|
|
|
|
|
* Each file info structure has the following keys:
|
|
|
|
|
* - type: May be "script", "script-vue" or "data". Optional, default "script".
|
|
|
|
|
* - content: The string content of the file
|
|
|
|
|
* - filePath: A FilePath object describing the location of the source file.
|
|
|
|
|
* This will be used to construct the source map during minification.
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getScript( Context $context ) {
|
2010-09-11 03:26:15 +00:00
|
|
|
// Stub, override expected
|
|
|
|
|
return '';
|
|
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2014-10-10 00:07:14 +00:00
|
|
|
/**
|
|
|
|
|
* Takes named templates by the module and returns an array mapping.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2019-05-22 11:37:12 +00:00
|
|
|
* @return string[] Array of templates mapping template alias to content
|
2014-10-10 00:07:14 +00:00
|
|
|
*/
|
|
|
|
|
public function getTemplates() {
|
|
|
|
|
// Stub, override expected.
|
2016-02-17 09:09:32 +00:00
|
|
|
return [];
|
2014-10-10 00:07:14 +00:00
|
|
|
}
|
|
|
|
|
|
2014-08-07 10:25:56 +00:00
|
|
|
/**
|
|
|
|
|
* @return Config
|
|
|
|
|
* @since 1.24
|
|
|
|
|
*/
|
|
|
|
|
public function getConfig() {
|
|
|
|
|
if ( $this->config === null ) {
|
2021-06-11 15:14:53 +00:00
|
|
|
throw new RuntimeException( 'Config accessed before it is set' );
|
2014-08-07 10:25:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param Config $config
|
|
|
|
|
* @since 1.24
|
|
|
|
|
*/
|
|
|
|
|
public function setConfig( Config $config ) {
|
|
|
|
|
$this->config = $config;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-13 00:04:12 +00:00
|
|
|
/**
|
|
|
|
|
* @since 1.27
|
|
|
|
|
* @param LoggerInterface $logger
|
|
|
|
|
*/
|
|
|
|
|
public function setLogger( LoggerInterface $logger ) {
|
|
|
|
|
$this->logger = $logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @since 1.27
|
|
|
|
|
* @return LoggerInterface
|
|
|
|
|
*/
|
|
|
|
|
protected function getLogger() {
|
|
|
|
|
if ( !$this->logger ) {
|
|
|
|
|
$this->logger = new NullLogger();
|
|
|
|
|
}
|
|
|
|
|
return $this->logger;
|
|
|
|
|
}
|
|
|
|
|
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
/**
|
2020-06-14 18:40:02 +00:00
|
|
|
* @internal For use only by ResourceLoader::getModule
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
* @param HookContainer $hookContainer
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function setHookContainer( HookContainer $hookContainer ): void {
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->hookRunner = new HookRunner( $hookContainer );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a HookRunner for running core hooks.
|
|
|
|
|
*
|
2023-03-27 23:21:06 +00:00
|
|
|
* @internal For use only within core Module subclasses. Hook interfaces may be removed
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
* without notice.
|
|
|
|
|
* @return HookRunner
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
protected function getHookRunner(): HookRunner {
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
return $this->hookRunner;
|
|
|
|
|
}
|
|
|
|
|
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
/**
|
resourceloader: Implement debug=2 request splitting
== What ==
Change debug mode 2 to behave more like production mode:
* use module scope (no longer global scope).
* load modules concurrently (no longer each module serially).
* bundle files (no longer each file separately).
What remains different in debug=2 from production mode:
* disable minification.
* disable batching (one module per request).
== How ==
* Limit the old logic (getScriptURLsForDebug) to just legacy debug.
* Set maxQueryLength=0 for non-legacy debug, to ensure each module
still gets its own dedicated request for easy debugging, and to
get concurrency to make more optimal use of server and browser
capacity.
This does not effect package file modules much, as those already
worked in this way. The only difference for package file modules
is that they now load faster (see below) by eliminating the
in-between request.
== Alternative approach ==
An alternative approach, which I considered, is to modify
Module::buildContent(), around where we currently call
getScriptURLsForDebug for DEBUG_LEGACY, and add a conditional branch
for DEBUG_MAIN which would always return an array with a single URL,
to `load.php?modules=:name`. Much like getScriptURLsForDebug does by
default, but without the legacy-specific overrides to that method from
e.g. FileModule.
I decided against this because the mw.loader client handles such
script-arrays in a global FIFO fashion, tailored for legacy debug mode
where it crucial to only serially queue>load>execute one script file
of one module at any given time (because the raw files can't have a
"mw.loader.implement" closure and thus execute immediately on arrival,
with no other coordination for file order and module dependency order).
This would make debug=2 slow, possibly slower than debug=1 since in
debug=1 at least we consolidate most PHP roundtrips in a single batch,
and most other scripts can be served quickly as static file by Apache.
By letting the client act like it does for production mode, and
proactively split its requests, we get a few benefits compared to
this alternative approach:
* Fewer requests and shorter request dependency chain.
There is no in-between request for the "page module batch" that fans
out to individual module reqs. Instead, the client makes those reqs
directly.
* All module requests are discovered and queued with the browser in
one go, letting the server handle them as quickly as it can.
In production, probably all in parallel. Locally, mediawiki-docker
seems to handle about 6 at time (this depite having 10 php-fpm
proccess). I suspect that maybe due to a poor interactions between
HTTP1 connection reuse and keep-alive timeouts, or perhaps unneeded
session locks with sqlite.
* The browser can spend time parsing/compiling other requests at the
same time as one of them executes.
* No additional client-side logic.
* No increase in client payload.
Bug: T85805
Change-Id: I232310eb624e0204484ec9f3d715d5b6b8532fe8
2021-12-06 17:40:46 +00:00
|
|
|
* Get alternative script URLs for legacy debug mode.
|
2011-10-14 08:06:54 +00:00
|
|
|
*
|
resourceloader: Implement debug=2 request splitting
== What ==
Change debug mode 2 to behave more like production mode:
* use module scope (no longer global scope).
* load modules concurrently (no longer each module serially).
* bundle files (no longer each file separately).
What remains different in debug=2 from production mode:
* disable minification.
* disable batching (one module per request).
== How ==
* Limit the old logic (getScriptURLsForDebug) to just legacy debug.
* Set maxQueryLength=0 for non-legacy debug, to ensure each module
still gets its own dedicated request for easy debugging, and to
get concurrency to make more optimal use of server and browser
capacity.
This does not effect package file modules much, as those already
worked in this way. The only difference for package file modules
is that they now load faster (see below) by eliminating the
in-between request.
== Alternative approach ==
An alternative approach, which I considered, is to modify
Module::buildContent(), around where we currently call
getScriptURLsForDebug for DEBUG_LEGACY, and add a conditional branch
for DEBUG_MAIN which would always return an array with a single URL,
to `load.php?modules=:name`. Much like getScriptURLsForDebug does by
default, but without the legacy-specific overrides to that method from
e.g. FileModule.
I decided against this because the mw.loader client handles such
script-arrays in a global FIFO fashion, tailored for legacy debug mode
where it crucial to only serially queue>load>execute one script file
of one module at any given time (because the raw files can't have a
"mw.loader.implement" closure and thus execute immediately on arrival,
with no other coordination for file order and module dependency order).
This would make debug=2 slow, possibly slower than debug=1 since in
debug=1 at least we consolidate most PHP roundtrips in a single batch,
and most other scripts can be served quickly as static file by Apache.
By letting the client act like it does for production mode, and
proactively split its requests, we get a few benefits compared to
this alternative approach:
* Fewer requests and shorter request dependency chain.
There is no in-between request for the "page module batch" that fans
out to individual module reqs. Instead, the client makes those reqs
directly.
* All module requests are discovered and queued with the browser in
one go, letting the server handle them as quickly as it can.
In production, probably all in parallel. Locally, mediawiki-docker
seems to handle about 6 at time (this depite having 10 php-fpm
proccess). I suspect that maybe due to a poor interactions between
HTTP1 connection reuse and keep-alive timeouts, or perhaps unneeded
session locks with sqlite.
* The browser can spend time parsing/compiling other requests at the
same time as one of them executes.
* No additional client-side logic.
* No increase in client payload.
Bug: T85805
Change-Id: I232310eb624e0204484ec9f3d715d5b6b8532fe8
2021-12-06 17:40:46 +00:00
|
|
|
* The default behavior is to return a `load.php?only=scripts&module=<name>` URL.
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
*
|
2022-01-09 17:44:44 +00:00
|
|
|
* Module classes that merely wrap one or more other script files in production mode, may
|
resourceloader: Implement debug=2 request splitting
== What ==
Change debug mode 2 to behave more like production mode:
* use module scope (no longer global scope).
* load modules concurrently (no longer each module serially).
* bundle files (no longer each file separately).
What remains different in debug=2 from production mode:
* disable minification.
* disable batching (one module per request).
== How ==
* Limit the old logic (getScriptURLsForDebug) to just legacy debug.
* Set maxQueryLength=0 for non-legacy debug, to ensure each module
still gets its own dedicated request for easy debugging, and to
get concurrency to make more optimal use of server and browser
capacity.
This does not effect package file modules much, as those already
worked in this way. The only difference for package file modules
is that they now load faster (see below) by eliminating the
in-between request.
== Alternative approach ==
An alternative approach, which I considered, is to modify
Module::buildContent(), around where we currently call
getScriptURLsForDebug for DEBUG_LEGACY, and add a conditional branch
for DEBUG_MAIN which would always return an array with a single URL,
to `load.php?modules=:name`. Much like getScriptURLsForDebug does by
default, but without the legacy-specific overrides to that method from
e.g. FileModule.
I decided against this because the mw.loader client handles such
script-arrays in a global FIFO fashion, tailored for legacy debug mode
where it crucial to only serially queue>load>execute one script file
of one module at any given time (because the raw files can't have a
"mw.loader.implement" closure and thus execute immediately on arrival,
with no other coordination for file order and module dependency order).
This would make debug=2 slow, possibly slower than debug=1 since in
debug=1 at least we consolidate most PHP roundtrips in a single batch,
and most other scripts can be served quickly as static file by Apache.
By letting the client act like it does for production mode, and
proactively split its requests, we get a few benefits compared to
this alternative approach:
* Fewer requests and shorter request dependency chain.
There is no in-between request for the "page module batch" that fans
out to individual module reqs. Instead, the client makes those reqs
directly.
* All module requests are discovered and queued with the browser in
one go, letting the server handle them as quickly as it can.
In production, probably all in parallel. Locally, mediawiki-docker
seems to handle about 6 at time (this depite having 10 php-fpm
proccess). I suspect that maybe due to a poor interactions between
HTTP1 connection reuse and keep-alive timeouts, or perhaps unneeded
session locks with sqlite.
* The browser can spend time parsing/compiling other requests at the
same time as one of them executes.
* No additional client-side logic.
* No increase in client payload.
Bug: T85805
Change-Id: I232310eb624e0204484ec9f3d715d5b6b8532fe8
2021-12-06 17:40:46 +00:00
|
|
|
* override this method to return an array of raw URLs for those underlying scripts,
|
|
|
|
|
* if those are individually web-accessible.
|
|
|
|
|
*
|
|
|
|
|
* The mw.loader client will load and execute each URL consecutively. This has the caveat of
|
|
|
|
|
* executing legacy debug scripts in the global scope, which is why non-package file modules
|
|
|
|
|
* tend to use file closures (T50886).
|
|
|
|
|
*
|
|
|
|
|
* This function MUST NOT be called, unless all the following are true:
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
*
|
|
|
|
|
* 1. We're in debug mode,
|
resourceloader: Implement debug=2 request splitting
== What ==
Change debug mode 2 to behave more like production mode:
* use module scope (no longer global scope).
* load modules concurrently (no longer each module serially).
* bundle files (no longer each file separately).
What remains different in debug=2 from production mode:
* disable minification.
* disable batching (one module per request).
== How ==
* Limit the old logic (getScriptURLsForDebug) to just legacy debug.
* Set maxQueryLength=0 for non-legacy debug, to ensure each module
still gets its own dedicated request for easy debugging, and to
get concurrency to make more optimal use of server and browser
capacity.
This does not effect package file modules much, as those already
worked in this way. The only difference for package file modules
is that they now load faster (see below) by eliminating the
in-between request.
== Alternative approach ==
An alternative approach, which I considered, is to modify
Module::buildContent(), around where we currently call
getScriptURLsForDebug for DEBUG_LEGACY, and add a conditional branch
for DEBUG_MAIN which would always return an array with a single URL,
to `load.php?modules=:name`. Much like getScriptURLsForDebug does by
default, but without the legacy-specific overrides to that method from
e.g. FileModule.
I decided against this because the mw.loader client handles such
script-arrays in a global FIFO fashion, tailored for legacy debug mode
where it crucial to only serially queue>load>execute one script file
of one module at any given time (because the raw files can't have a
"mw.loader.implement" closure and thus execute immediately on arrival,
with no other coordination for file order and module dependency order).
This would make debug=2 slow, possibly slower than debug=1 since in
debug=1 at least we consolidate most PHP roundtrips in a single batch,
and most other scripts can be served quickly as static file by Apache.
By letting the client act like it does for production mode, and
proactively split its requests, we get a few benefits compared to
this alternative approach:
* Fewer requests and shorter request dependency chain.
There is no in-between request for the "page module batch" that fans
out to individual module reqs. Instead, the client makes those reqs
directly.
* All module requests are discovered and queued with the browser in
one go, letting the server handle them as quickly as it can.
In production, probably all in parallel. Locally, mediawiki-docker
seems to handle about 6 at time (this depite having 10 php-fpm
proccess). I suspect that maybe due to a poor interactions between
HTTP1 connection reuse and keep-alive timeouts, or perhaps unneeded
session locks with sqlite.
* The browser can spend time parsing/compiling other requests at the
same time as one of them executes.
* No additional client-side logic.
* No increase in client payload.
Bug: T85805
Change-Id: I232310eb624e0204484ec9f3d715d5b6b8532fe8
2021-12-06 17:40:46 +00:00
|
|
|
* 2. There is no `only=` parameter in the context,
|
|
|
|
|
* 3. self::supportsURLLoading() has returned true.
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
*
|
resourceloader: Implement debug=2 request splitting
== What ==
Change debug mode 2 to behave more like production mode:
* use module scope (no longer global scope).
* load modules concurrently (no longer each module serially).
* bundle files (no longer each file separately).
What remains different in debug=2 from production mode:
* disable minification.
* disable batching (one module per request).
== How ==
* Limit the old logic (getScriptURLsForDebug) to just legacy debug.
* Set maxQueryLength=0 for non-legacy debug, to ensure each module
still gets its own dedicated request for easy debugging, and to
get concurrency to make more optimal use of server and browser
capacity.
This does not effect package file modules much, as those already
worked in this way. The only difference for package file modules
is that they now load faster (see below) by eliminating the
in-between request.
== Alternative approach ==
An alternative approach, which I considered, is to modify
Module::buildContent(), around where we currently call
getScriptURLsForDebug for DEBUG_LEGACY, and add a conditional branch
for DEBUG_MAIN which would always return an array with a single URL,
to `load.php?modules=:name`. Much like getScriptURLsForDebug does by
default, but without the legacy-specific overrides to that method from
e.g. FileModule.
I decided against this because the mw.loader client handles such
script-arrays in a global FIFO fashion, tailored for legacy debug mode
where it crucial to only serially queue>load>execute one script file
of one module at any given time (because the raw files can't have a
"mw.loader.implement" closure and thus execute immediately on arrival,
with no other coordination for file order and module dependency order).
This would make debug=2 slow, possibly slower than debug=1 since in
debug=1 at least we consolidate most PHP roundtrips in a single batch,
and most other scripts can be served quickly as static file by Apache.
By letting the client act like it does for production mode, and
proactively split its requests, we get a few benefits compared to
this alternative approach:
* Fewer requests and shorter request dependency chain.
There is no in-between request for the "page module batch" that fans
out to individual module reqs. Instead, the client makes those reqs
directly.
* All module requests are discovered and queued with the browser in
one go, letting the server handle them as quickly as it can.
In production, probably all in parallel. Locally, mediawiki-docker
seems to handle about 6 at time (this depite having 10 php-fpm
proccess). I suspect that maybe due to a poor interactions between
HTTP1 connection reuse and keep-alive timeouts, or perhaps unneeded
session locks with sqlite.
* The browser can spend time parsing/compiling other requests at the
same time as one of them executes.
* No additional client-side logic.
* No increase in client payload.
Bug: T85805
Change-Id: I232310eb624e0204484ec9f3d715d5b6b8532fe8
2021-12-06 17:40:46 +00:00
|
|
|
* Point 2 prevents an infinite loop since we use the `only=` mechanism in the return value.
|
|
|
|
|
* Overrides must similarly return with `only`, or return or a non-load.php URL.
|
2011-10-14 08:06:54 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2020-10-28 10:01:33 +00:00
|
|
|
* @return string[]
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getScriptURLsForDebug( Context $context ) {
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
$rl = $context->getResourceLoader();
|
2022-05-06 09:09:56 +00:00
|
|
|
$derivative = new DerivativeContext( $context );
|
2016-02-17 09:09:32 +00:00
|
|
|
$derivative->setModules( [ $this->getName() ] );
|
2014-06-28 02:57:40 +00:00
|
|
|
$derivative->setOnly( 'scripts' );
|
|
|
|
|
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
$url = $rl->createLoaderURL(
|
2014-06-28 02:57:40 +00:00
|
|
|
$this->getSource(),
|
|
|
|
|
$derivative
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
);
|
2014-06-28 02:57:40 +00:00
|
|
|
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
// Expand debug URL in case we are another wiki's module source (T255367)
|
2022-04-10 15:34:45 +00:00
|
|
|
$url = $rl->expandUrl( $this->getConfig()->get( MainConfigNames::Server ), $url );
|
resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.
For example:
1. Page view on en.wikipedia.org.
2. Script call to
meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user
This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
when building the article HTML. It knows the modules is on Meta, and
formats it as such.
So far so good.
3. meta.wikimedia.org responds with an array of urls for sub resources.
That array contained URLs like "/w/load.php...only=scripts".
These were formatted by getScriptURLsForDebug running on Meta,
no longer with a reason to make it a Meta-Wiki URL as it isn't
perceived as cross-wiki. It is indistinguishable from debugging
a Meta-Wiki page view from its perspective.
This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.
The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.
Test Plan:
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
For Module base class.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
For FileModule.
Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
Unchanged.
Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 02:36:25 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return [ $url ];
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
/**
|
|
|
|
|
* Whether this module supports URL loading. If this function returns false,
|
|
|
|
|
* getScript() will be used even in cases (debug mode, no only param) where
|
|
|
|
|
* getScriptURLsForDebug() would normally be used instead.
|
2020-07-03 12:31:37 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function supportsURLLoading() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2010-09-04 12:53:01 +00:00
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
|
|
|
|
* Get all CSS for this module for a given skin.
|
2010-09-05 13:31:34 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2014-04-20 21:33:05 +00:00
|
|
|
* @return array List of CSS strings or array of CSS strings keyed by media type.
|
2016-08-26 11:36:58 +00:00
|
|
|
* like [ 'screen' => '.foo { width: 0 }' ];
|
|
|
|
|
* or [ 'screen' => [ '.foo { width: 0 }' ] ];
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getStyles( Context $context ) {
|
2010-09-11 03:26:15 +00:00
|
|
|
// Stub, override expected
|
2016-02-17 09:09:32 +00:00
|
|
|
return [];
|
2010-09-11 03:26:15 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
/**
|
|
|
|
|
* Get the URL or URLs to load for this module's CSS in debug mode.
|
|
|
|
|
* The default behavior is to return a load.php?only=styles URL for
|
|
|
|
|
* the module, but file-based modules will want to override this to
|
2021-09-24 22:52:05 +00:00
|
|
|
* load the files directly
|
|
|
|
|
*
|
|
|
|
|
* This function must only be called when:
|
|
|
|
|
*
|
|
|
|
|
* 1. We're in debug mode,
|
|
|
|
|
* 2. There is no `only=` parameter and,
|
|
|
|
|
* 3. self::supportsURLLoading() returns true.
|
|
|
|
|
*
|
|
|
|
|
* See also getScriptURLsForDebug().
|
2011-10-14 08:06:54 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2016-08-26 11:36:58 +00:00
|
|
|
* @return array [ mediaType => [ URL1, URL2, ... ], ... ]
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getStyleURLsForDebug( Context $context ) {
|
2014-06-28 02:57:40 +00:00
|
|
|
$resourceLoader = $context->getResourceLoader();
|
2022-05-06 09:09:56 +00:00
|
|
|
$derivative = new DerivativeContext( $context );
|
2016-02-17 09:09:32 +00:00
|
|
|
$derivative->setModules( [ $this->getName() ] );
|
2014-06-28 02:57:40 +00:00
|
|
|
$derivative->setOnly( 'styles' );
|
|
|
|
|
|
|
|
|
|
$url = $resourceLoader->createLoaderURL(
|
|
|
|
|
$this->getSource(),
|
|
|
|
|
$derivative
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
);
|
2014-06-28 02:57:40 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return [ 'all' => [ $url ] ];
|
Fix the fixme on r88053: dependency handling was broken in debug mode in certain cases. More specifically, if A is a file module that depends on B, B is a wiki module that depends on C and C is a file module, the loading order is CBA (correct) in production mode but was BCA (wrong) in debug mode. Fixed this by URL-ifying scripts and styles for those modules in debug mode, as I said to on CR. What this means is that the initial debug=true request for a module will now always return arrays of URLs, never the JS or CSS itself. This was already the case for file modules (which returned arrays of URLs to the raw files), but not for other modules (which returned the JS and CSS itself). So for non-file modules, load.php?modules=foo&debug=true now returns some JS that instructs the loader to fetch the module's JS from load.php?modules=foo&debug=true&only=scripts and the CSS from ...&only=styles .
* Removed the magic behavior where ResourceLoaderModule::getScripts() and getStyles() could return an array of URLs where the documentation said they should return a JS/CSS string. Because I didn't restructure the calling code too much, the old magical behavior should still work.
* Instead, move this behavior to getScriptURLsForDebug() and getStyleURLsForDebug(). The default implementation constructs a single URL for a load.php request for the module with debug=true&only=scripts (or styles). The URL building code duplicates some things from OutputPage::makeResourceLoaderLink(), I'll clean that up later. ResourceLoaderFileModule overrides this method to return URLs to the raw files, using code that I removed from getScripts()/getStyles()
* Add ResourceLoaderModule::supportsURLLoading(), which returns true by default but may return false to indicate that a module does not support loading via a URL. This is needed to respect $this->debugRaw in ResourceLoaderFileModule (set to true for jquery and mediawiki), and obviously for the startup module as well, because we get bootstrapping problems otherwise (can't call mw.loader.implement() when the code for mw.loader isn't loaded yet)
2011-09-13 17:13:53 +00:00
|
|
|
}
|
2010-09-04 12:53:01 +00:00
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
|
|
|
|
* Get the messages needed for this module.
|
|
|
|
|
*
|
|
|
|
|
* To get a JSON blob with messages, use MessageBlobStore::get()
|
2010-09-05 13:31:34 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2020-10-28 10:01:33 +00:00
|
|
|
* @return string[] List of message keys. Keys may occur more than once
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
2010-09-11 03:26:15 +00:00
|
|
|
public function getMessages() {
|
|
|
|
|
// Stub, override expected
|
2016-02-17 09:09:32 +00:00
|
|
|
return [];
|
2010-09-11 03:26:15 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2010-09-20 21:23:29 +00:00
|
|
|
/**
|
2024-07-18 13:42:58 +00:00
|
|
|
* Specifies the group this module is in.
|
|
|
|
|
*
|
|
|
|
|
* Return one of the Module::GROUP_ constants for reserved group names with special behavior,
|
|
|
|
|
* or a freeform string.
|
|
|
|
|
* Refer to https://www.mediawiki.org/wiki/ResourceLoader/Architecture#Groups for documentation.
|
2011-10-14 08:06:54 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2021-11-02 14:29:42 +00:00
|
|
|
* @return string|null Group name
|
2010-09-20 21:23:29 +00:00
|
|
|
*/
|
|
|
|
|
public function getGroup() {
|
|
|
|
|
// Stub, override expected
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2011-07-26 21:10:34 +00:00
|
|
|
|
|
|
|
|
/**
|
2018-03-28 00:57:06 +00:00
|
|
|
* Get the source of this module. Should only be overridden for foreign modules.
|
2011-10-14 08:06:54 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2018-03-28 00:57:06 +00:00
|
|
|
* @return string Source name, 'local' for local modules
|
2011-07-26 21:10:34 +00:00
|
|
|
*/
|
|
|
|
|
public function getSource() {
|
|
|
|
|
// Stub, override expected
|
|
|
|
|
return 'local';
|
|
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
|
|
|
|
* Get a list of modules this module depends on.
|
|
|
|
|
*
|
|
|
|
|
* Dependency information is taken into account when loading a module
|
2013-08-22 17:31:09 +00:00
|
|
|
* on the client side.
|
2010-09-04 04:00:09 +00:00
|
|
|
*
|
2015-04-08 21:34:08 +00:00
|
|
|
* Note: It is expected that $context will be made non-optional in the near
|
|
|
|
|
* future.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context|null $context
|
2020-10-28 10:01:33 +00:00
|
|
|
* @return string[] List of module names as strings
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
2024-10-16 18:58:33 +00:00
|
|
|
public function getDependencies( ?Context $context = null ) {
|
2010-09-11 03:26:15 +00:00
|
|
|
// Stub, override expected
|
2016-02-17 09:09:32 +00:00
|
|
|
return [];
|
2010-09-11 03:26:15 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2021-12-24 19:32:14 +00:00
|
|
|
/**
|
|
|
|
|
* Get list of skins for which this module must be available to load.
|
|
|
|
|
*
|
|
|
|
|
* By default, modules are available to all skins.
|
|
|
|
|
*
|
|
|
|
|
* This information may be used by the startup module to optimise registrations
|
|
|
|
|
* based on the current skin.
|
|
|
|
|
*
|
|
|
|
|
* @stable to override
|
|
|
|
|
* @since 1.39
|
|
|
|
|
* @return string[]|null
|
|
|
|
|
*/
|
|
|
|
|
public function getSkins(): ?array {
|
|
|
|
|
return $this->skins;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-10 19:00:44 +00:00
|
|
|
/**
|
|
|
|
|
* Get the module's load type.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2016-05-10 19:00:44 +00:00
|
|
|
* @since 1.28
|
2022-05-06 09:09:56 +00:00
|
|
|
* @return string Module LOAD_* constant
|
2016-05-10 19:00:44 +00:00
|
|
|
*/
|
|
|
|
|
public function getType() {
|
|
|
|
|
return self::LOAD_GENERAL;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-30 21:06:51 +00:00
|
|
|
/**
|
|
|
|
|
* Get the skip function.
|
|
|
|
|
*
|
|
|
|
|
* Modules that provide fallback functionality can provide a "skip function". This
|
|
|
|
|
* function, if provided, will be passed along to the module registry on the client.
|
|
|
|
|
* When this module is loaded (either directly or as a dependency of another module),
|
|
|
|
|
* then this function is executed first. If the function returns true, the module will
|
|
|
|
|
* instantly be considered "ready" without requesting the associated module resources.
|
|
|
|
|
*
|
|
|
|
|
* The value returned here must be valid javascript for execution in a private function.
|
|
|
|
|
* It must not contain the "function () {" and "}" wrapper though.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2014-04-30 21:06:51 +00:00
|
|
|
* @return string|null A JavaScript function body returning a boolean value, or null
|
|
|
|
|
*/
|
|
|
|
|
public function getSkipFunction() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-23 07:33:38 +00:00
|
|
|
/**
|
|
|
|
|
* Whether the module requires ES6 support in the client.
|
|
|
|
|
*
|
|
|
|
|
* If the client does not support ES6, attempting to load a module that requires ES6 will
|
|
|
|
|
* result in an error.
|
|
|
|
|
*
|
ResourceLoader: Raise MW JavaScript startup requirement to ES6
The UA sniffs that overrode the feature tests are no longer needed.
* MSIE 10: Fine, rejected by feature checks.
* UC Mini "Speed Mode": Redundant, the version that this sniff
matched is pre-ES6. Current versions of UC Mini don't appear to
support enabling "Speed Mode" on random websites nor does it offer
it for Wikipedia specifically.
Details at https://phabricator.wikimedia.org/T178356#8740573.
* Google Web Light: Redundant, shutdown as of 2022.
Any references or extensions that still reach the proxy, get
redirected to our online URLs
https://googleweblight.com/?lite_url=https://en.m.wikipedia.org/wiki/Banana
https://phabricator.wikimedia.org/T152602
https://en.wikipedia.org/wiki/Google_Web_Light
* MeeGo: Redundant, discontinued and presumed rejected.
Either way, unsupported.
* Opera Mini: Fine, rejected by checks.
Details at https://phabricator.wikimedia.org/T178356#8740573.
* Ovi Browser: Redundant, discontinued and presumed rejected.
Either way, unsupported.
* Google Glass: Improve UX (since 2013, T58008).
* NetFront: Redundant. Old versions are presumed rejected.
Current versions are Chromium-based and presumed fine.
The exclusion was not UX based, but due to jQuery explicitly not
supporting it in 2013. This is no longer the case, so we can let
the feature test lead the way here.
* PlayStation: Redundant, same story as NetFront.
The version that matched the sniff is presumed rejected.
Current versions probably fine, but even not, don't match
our sniff so are already enabled today.
Bug: T178356
Change-Id: Ib6263ce3ffd11af5e501de8857f3e48a248c6210
2023-03-24 12:56:01 +00:00
|
|
|
* @deprecated since 1.41, ignored by ResourceLoader
|
2021-01-23 07:33:38 +00:00
|
|
|
* @since 1.36
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function requiresES6() {
|
ResourceLoader: Raise MW JavaScript startup requirement to ES6
The UA sniffs that overrode the feature tests are no longer needed.
* MSIE 10: Fine, rejected by feature checks.
* UC Mini "Speed Mode": Redundant, the version that this sniff
matched is pre-ES6. Current versions of UC Mini don't appear to
support enabling "Speed Mode" on random websites nor does it offer
it for Wikipedia specifically.
Details at https://phabricator.wikimedia.org/T178356#8740573.
* Google Web Light: Redundant, shutdown as of 2022.
Any references or extensions that still reach the proxy, get
redirected to our online URLs
https://googleweblight.com/?lite_url=https://en.m.wikipedia.org/wiki/Banana
https://phabricator.wikimedia.org/T152602
https://en.wikipedia.org/wiki/Google_Web_Light
* MeeGo: Redundant, discontinued and presumed rejected.
Either way, unsupported.
* Opera Mini: Fine, rejected by checks.
Details at https://phabricator.wikimedia.org/T178356#8740573.
* Ovi Browser: Redundant, discontinued and presumed rejected.
Either way, unsupported.
* Google Glass: Improve UX (since 2013, T58008).
* NetFront: Redundant. Old versions are presumed rejected.
Current versions are Chromium-based and presumed fine.
The exclusion was not UX based, but due to jQuery explicitly not
supporting it in 2013. This is no longer the case, so we can let
the feature test lead the way here.
* PlayStation: Redundant, same story as NetFront.
The version that matched the sniff is presumed rejected.
Current versions probably fine, but even not, don't match
our sniff so are already enabled today.
Bug: T178356
Change-Id: Ib6263ce3ffd11af5e501de8857f3e48a248c6210
2023-03-24 12:56:01 +00:00
|
|
|
return true;
|
2021-01-23 07:33:38 +00:00
|
|
|
}
|
|
|
|
|
|
2010-09-23 21:23:51 +00:00
|
|
|
/**
|
2022-01-09 17:44:44 +00:00
|
|
|
* Get the indirect dependencies for this module pursuant to the skin/language context
|
2019-06-29 04:50:31 +00:00
|
|
|
*
|
|
|
|
|
* These are only image files referenced by the module's stylesheet
|
2015-09-17 22:14:46 +00:00
|
|
|
*
|
2019-12-13 09:58:24 +00:00
|
|
|
* If neither setFileDependencies() nor setDependencyAccessCallbacks() was called,
|
|
|
|
|
* this will simply return a placeholder with an empty file list
|
2010-09-23 21:23:51 +00:00
|
|
|
*
|
2019-12-13 09:58:24 +00:00
|
|
|
* @see Module::setFileDependencies()
|
|
|
|
|
* @see Module::saveFileDependencies()
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2019-06-29 04:50:31 +00:00
|
|
|
* @return string[] List of absolute file paths
|
2010-09-23 21:23:51 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
protected function getFileDependencies( Context $context ) {
|
2019-06-29 04:50:31 +00:00
|
|
|
$variant = self::getVary( $context );
|
2015-06-18 20:52:17 +00:00
|
|
|
|
2019-06-29 04:50:31 +00:00
|
|
|
if ( !isset( $this->fileDeps[$variant] ) ) {
|
|
|
|
|
if ( $this->depLoadCallback ) {
|
|
|
|
|
$this->fileDeps[$variant] =
|
|
|
|
|
call_user_func( $this->depLoadCallback, $this->getName(), $variant );
|
2015-09-30 00:15:57 +00:00
|
|
|
} else {
|
2019-06-29 04:50:31 +00:00
|
|
|
$this->getLogger()->info( __METHOD__ . ": no callback registered" );
|
|
|
|
|
$this->fileDeps[$variant] = [];
|
2015-09-30 00:15:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-06-29 04:50:31 +00:00
|
|
|
|
|
|
|
|
return $this->fileDeps[$variant];
|
2010-09-23 21:23:51 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2010-09-23 21:23:51 +00:00
|
|
|
/**
|
2022-01-09 17:44:44 +00:00
|
|
|
* Set the indirect dependencies for this module pursuant to the skin/language context
|
2015-09-17 22:14:46 +00:00
|
|
|
*
|
2019-06-29 04:50:31 +00:00
|
|
|
* These are only image files referenced by the module's stylesheet
|
|
|
|
|
*
|
2019-12-13 09:58:24 +00:00
|
|
|
* @see Module::getFileDependencies()
|
|
|
|
|
* @see Module::saveFileDependencies()
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2019-06-29 04:50:31 +00:00
|
|
|
* @param string[] $paths List of absolute file paths
|
2010-09-23 21:23:51 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function setFileDependencies( Context $context, array $paths ) {
|
2019-06-29 04:50:31 +00:00
|
|
|
$variant = self::getVary( $context );
|
|
|
|
|
$this->fileDeps[$variant] = $paths;
|
2010-09-23 21:23:51 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2015-09-17 22:14:46 +00:00
|
|
|
/**
|
2022-01-09 17:44:44 +00:00
|
|
|
* Save the indirect dependencies for this module pursuant to the skin/language context
|
2015-09-17 22:14:46 +00:00
|
|
|
*
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2019-06-29 04:50:31 +00:00
|
|
|
* @param string[] $curFileRefs List of newly computed indirect file dependencies
|
|
|
|
|
* @since 1.27
|
2015-09-17 22:14:46 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
protected function saveFileDependencies( Context $context, array $curFileRefs ) {
|
2019-06-29 04:50:31 +00:00
|
|
|
if ( !$this->depSaveCallback ) {
|
|
|
|
|
$this->getLogger()->info( __METHOD__ . ": no callback registered" );
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-17 22:14:46 +00:00
|
|
|
try {
|
2019-06-29 04:50:31 +00:00
|
|
|
// Pitfalls and performance considerations:
|
|
|
|
|
// 1. Don't keep updating the tracked paths due to duplicates or sorting.
|
2017-02-22 21:54:40 +00:00
|
|
|
// 2. Use relative paths to avoid ghost entries when $IP changes. (T111481)
|
2019-06-29 04:50:31 +00:00
|
|
|
// 3. Don't needlessly replace tracked paths with the same value
|
2017-02-22 21:54:40 +00:00
|
|
|
// just because $IP changed (e.g. when upgrading a wiki).
|
|
|
|
|
// 4. Don't create an endless replace loop on every request for this
|
|
|
|
|
// module when '../' is used anywhere. Even though both are expanded
|
|
|
|
|
// (one expanded by getFileDependencies from the DB, the other is
|
|
|
|
|
// still raw as originally read by RL), the latter has not
|
|
|
|
|
// been normalized yet.
|
2019-06-29 04:50:31 +00:00
|
|
|
call_user_func(
|
|
|
|
|
$this->depSaveCallback,
|
|
|
|
|
$this->getName(),
|
|
|
|
|
self::getVary( $context ),
|
|
|
|
|
self::getRelativePaths( $curFileRefs ),
|
|
|
|
|
self::getRelativePaths( $this->getFileDependencies( $context ) )
|
2019-03-06 01:35:05 +00:00
|
|
|
);
|
2022-02-01 01:11:09 +00:00
|
|
|
} catch ( TimeoutException $e ) {
|
|
|
|
|
throw $e;
|
2015-09-17 22:14:46 +00:00
|
|
|
} catch ( Exception $e ) {
|
2019-06-29 04:50:31 +00:00
|
|
|
$this->getLogger()->warning(
|
|
|
|
|
__METHOD__ . ": failed to update dependencies: {$e->getMessage()}",
|
|
|
|
|
[ 'exception' => $e ]
|
|
|
|
|
);
|
2015-09-17 22:14:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-23 01:35:38 +00:00
|
|
|
/**
|
|
|
|
|
* Make file paths relative to MediaWiki directory.
|
|
|
|
|
*
|
|
|
|
|
* This is used to make file paths safe for storing in a database without the paths
|
|
|
|
|
* becoming stale or incorrect when MediaWiki is moved or upgraded (T111481).
|
|
|
|
|
*
|
2015-10-07 04:55:42 +00:00
|
|
|
* @since 1.27
|
2015-09-23 01:35:38 +00:00
|
|
|
* @param array $filePaths
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2016-02-17 10:31:52 +00:00
|
|
|
public static function getRelativePaths( array $filePaths ) {
|
2015-09-23 01:35:38 +00:00
|
|
|
global $IP;
|
2021-02-10 22:31:02 +00:00
|
|
|
return array_map( static function ( $path ) use ( $IP ) {
|
2018-01-21 04:33:38 +00:00
|
|
|
return RelPath::getRelativePath( $path, $IP );
|
2015-09-23 01:35:38 +00:00
|
|
|
}, $filePaths );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Expand directories relative to $IP.
|
|
|
|
|
*
|
2015-10-07 04:55:42 +00:00
|
|
|
* @since 1.27
|
2015-09-23 01:35:38 +00:00
|
|
|
* @param array $filePaths
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2016-02-17 10:31:52 +00:00
|
|
|
public static function expandRelativePaths( array $filePaths ) {
|
2015-09-23 01:35:38 +00:00
|
|
|
global $IP;
|
2021-02-10 22:31:02 +00:00
|
|
|
return array_map( static function ( $path ) use ( $IP ) {
|
2018-01-21 04:33:38 +00:00
|
|
|
return RelPath::joinPath( $IP, $path );
|
2015-09-23 01:35:38 +00:00
|
|
|
}, $filePaths );
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-23 21:23:51 +00:00
|
|
|
/**
|
2015-11-13 00:04:12 +00:00
|
|
|
* Get the hash of the message blob.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2015-11-13 00:04:12 +00:00
|
|
|
* @since 1.27
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2015-11-13 00:04:12 +00:00
|
|
|
* @return string|null JSON blob or null if module has no messages
|
2023-07-24 06:33:01 +00:00
|
|
|
* @return-taint none -- do not propagate taint from $context->getLanguage()
|
2010-09-23 21:23:51 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
protected function getMessageBlob( Context $context ) {
|
2015-11-13 00:04:12 +00:00
|
|
|
if ( !$this->getMessages() ) {
|
|
|
|
|
// Don't bother consulting MessageBlobStore
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
// Message blobs may only vary language, not by context keys
|
|
|
|
|
$lang = $context->getLanguage();
|
|
|
|
|
if ( !isset( $this->msgBlobs[$lang] ) ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->getLogger()->warning( 'Message blob for {module} should have been preloaded', [
|
2015-11-13 00:04:12 +00:00
|
|
|
'module' => $this->getName(),
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2015-11-13 00:04:12 +00:00
|
|
|
$store = $context->getResourceLoader()->getMessageBlobStore();
|
|
|
|
|
$this->msgBlobs[$lang] = $store->getBlob( $this, $lang );
|
2010-11-19 06:52:38 +00:00
|
|
|
}
|
2015-11-13 00:04:12 +00:00
|
|
|
return $this->msgBlobs[$lang];
|
2010-09-23 21:23:51 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
2010-09-23 21:23:51 +00:00
|
|
|
/**
|
2015-11-13 00:04:12 +00:00
|
|
|
* Set in-object cache for message blobs.
|
2015-09-17 22:14:46 +00:00
|
|
|
*
|
2015-11-13 00:04:12 +00:00
|
|
|
* Used to allow fetching of message blobs in batches. See ResourceLoader::preloadModuleInfo().
|
2015-09-17 22:14:46 +00:00
|
|
|
*
|
2015-11-13 00:04:12 +00:00
|
|
|
* @since 1.27
|
|
|
|
|
* @param string|null $blob JSON blob or null
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $lang Language code
|
2010-09-23 21:23:51 +00:00
|
|
|
*/
|
2015-11-13 00:04:12 +00:00
|
|
|
public function setMessageBlob( $blob, $lang ) {
|
|
|
|
|
$this->msgBlobs[$lang] = $blob;
|
2010-09-23 21:23:51 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
/**
|
|
|
|
|
* Get headers to send as part of a module web response.
|
|
|
|
|
*
|
|
|
|
|
* It is not supported to send headers through this method that are
|
|
|
|
|
* required to be unique or otherwise sent once in an HTTP response
|
|
|
|
|
* because clients may make batch requests for multiple modules (as
|
|
|
|
|
* is the default behaviour for ResourceLoader clients).
|
|
|
|
|
*
|
|
|
|
|
* For exclusive or aggregated headers, see ResourceLoader::sendResponseHeaders().
|
|
|
|
|
*
|
|
|
|
|
* @since 1.30
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
* @return string[] Array of HTTP response headers
|
|
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
final public function getHeaders( Context $context ) {
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
$formattedLinks = [];
|
|
|
|
|
foreach ( $this->getPreloadLinks( $context ) as $url => $attribs ) {
|
|
|
|
|
$link = "<{$url}>;rel=preload";
|
|
|
|
|
foreach ( $attribs as $key => $val ) {
|
|
|
|
|
$link .= ";{$key}={$val}";
|
|
|
|
|
}
|
|
|
|
|
$formattedLinks[] = $link;
|
|
|
|
|
}
|
|
|
|
|
if ( $formattedLinks ) {
|
2022-06-22 02:41:41 +00:00
|
|
|
return [ 'Link: ' . implode( ',', $formattedLinks ) ];
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
}
|
2022-06-22 02:41:41 +00:00
|
|
|
return [];
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a list of resources that web browsers may preload.
|
|
|
|
|
*
|
|
|
|
|
* Behaviour of rel=preload link is specified at <https://www.w3.org/TR/preload/>.
|
|
|
|
|
*
|
|
|
|
|
* Use case for ResourceLoader originally part of T164299.
|
|
|
|
|
*
|
|
|
|
|
* @par Example
|
|
|
|
|
* @code
|
|
|
|
|
* protected function getPreloadLinks() {
|
|
|
|
|
* return [
|
|
|
|
|
* 'https://example.org/script.js' => [ 'as' => 'script' ],
|
|
|
|
|
* 'https://example.org/image.png' => [ 'as' => 'image' ],
|
|
|
|
|
* ];
|
|
|
|
|
* }
|
2018-03-17 05:30:19 +00:00
|
|
|
* @endcode
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
*
|
|
|
|
|
* @par Example using HiDPI image variants
|
|
|
|
|
* @code
|
|
|
|
|
* protected function getPreloadLinks() {
|
|
|
|
|
* return [
|
|
|
|
|
* 'https://example.org/logo.png' => [
|
|
|
|
|
* 'as' => 'image',
|
|
|
|
|
* 'media' => 'not all and (min-resolution: 2dppx)',
|
|
|
|
|
* ],
|
|
|
|
|
* 'https://example.org/logo@2x.png' => [
|
|
|
|
|
* 'as' => 'image',
|
|
|
|
|
* 'media' => '(min-resolution: 2dppx)',
|
|
|
|
|
* ],
|
|
|
|
|
* ];
|
|
|
|
|
* }
|
2018-03-17 05:30:19 +00:00
|
|
|
* @endcode
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
*
|
2022-05-06 09:09:56 +00:00
|
|
|
* @see Module::getHeaders
|
2020-07-03 12:31:37 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
* @since 1.30
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
* @return array Keyed by url, values must be an array containing
|
|
|
|
|
* at least an 'as' key. Optionally a 'media' key as well.
|
2022-05-06 09:09:56 +00:00
|
|
|
*
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
protected function getPreloadLinks( Context $context ) {
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-25 17:57:35 +00:00
|
|
|
/**
|
|
|
|
|
* Get module-specific LESS variables, if any.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2015-10-07 04:55:42 +00:00
|
|
|
* @since 1.27
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2015-09-25 17:57:35 +00:00
|
|
|
* @return array Module-specific LESS variables.
|
|
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
protected function getLessVars( Context $context ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
return [];
|
2015-09-25 17:57:35 +00:00
|
|
|
}
|
|
|
|
|
|
2015-05-14 19:05:47 +00:00
|
|
|
/**
|
|
|
|
|
* Get an array of this module's resources. Ready for serving to the web.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.26
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2015-05-14 19:05:47 +00:00
|
|
|
* @return array
|
|
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getModuleContent( Context $context ) {
|
2015-05-14 19:05:47 +00:00
|
|
|
$contextHash = $context->getHash();
|
|
|
|
|
// Cache this expensive operation. This calls builds the scripts, styles, and messages
|
|
|
|
|
// content which typically involves filesystem and/or database access.
|
|
|
|
|
if ( !array_key_exists( $contextHash, $this->contents ) ) {
|
2015-06-17 20:01:00 +00:00
|
|
|
$this->contents[$contextHash] = $this->buildContent( $context );
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
2015-06-17 20:01:00 +00:00
|
|
|
return $this->contents[$contextHash];
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bundle all resources attached to this module into an array.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.26
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2015-05-14 19:05:47 +00:00
|
|
|
* @return array
|
|
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
final protected function buildContent( Context $context ) {
|
2024-01-26 18:00:09 +00:00
|
|
|
$statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
|
2015-06-26 05:57:17 +00:00
|
|
|
$statStart = microtime( true );
|
2015-05-14 19:05:47 +00:00
|
|
|
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
// This MUST build both scripts and styles, regardless of whether $context->getOnly()
|
|
|
|
|
// is 'scripts' or 'styles' because the result is used by getVersionHash which
|
ResourceLoader: Add support for packageFiles
Package files are files that are part of a module, but are not
immediately executed when the module executes. Instead, they are
lazy-excecuted when require() is called on them. Package files can be
scripts (JS) or data (JSON), and can be real files on the file system,
or virtual files generated by a callback.
Using virtual data files, server-side data and config variables can be
bundled with a module. Support for file-based require() allows us to
import npm modules into ResourceLoader more easily.
The require function passed to each script execution context, which was
previously a reference to the global mw.loader.require() function, is
changed to one that is scoped to the module and the file being executed.
This is needed to support relative paths: require( '../foo.js' ) can
mean a different file depending on the path of the calling file.
The results of require()ing each file (i.e. the value of module.exports
after executing it) are stored, and calling require() on the same file a
second time won't execute it again, but will return the stored value.
Miscellaneous changes:
- Add XmlJsCode::encodeObject(), which combines an associative array of
XmlJsCode objects into one larger XmlJsCode object. This is needed for
encoding the packageFiles parameter in mw.loader.implement() calls.
Bug: T133462
Change-Id: I78cc86e626de0720397718cd2bed8ed279579112
2018-11-03 00:53:17 +00:00
|
|
|
// must be consistent regardless of the 'only' filter on the current request.
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
// Also, when introducing new module content resources (e.g. templates, headers),
|
|
|
|
|
// these should only be included in the array when they are non-empty so that
|
|
|
|
|
// existing modules not using them do not get their cache invalidated.
|
2016-02-17 09:09:32 +00:00
|
|
|
$content = [];
|
2015-05-14 19:05:47 +00:00
|
|
|
|
|
|
|
|
// Scripts
|
resourceloader: Implement debug=2 request splitting
== What ==
Change debug mode 2 to behave more like production mode:
* use module scope (no longer global scope).
* load modules concurrently (no longer each module serially).
* bundle files (no longer each file separately).
What remains different in debug=2 from production mode:
* disable minification.
* disable batching (one module per request).
== How ==
* Limit the old logic (getScriptURLsForDebug) to just legacy debug.
* Set maxQueryLength=0 for non-legacy debug, to ensure each module
still gets its own dedicated request for easy debugging, and to
get concurrency to make more optimal use of server and browser
capacity.
This does not effect package file modules much, as those already
worked in this way. The only difference for package file modules
is that they now load faster (see below) by eliminating the
in-between request.
== Alternative approach ==
An alternative approach, which I considered, is to modify
Module::buildContent(), around where we currently call
getScriptURLsForDebug for DEBUG_LEGACY, and add a conditional branch
for DEBUG_MAIN which would always return an array with a single URL,
to `load.php?modules=:name`. Much like getScriptURLsForDebug does by
default, but without the legacy-specific overrides to that method from
e.g. FileModule.
I decided against this because the mw.loader client handles such
script-arrays in a global FIFO fashion, tailored for legacy debug mode
where it crucial to only serially queue>load>execute one script file
of one module at any given time (because the raw files can't have a
"mw.loader.implement" closure and thus execute immediately on arrival,
with no other coordination for file order and module dependency order).
This would make debug=2 slow, possibly slower than debug=1 since in
debug=1 at least we consolidate most PHP roundtrips in a single batch,
and most other scripts can be served quickly as static file by Apache.
By letting the client act like it does for production mode, and
proactively split its requests, we get a few benefits compared to
this alternative approach:
* Fewer requests and shorter request dependency chain.
There is no in-between request for the "page module batch" that fans
out to individual module reqs. Instead, the client makes those reqs
directly.
* All module requests are discovered and queued with the browser in
one go, letting the server handle them as quickly as it can.
In production, probably all in parallel. Locally, mediawiki-docker
seems to handle about 6 at time (this depite having 10 php-fpm
proccess). I suspect that maybe due to a poor interactions between
HTTP1 connection reuse and keep-alive timeouts, or perhaps unneeded
session locks with sqlite.
* The browser can spend time parsing/compiling other requests at the
same time as one of them executes.
* No additional client-side logic.
* No increase in client payload.
Bug: T85805
Change-Id: I232310eb624e0204484ec9f3d715d5b6b8532fe8
2021-12-06 17:40:46 +00:00
|
|
|
if ( $context->getDebug() === $context::DEBUG_LEGACY && !$context->getOnly() && $this->supportsURLLoading() ) {
|
|
|
|
|
// In legacy debug mode, let supporting modules like FileModule replace the bundled
|
|
|
|
|
// script closure with an array of alternative script URLs to consecutively load instead.
|
|
|
|
|
// See self::getScriptURLsForDebug() more details.
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
$scripts = $this->getScriptURLsForDebug( $context );
|
|
|
|
|
} else {
|
|
|
|
|
$scripts = $this->getScript( $context );
|
2019-07-27 09:26:58 +00:00
|
|
|
if ( is_string( $scripts ) ) {
|
2023-07-24 05:37:04 +00:00
|
|
|
$scripts = [ 'plainScripts' => [ [ 'content' => $scripts ] ] ];
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
$content['scripts'] = $scripts;
|
2015-05-14 19:05:47 +00:00
|
|
|
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
$styles = [];
|
|
|
|
|
// Don't create empty stylesheets like [ '' => '' ] for modules
|
|
|
|
|
// that don't *have* any stylesheets (T40024).
|
|
|
|
|
$stylePairs = $this->getStyles( $context );
|
|
|
|
|
if ( count( $stylePairs ) ) {
|
|
|
|
|
// If we are in debug mode without &only= set, we'll want to return an array of URLs
|
|
|
|
|
// See comment near shouldIncludeScripts() for more details
|
|
|
|
|
if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
|
|
|
|
|
$styles = [
|
|
|
|
|
'url' => $this->getStyleURLsForDebug( $context )
|
|
|
|
|
];
|
|
|
|
|
} else {
|
2023-08-03 04:09:01 +00:00
|
|
|
// Minify CSS before embedding in mw.loader.impl call
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
// (unless in debug mode)
|
|
|
|
|
if ( !$context->getDebug() ) {
|
|
|
|
|
foreach ( $stylePairs as $media => $style ) {
|
|
|
|
|
// Can be either a string or an array of strings.
|
|
|
|
|
if ( is_array( $style ) ) {
|
|
|
|
|
$stylePairs[$media] = [];
|
|
|
|
|
foreach ( $style as $cssText ) {
|
|
|
|
|
if ( is_string( $cssText ) ) {
|
|
|
|
|
$stylePairs[$media][] =
|
|
|
|
|
ResourceLoader::filter( 'minify-css', $cssText );
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
} elseif ( is_string( $style ) ) {
|
|
|
|
|
$stylePairs[$media] = ResourceLoader::filter( 'minify-css', $style );
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
// Wrap styles into @media groups as needed and flatten into a numerical array
|
|
|
|
|
$styles = [
|
2019-09-30 23:08:05 +00:00
|
|
|
'css' => ResourceLoader::makeCombinedStyles( $stylePairs )
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
];
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
$content['styles'] = $styles;
|
2015-05-14 19:05:47 +00:00
|
|
|
|
|
|
|
|
// Messages
|
2015-11-13 00:04:12 +00:00
|
|
|
$blob = $this->getMessageBlob( $context );
|
|
|
|
|
if ( $blob ) {
|
|
|
|
|
$content['messagesBlob'] = $blob;
|
2015-05-14 19:05:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$templates = $this->getTemplates();
|
|
|
|
|
if ( $templates ) {
|
|
|
|
|
$content['templates'] = $templates;
|
|
|
|
|
}
|
|
|
|
|
|
resourceloader: Add support for modules sending preload headers
ResourceLoaderModule objects gain a new method getPreloadLinks() which
returns an array with the meta data required to build a Link rel=preload
header according to the current draft for W3C Preload.
<https://w3c.github.io/preload/>
Another implementation of this is already in use in OutputPage for
preloading the logo image.
This array is formatted by the ResourceLoaderModule::getHeaders method,
which is implemented as "final" at this time, thus restricting use to
the Link rel=preload header.
Headers are exposed and process-cached, like all other content
(scripts, styles, etc.), through ResourceLoaderModule::getModuleContent,
and aggregated by ResoureLoader::makeModuleResponse.
I had hoped for the getPreloadLinks to be stateless (not vary on $context).
Whether something should be preloaded and what, should not vary on the
skin or language. However, while that conceptually holds true, the exact
url for any given resource may still vary. Even the main use case for this
feature (T164299, preloading base modules request) require $context to pass
down skin and lang to the load.php url.
Add full test coverage and example documentation.
Bug: T164299
Change-Id: I2bfe0796ceaa0c82579c501f5b10e931f2175681
2017-07-18 02:36:01 +00:00
|
|
|
$headers = $this->getHeaders( $context );
|
|
|
|
|
if ( $headers ) {
|
|
|
|
|
$content['headers'] = $headers;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 03:40:54 +00:00
|
|
|
$deprecationWarning = $this->getDeprecationWarning();
|
|
|
|
|
if ( $deprecationWarning !== null ) {
|
|
|
|
|
$content['deprecationWarning'] = $deprecationWarning;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-26 05:57:17 +00:00
|
|
|
$statTiming = microtime( true ) - $statStart;
|
2024-01-26 18:00:09 +00:00
|
|
|
$statName = strtr( $this->getName(), '.', '_' );
|
|
|
|
|
|
|
|
|
|
$statsFactory->getTiming( 'resourceloader_build_seconds' )
|
2024-02-07 17:52:33 +00:00
|
|
|
->setLabel( 'name', $statName )
|
|
|
|
|
->copyToStatsdAt( [
|
|
|
|
|
'resourceloader_build.all',
|
|
|
|
|
"resourceloader_build.$statName",
|
|
|
|
|
] )
|
2024-01-26 18:00:09 +00:00
|
|
|
->observe( 1000 * $statTiming );
|
2015-06-26 05:57:17 +00:00
|
|
|
|
2015-05-14 19:05:47 +00:00
|
|
|
return $content;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-04 04:00:09 +00:00
|
|
|
/**
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* Get a string identifying the current version of this module in a given context.
|
2010-09-05 13:31:34 +00:00
|
|
|
*
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* Whenever anything happens that changes the module's response (e.g. scripts, styles, and
|
2021-08-28 01:50:11 +00:00
|
|
|
* messages) this value must change. This value is used to store module responses in caches,
|
|
|
|
|
* both server-side (by a CDN, or other HTTP cache), and client-side (in `mw.loader.store`,
|
|
|
|
|
* and in the browser's own HTTP cache).
|
2012-10-19 20:03:05 +00:00
|
|
|
*
|
2021-08-28 01:50:11 +00:00
|
|
|
* The underlying methods called here for any given module should be quick because this
|
|
|
|
|
* is called for potentially thousands of module bundles in the same request as part of the
|
2022-05-06 09:09:56 +00:00
|
|
|
* StartUpModule, which is how we invalidate caches and propagate changes to clients.
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
*
|
|
|
|
|
* @since 1.26
|
2021-08-28 01:50:11 +00:00
|
|
|
* @see self::getDefinitionSummary for how to customize version computation.
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2021-08-28 01:50:11 +00:00
|
|
|
* @return string Hash formatted by ResourceLoader::makeHash
|
2010-09-04 04:00:09 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
final public function getVersionHash( Context $context ) {
|
resourceloader: Skip version hash calculation in debug mode
=== Why
* More speed
In debug mode, the server should regenerate the startup manifest
on each page view to ensure immediate effect of changes. But,
this also means more version recomputation work on the server.
For most modules, this was already quite fast on repeat views
because of OS-level file caches, and our file-hash caches and
LESS compile caches in php-apcu from ResourceLoader.
But, this makes it even faster.
* Better integration with browser devtools.
Breakpoints stay more consistently across browsers when the
URL stays the same even after you have changed the file and
reloaded the page. For static files, I believe most browsers ignore
query parameters. But for package files that come from load.php,
this was harder for browsers to guess correctly which old script URL
is logically replaced by a different one on the next page view.
=== How
Change Module::getVersionHash to return empty strings in debug mode.
I considered approaching this from StartupModule::getModuleRegistrations
instead to make the change apply only to the client-side manifest.
I decided against this because we have other calls to getVersionHash
on the server-side (such as for E-Tag calculation, and formatting
cross-wiki URLs) which would then not match the version queries that
mw.loader formats in debug mode.
Also, those calls would still be incurring some the avoidable costs.
=== Notes
* The two test cases for verifying the graceful fallback in production
if version hash computations throw an exception, were moved to a
non-debug test case as no longer happen now during the debug
(unminified) test cases.
* Avoid "PHP Notice: Undefined offset 0" in testMakeModuleResponseStartupError
by adding a fallback to empty string so that if the test fails,
it fails in a more useful way instead of aborting with this error
before the assertion happens. (Since PHPUnit generally stops on the
first error.)
* In practice, there are still "version" query parameters and E-Tag
headers in debug mode. These are not module versions, but URL
"combined versions" crafted by getCombinedVersion() in JS and PHP.
These return the constant "ztntf" in debug mode, which is the hash
of an empty string. We could alter these methods to special-case
when all inputs are and join to a still-empty string, or maybe we
just leave them be. I've done the latter for now.
Bug: T235672
Bug: T85805
Change-Id: I0e63eef4f85b13089a0aa3806a5b6f821d527a92
2021-08-28 02:53:36 +00:00
|
|
|
if ( $context->getDebug() ) {
|
|
|
|
|
// In debug mode, make uncached startup module extra fast by not computing any hashes.
|
|
|
|
|
// Server responses from load.php for individual modules already have no-cache so
|
|
|
|
|
// we don't need them. This also makes breakpoint debugging easier, as each module
|
|
|
|
|
// gets its own consistent URL. (T235672)
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
// Cache this somewhat expensive operation. Especially because some classes
|
|
|
|
|
// (e.g. startup module) iterate more than once over all modules to get versions.
|
|
|
|
|
$contextHash = $context->getHash();
|
|
|
|
|
if ( !array_key_exists( $contextHash, $this->versionHash ) ) {
|
resourceloader: Enable module content version for data modules
This greatly simplifies logic required to compute module versions.
It also makes it significantly less error-prone.
Since f37cee996e, we support hashes as versions (instead of timestamps).
This means we can build a hash of the content directly, instead of compiling a
large array with all values that may influence the module content somehow.
Benefits:
* Remove all methods and logic related to querying database and disk for
timestamps, revision numbers, definition summaries, cache epochs, and more.
* No longer needlessly invalidate cache as a result of no-op changes to
implementation datails. Due to inclusion of absolute file paths in the
definition summary, cache was always invalidated when moving wikis to newer
MediaWiki branches; even if the module observed no actual changes.
* When changes are reverted within a certain period of time, old caches can now
be re-used. The module would produce the same version hash as before.
Previously when a change was deployed and then reverted, all web clients (even
those that never saw the bad version) would have re-fetch modules because the
version increased.
Updated unit tests to account for the change in version. New default version of
empty test modules is: "mvgTPvXh". For the record, this comes from the base64
encoding of the SHA1 digest of the JSON serialised form of the module content:
> $str = '{"scripts":"","styles":{"css":[]},"messagesBlob":"{}"}';
> echo base64_encode(sha1($str, true));
> FEb3+VuiUm/fOMfod1bjw/te+AQ=
Enabled content versioning for the data modules in MediaWiki core:
* EditToolbarModule
* JqueryMsgModule
* LanguageDataModule
* LanguageNamesModule
* SpecialCharacterDataModule
* UserCSSPrefsModule
* UserDefaultsModule
* UserOptionsModule
The FileModule and base class explicitly disable it for now and keep their
current behaviour of using the definition summary. We may remove it later, but
that requires more performance testing first.
Explicitly disable it in the WikiModule class to avoid breakage when the
default changes.
Ref T98087.
Change-Id: I782df43c50dfcfb7d7592f744e13a3a0430b0dc6
2015-06-02 17:27:23 +00:00
|
|
|
if ( $this->enableModuleContentVersion() ) {
|
resourceloader: Remove selective build optimisation from getModuleContent()
This follows 5ddd7f91c7, which factored out response building
from ResourceLoader.php to ResourceLoaderModule::buildContent.
As optimisation, I made this method only return the array keys
needed for the current response; based $context->getOnly().
The reason for this refactoring was the creation of the
'enableModuleContentVersion' option to getVersionHash(), which
would use this method to create a module response, and hash it.
During the implementation of that option, I ran into a problem.
getVersionHash() is called by the startup module for each
registered module, to create the manifest. The context for the
StartupModule request itself has "only=scripts". But, we must
still compute the version hashes for whole modules, not just
their scripts.
I worked around that problem in aac831f9fa by creating a mock
context in getVersionHash() that stubs out the 'only' parameter.
This worked, but made the assumption that the scripts and styles
of a module cannot differ based on the 'only' parameter.
This assumption was wrong, because the 'only' parameter is part
of ResourceLoaderContext and available to all getters to vary on.
Fortunately, the 'enableModuleContentVersion' option is off by
default and nobody currently using it was differing its output
by the 'only' parameter.
I intend to make use of the 'enableModuleContentVersion' option
in StartupModule to fix T201686. And StartupModule outputs a
manifest if the request specifies only=scripts, and outputs
a warning otherwise. As such, it cannot compute its version
if the 'only' parameter is stubbed out.
* Remove the 'only' parameter stubbing.
* Remove the selective building from the buildContent() method.
This was not very useful because we need to build the whole
module regardless when computing the version.
As benefit, this means the in-process cache is now shared between
the call from getVersionHash and the call from makeModuleResponse.
Bug: T201686
Change-Id: I8a17888f95f86ac795bc2de43086225b8a8f4b78
2018-08-30 01:42:24 +00:00
|
|
|
// Detect changes directly by hashing the module contents.
|
resourceloader: Enable module content version for data modules
This greatly simplifies logic required to compute module versions.
It also makes it significantly less error-prone.
Since f37cee996e, we support hashes as versions (instead of timestamps).
This means we can build a hash of the content directly, instead of compiling a
large array with all values that may influence the module content somehow.
Benefits:
* Remove all methods and logic related to querying database and disk for
timestamps, revision numbers, definition summaries, cache epochs, and more.
* No longer needlessly invalidate cache as a result of no-op changes to
implementation datails. Due to inclusion of absolute file paths in the
definition summary, cache was always invalidated when moving wikis to newer
MediaWiki branches; even if the module observed no actual changes.
* When changes are reverted within a certain period of time, old caches can now
be re-used. The module would produce the same version hash as before.
Previously when a change was deployed and then reverted, all web clients (even
those that never saw the bad version) would have re-fetch modules because the
version increased.
Updated unit tests to account for the change in version. New default version of
empty test modules is: "mvgTPvXh". For the record, this comes from the base64
encoding of the SHA1 digest of the JSON serialised form of the module content:
> $str = '{"scripts":"","styles":{"css":[]},"messagesBlob":"{}"}';
> echo base64_encode(sha1($str, true));
> FEb3+VuiUm/fOMfod1bjw/te+AQ=
Enabled content versioning for the data modules in MediaWiki core:
* EditToolbarModule
* JqueryMsgModule
* LanguageDataModule
* LanguageNamesModule
* SpecialCharacterDataModule
* UserCSSPrefsModule
* UserDefaultsModule
* UserOptionsModule
The FileModule and base class explicitly disable it for now and keep their
current behaviour of using the definition summary. We may remove it later, but
that requires more performance testing first.
Explicitly disable it in the WikiModule class to avoid breakage when the
default changes.
Ref T98087.
Change-Id: I782df43c50dfcfb7d7592f744e13a3a0430b0dc6
2015-06-02 17:27:23 +00:00
|
|
|
$str = json_encode( $this->getModuleContent( $context ) );
|
|
|
|
|
} else {
|
|
|
|
|
// Infer changes based on definition and other metrics
|
|
|
|
|
$summary = $this->getDefinitionSummary( $context );
|
2018-09-14 00:13:57 +00:00
|
|
|
if ( !isset( $summary['_class'] ) ) {
|
2015-06-18 20:52:17 +00:00
|
|
|
throw new LogicException( 'getDefinitionSummary must call parent method' );
|
resourceloader: Enable module content version for data modules
This greatly simplifies logic required to compute module versions.
It also makes it significantly less error-prone.
Since f37cee996e, we support hashes as versions (instead of timestamps).
This means we can build a hash of the content directly, instead of compiling a
large array with all values that may influence the module content somehow.
Benefits:
* Remove all methods and logic related to querying database and disk for
timestamps, revision numbers, definition summaries, cache epochs, and more.
* No longer needlessly invalidate cache as a result of no-op changes to
implementation datails. Due to inclusion of absolute file paths in the
definition summary, cache was always invalidated when moving wikis to newer
MediaWiki branches; even if the module observed no actual changes.
* When changes are reverted within a certain period of time, old caches can now
be re-used. The module would produce the same version hash as before.
Previously when a change was deployed and then reverted, all web clients (even
those that never saw the bad version) would have re-fetch modules because the
version increased.
Updated unit tests to account for the change in version. New default version of
empty test modules is: "mvgTPvXh". For the record, this comes from the base64
encoding of the SHA1 digest of the JSON serialised form of the module content:
> $str = '{"scripts":"","styles":{"css":[]},"messagesBlob":"{}"}';
> echo base64_encode(sha1($str, true));
> FEb3+VuiUm/fOMfod1bjw/te+AQ=
Enabled content versioning for the data modules in MediaWiki core:
* EditToolbarModule
* JqueryMsgModule
* LanguageDataModule
* LanguageNamesModule
* SpecialCharacterDataModule
* UserCSSPrefsModule
* UserDefaultsModule
* UserOptionsModule
The FileModule and base class explicitly disable it for now and keep their
current behaviour of using the definition summary. We may remove it later, but
that requires more performance testing first.
Explicitly disable it in the WikiModule class to avoid breakage when the
default changes.
Ref T98087.
Change-Id: I782df43c50dfcfb7d7592f744e13a3a0430b0dc6
2015-06-02 17:27:23 +00:00
|
|
|
}
|
|
|
|
|
$str = json_encode( $summary );
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
}
|
|
|
|
|
|
2015-06-17 20:01:00 +00:00
|
|
|
$this->versionHash[$contextHash] = ResourceLoader::makeHash( $str );
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
}
|
2015-06-17 20:01:00 +00:00
|
|
|
return $this->versionHash[$contextHash];
|
2010-09-29 23:25:07 +00:00
|
|
|
}
|
2011-10-14 08:06:54 +00:00
|
|
|
|
resourceloader: Enable module content version for data modules
This greatly simplifies logic required to compute module versions.
It also makes it significantly less error-prone.
Since f37cee996e, we support hashes as versions (instead of timestamps).
This means we can build a hash of the content directly, instead of compiling a
large array with all values that may influence the module content somehow.
Benefits:
* Remove all methods and logic related to querying database and disk for
timestamps, revision numbers, definition summaries, cache epochs, and more.
* No longer needlessly invalidate cache as a result of no-op changes to
implementation datails. Due to inclusion of absolute file paths in the
definition summary, cache was always invalidated when moving wikis to newer
MediaWiki branches; even if the module observed no actual changes.
* When changes are reverted within a certain period of time, old caches can now
be re-used. The module would produce the same version hash as before.
Previously when a change was deployed and then reverted, all web clients (even
those that never saw the bad version) would have re-fetch modules because the
version increased.
Updated unit tests to account for the change in version. New default version of
empty test modules is: "mvgTPvXh". For the record, this comes from the base64
encoding of the SHA1 digest of the JSON serialised form of the module content:
> $str = '{"scripts":"","styles":{"css":[]},"messagesBlob":"{}"}';
> echo base64_encode(sha1($str, true));
> FEb3+VuiUm/fOMfod1bjw/te+AQ=
Enabled content versioning for the data modules in MediaWiki core:
* EditToolbarModule
* JqueryMsgModule
* LanguageDataModule
* LanguageNamesModule
* SpecialCharacterDataModule
* UserCSSPrefsModule
* UserDefaultsModule
* UserOptionsModule
The FileModule and base class explicitly disable it for now and keep their
current behaviour of using the definition summary. We may remove it later, but
that requires more performance testing first.
Explicitly disable it in the WikiModule class to avoid breakage when the
default changes.
Ref T98087.
Change-Id: I782df43c50dfcfb7d7592f744e13a3a0430b0dc6
2015-06-02 17:27:23 +00:00
|
|
|
/**
|
|
|
|
|
* Whether to generate version hash based on module content.
|
|
|
|
|
*
|
|
|
|
|
* If a module requires database or file system access to build the module
|
|
|
|
|
* content, consider disabling this in favour of manually tracking relevant
|
|
|
|
|
* aspects in getDefinitionSummary(). See getVersionHash() for how this is used.
|
|
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
resourceloader: Enable module content version for data modules
This greatly simplifies logic required to compute module versions.
It also makes it significantly less error-prone.
Since f37cee996e, we support hashes as versions (instead of timestamps).
This means we can build a hash of the content directly, instead of compiling a
large array with all values that may influence the module content somehow.
Benefits:
* Remove all methods and logic related to querying database and disk for
timestamps, revision numbers, definition summaries, cache epochs, and more.
* No longer needlessly invalidate cache as a result of no-op changes to
implementation datails. Due to inclusion of absolute file paths in the
definition summary, cache was always invalidated when moving wikis to newer
MediaWiki branches; even if the module observed no actual changes.
* When changes are reverted within a certain period of time, old caches can now
be re-used. The module would produce the same version hash as before.
Previously when a change was deployed and then reverted, all web clients (even
those that never saw the bad version) would have re-fetch modules because the
version increased.
Updated unit tests to account for the change in version. New default version of
empty test modules is: "mvgTPvXh". For the record, this comes from the base64
encoding of the SHA1 digest of the JSON serialised form of the module content:
> $str = '{"scripts":"","styles":{"css":[]},"messagesBlob":"{}"}';
> echo base64_encode(sha1($str, true));
> FEb3+VuiUm/fOMfod1bjw/te+AQ=
Enabled content versioning for the data modules in MediaWiki core:
* EditToolbarModule
* JqueryMsgModule
* LanguageDataModule
* LanguageNamesModule
* SpecialCharacterDataModule
* UserCSSPrefsModule
* UserDefaultsModule
* UserOptionsModule
The FileModule and base class explicitly disable it for now and keep their
current behaviour of using the definition summary. We may remove it later, but
that requires more performance testing first.
Explicitly disable it in the WikiModule class to avoid breakage when the
default changes.
Ref T98087.
Change-Id: I782df43c50dfcfb7d7592f744e13a3a0430b0dc6
2015-06-02 17:27:23 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function enableModuleContentVersion() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-30 00:31:37 +00:00
|
|
|
/**
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* Get the definition summary for this module.
|
|
|
|
|
*
|
2019-10-03 21:38:00 +00:00
|
|
|
* This is the method subclasses are recommended to use to track data that
|
|
|
|
|
* should influence the module's version hash.
|
2013-08-30 00:31:37 +00:00
|
|
|
*
|
2019-10-03 21:38:00 +00:00
|
|
|
* Subclasses must call the parent getDefinitionSummary() and add to the
|
|
|
|
|
* returned array. It is recommended that each subclass appends its own array,
|
|
|
|
|
* to prevent clashes or accidental overwrites of array keys from the parent
|
|
|
|
|
* class. This gives each subclass a clean scope.
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
*
|
|
|
|
|
* @code
|
|
|
|
|
* $summary = parent::getDefinitionSummary( $context );
|
2016-09-12 10:06:37 +00:00
|
|
|
* $summary[] = [
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* 'foo' => 123,
|
|
|
|
|
* 'bar' => 'quux',
|
2016-09-12 10:06:37 +00:00
|
|
|
* ];
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* return $summary;
|
|
|
|
|
* @endcode
|
|
|
|
|
*
|
2019-10-03 21:38:00 +00:00
|
|
|
* Return an array that contains all significant properties that define the
|
|
|
|
|
* module. The returned data should be deterministic and only change when
|
|
|
|
|
* the generated module response would change. Prefer content hashes over
|
|
|
|
|
* modified timestamps because timestamps may change for unrelated reasons
|
|
|
|
|
* and are not deterministic (T102578). For example, because timestamps are
|
|
|
|
|
* not stored in Git, each branch checkout would cause all files to appear as
|
|
|
|
|
* new. Timestamps also tend to not match between servers causing additional
|
|
|
|
|
* ever-lasting churning of the version hash.
|
|
|
|
|
*
|
|
|
|
|
* Be careful not to normalise the data too much in an effort to be deterministic.
|
|
|
|
|
* For example, if a module concatenates files together (order is significant),
|
|
|
|
|
* then the definition summary could be a list of file names, and a list of
|
|
|
|
|
* file hashes. These lists should not be sorted as that would mean the cache
|
|
|
|
|
* is not invalidated when the order changes (T39812).
|
|
|
|
|
*
|
|
|
|
|
* This data structure must exclusively contain primitive "scalar" values,
|
|
|
|
|
* as it will be serialised using `json_encode`.
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* @since 1.23
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
resourceloader: Replace timestamp system with version hashing
Modules now track their version via getVersionHash() instead of getModifiedTime().
== Background ==
While some resources have observeable timestamps (e.g. files stored on disk),
many other resources do not. E.g. config variables, and module definitions.
For static file modules, one can e.g. revert one of more files in a module to a
previous version and not affect the max timestamp.
Wiki modules include pages only if they exist. The user module supports common.js
and skin.js. By default neither exists. If a user has both, and then the
less-recently modified one is deleted, the max-timestamp remains unchanged.
For client-side caching, batch requests use "Math.max" on the relevant timestamps.
Again, if a module changes but another module is more recent (e.g. out-of-order
deployment, or out-of-order discovery), the change would not result in a cache miss.
More scenarios can be found in the associated Phabricator tasks.
== Version hash ==
Previously we virtually mapped these variables to a timestamp by storing the current
time alongside a hash of the value in ObjectCache. Considering the number of
possible request contexts (wikis * modules * users * skins * languages) this doesn't
work well. It results in needless cache invalidation when the first time observation
is purged due to LRU algorithms. It also has other minor bugs leading to fewer
cache hits.
All modules automatically get the benefits of version hashing with this change.
The old getDefinitionMtime() and getHashMtime() have been replaced with dummies
that return 1. These functions are often called from getModifiedTime() in subclasses.
For backward-compatibility, their respective values (definition summary and hash)
are now included in getVersionHash directly.
As examples, the following modules have been updated to use getVersionHash directly.
Other modules still work fine and can be updated later.
* ResourceLoaderFileModule
* ResourceLoaderEditToolbarModule
* ResourceLoaderStartUpModule
* ResourceLoaderWikiModule
The presence of hashes in place of timestamps increases the startup module size on
a default MediaWiki install from 4.4k to 5.8k (after gzip and minification).
== ETag ==
Since timestamps are no longer tracked, we need a different way to implement caching
for cache proxies (e.g. Varnish) and web browsers. Previously we used the
Last-Modified header (in combination with Cache-Control and Expires).
Instead of Last-Modified (and If-Modified-Since), we use ETag (and If-None-Match).
Entity tags (new in HTTP/1.1) are much stricter than Last-Modified by default.
They instruct browsers to allow usage of partial Range requests. Since our responses
are dynamically generated, we need to use the Weak version of ETag.
While this sounds bad, it's no different than Last-Modified. As reassured by
RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3> the
specified behaviour behind Last-Modified follows the same "Weak" caching logic as
Entity tags. It's just that entity tags are capable of a stricter mode (whereas
Last-Modified is inherently weak).
== File cache ==
If $wgUseFileCache is enabled, ResourceLoader uses ResourceFileCache to cache
load.php responses. While the blind TTL handling (during the allowed expiry period)
is still maxage/timestamp based, tryRespondNotModified() now requires the caller to
know the expected ETag.
For this to work, the FileCache handling had to be moved from the top of
ResoureLoader::respond() to after the expected ETag is computed.
This also allows us to remove the duplicate tryRespondNotModified() handling since
that's is already handled by ResourceLoader::respond() meanwhile.
== Misc ==
* Remove redundant modifiedTime cache in ResourceLoaderFileModule.
* Change bugzilla references to Phabricator.
* Centralised inclusion of wgCacheEpoch using getDefinitionSummary. Previously this
logic was duplicated in each place the modified timestamp was used.
* It's easy to forget calling the parent class in getDefinitionSummary().
Previously this method only tracked 'class' by default. As such, various
extensions hardcoded that one value instead of calling the parent and extending
the array. To better prevent this in the future, getVersionHash() now asserts
that the '_cacheEpoch' property made it through.
* tests: Don't use getDefinitionSummary() as an API.
Fix ResourceLoaderWikiModuleTest to call getPages properly.
* In tests, the default timestamp used to be 1388534400000 (which is the unix time
of 20140101000000; the unit tests' CacheEpoch). The new version hash of these
modules is "XyCC+PSK", which is the base64 encoded prefix of the SHA1 digest of:
'{"_class":"ResourceLoaderTestModule","_cacheEpoch":"20140101000000"}'
* Add sha1.js library for client-side hash generation.
Compared various different implementations for code size (after minfication/gzip),
and speed (when used for short hexidecimal strings).
https://jsperf.com/sha1-implementations
- CryptoJS <https://code.google.com/p/crypto-js/#SHA-1> (min+gzip: 2.5k)
http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js
Chrome: 45k, Firefox: 89k, Safari: 92k
- jsSHA <https://github.com/Caligatio/jsSHA>
https://github.com/Caligatio/jsSHA/blob/3c1d4f2e/src/sha1.js (min+gzip: 1.8k)
Chrome: 65k, Firefox: 53k, Safari: 69k
- phpjs-sha1 <https://github.com/kvz/phpjs> (RL min+gzip: 0.8k)
https://github.com/kvz/phpjs/blob/1eaab15d/functions/strings/sha1.js
Chrome: 200k, Firefox: 280k, Safari: 78k
Modern browsers implement the HTML5 Crypto API. However, this API is asynchronous,
only enabled when on HTTPS in Chromium, and is quite low-level. It requires boilerplate
code to actually use with TextEncoder, ArrayBuffer and Uint32Array. Due this being
needed in the module loader, we'd have to load the fallback regardless. Considering
this is not used in a critical path for performance, it's not worth shipping two
implementations for this optimisation.
May also resolve:
* T44094
* T90411
* T94810
Bug: T94074
Change-Id: Ibb292d2416839327d1807a66c78fd96dac0637d0
2015-04-29 22:53:24 +00:00
|
|
|
* @return array|null
|
2013-08-30 00:31:37 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function getDefinitionSummary( Context $context ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
return [
|
2017-03-07 02:14:14 +00:00
|
|
|
'_class' => static::class,
|
2017-09-29 20:35:00 +00:00
|
|
|
// Make sure that when filter cache for minification is invalidated,
|
|
|
|
|
// we also change the HTTP urls and mw.loader.store keys (T176884).
|
|
|
|
|
'_cacheVersion' => ResourceLoader::CACHE_VERSION,
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2013-08-30 00:31:37 +00:00
|
|
|
}
|
|
|
|
|
|
2011-02-19 17:07:05 +00:00
|
|
|
/**
|
|
|
|
|
* Check whether this module is known to be empty. If a child class
|
|
|
|
|
* has an easy and cheap way to determine that this module is
|
|
|
|
|
* definitely going to be empty, it should override this method to
|
|
|
|
|
* return true in that case. Callers may optimize the request for this
|
|
|
|
|
* module away if this function returns true.
|
2020-07-03 12:31:37 +00:00
|
|
|
*
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2013-09-13 04:50:45 +00:00
|
|
|
* @return bool
|
2011-02-19 17:07:05 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function isKnownEmpty( Context $context ) {
|
2011-02-19 17:07:05 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2011-07-06 21:48:09 +00:00
|
|
|
|
2017-06-30 18:19:38 +00:00
|
|
|
/**
|
2022-01-09 17:44:44 +00:00
|
|
|
* Check whether this module should be embedded rather than linked
|
2017-06-30 18:19:38 +00:00
|
|
|
*
|
|
|
|
|
* Modules returning true here will be embedded rather than loaded by
|
2022-05-06 09:09:56 +00:00
|
|
|
* ClientHtml.
|
2017-06-30 18:19:38 +00:00
|
|
|
*
|
|
|
|
|
* @since 1.30
|
2020-07-13 08:57:12 +00:00
|
|
|
* @stable to override
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2017-06-30 18:19:38 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public function shouldEmbedModule( Context $context ) {
|
2022-01-12 14:34:02 +00:00
|
|
|
return $this->getGroup() === self::GROUP_PRIVATE;
|
2017-06-30 18:19:38 +00:00
|
|
|
}
|
|
|
|
|
|
2024-03-04 01:38:30 +00:00
|
|
|
/**
|
|
|
|
|
* Whether to skip the structure test ResourcesTest::testRespond() for this
|
|
|
|
|
* module.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.42
|
|
|
|
|
* @stable to override
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function shouldSkipStructureTest() {
|
|
|
|
|
return $this->getGroup() === self::GROUP_PRIVATE;
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-06 21:48:09 +00:00
|
|
|
/**
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
* Validate a user-provided JavaScript blob.
|
2011-07-06 21:48:09 +00:00
|
|
|
*
|
|
|
|
|
* @param string $fileName
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
* @param string $contents JavaScript code
|
|
|
|
|
* @return string JavaScript code, either the original content or a replacement
|
|
|
|
|
* that uses `mw.log.error()` to communicate a syntax error.
|
2011-07-06 21:48:09 +00:00
|
|
|
*/
|
|
|
|
|
protected function validateScriptFile( $fileName, $contents ) {
|
2022-06-22 02:41:41 +00:00
|
|
|
if ( !$this->getConfig()->get( MainConfigNames::ResourceLoaderValidateJS ) ) {
|
|
|
|
|
return $contents;
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
}
|
2022-06-22 02:41:41 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
|
|
|
|
// Cache potentially slow parsing of JavaScript code during the
|
|
|
|
|
// critical path. This happens lazily when responding to requests
|
|
|
|
|
// for modules=site, modules=user, and Gadgets.
|
|
|
|
|
$error = $cache->getWithSetCallback(
|
|
|
|
|
$cache->makeKey(
|
|
|
|
|
'resourceloader-userjsparse',
|
|
|
|
|
self::USERJSPARSE_CACHE_VERSION,
|
|
|
|
|
md5( $contents ),
|
|
|
|
|
$fileName
|
|
|
|
|
),
|
|
|
|
|
$cache::TTL_WEEK,
|
|
|
|
|
static function () use ( $contents, $fileName ) {
|
|
|
|
|
try {
|
2023-10-03 01:22:48 +00:00
|
|
|
Peast::ES2016( $contents )->parse();
|
|
|
|
|
} catch ( PeastSyntaxException $e ) {
|
|
|
|
|
return $e->getMessage() . " on line " . $e->getPosition()->getLine();
|
2022-06-22 02:41:41 +00:00
|
|
|
}
|
|
|
|
|
// Cache success as null
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
);
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
|
|
|
|
|
if ( $error ) {
|
|
|
|
|
// Send the error to the browser console client-side.
|
|
|
|
|
// By returning this as replacement for the actual script,
|
|
|
|
|
// we ensure user-provided scripts are safe to include in a batch
|
|
|
|
|
// request, without risk of a syntax error in this blob breaking
|
|
|
|
|
// the response itself.
|
|
|
|
|
return 'mw.log.error(' .
|
|
|
|
|
json_encode(
|
2023-10-03 01:22:48 +00:00
|
|
|
'Parse error: ' . $error
|
resourceloader: Remove redundant blob from validateScriptFile cache
Originally introduced in r91608 (0f201b19f47507), which cached
the input verbatim in Memcached, which is somewhat wasteful.
Instead, use null for the 99% case of the script being valid,
and cache that.
Also follows-up 3a748592f8da00 which made this a shared cache key,
which I don't think makes sense here since since there aren't any
shared resources going through this code path.
As counter example, the minification cache applies also to JS
files we deploy from Git, which are the same on all wikis and
identified by a file path that will match regardless of wiki
content, and so benefit from shared caching and can safely do so.
User scripts, however, are always stored on a particular wiki.
Even if they are loaded across domains in a browser, they will
still come through the wiki where they are stored, so this has
no added value.
Change-Id: I1615359e9ae8762f177004a02a9d3f69178e05c1
2020-12-07 21:09:42 +00:00
|
|
|
) .
|
|
|
|
|
');';
|
2017-05-12 18:03:12 +00:00
|
|
|
}
|
2022-06-22 02:41:41 +00:00
|
|
|
return $contents;
|
2011-07-06 21:48:09 +00:00
|
|
|
}
|
|
|
|
|
|
2015-07-09 18:30:53 +00:00
|
|
|
/**
|
2015-09-16 23:51:53 +00:00
|
|
|
* Compute a non-cryptographic string hash of a file's contents.
|
|
|
|
|
* If the file does not exist or cannot be read, returns an empty string.
|
2015-07-09 18:30:53 +00:00
|
|
|
*
|
2015-09-16 23:51:53 +00:00
|
|
|
* @since 1.26 Uses MD4 instead of SHA1.
|
2021-11-26 15:21:17 +00:00
|
|
|
* @param string $filePath
|
2015-07-09 18:30:53 +00:00
|
|
|
* @return string Hash
|
|
|
|
|
*/
|
|
|
|
|
protected static function safeFileHash( $filePath ) {
|
2015-09-23 08:11:58 +00:00
|
|
|
return FileContentsHasher::getFileContentsHash( $filePath );
|
2015-07-09 18:30:53 +00:00
|
|
|
}
|
2019-06-24 14:47:23 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get vary string.
|
|
|
|
|
*
|
|
|
|
|
* @internal For internal use only.
|
2022-05-06 09:09:56 +00:00
|
|
|
* @param Context $context
|
2021-06-17 14:32:05 +00:00
|
|
|
* @return string
|
2019-06-24 14:47:23 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
public static function getVary( Context $context ) {
|
2019-06-24 14:47:23 +00:00
|
|
|
return implode( '|', [
|
|
|
|
|
$context->getSkin(),
|
|
|
|
|
$context->getLanguage(),
|
|
|
|
|
] );
|
|
|
|
|
}
|
2010-09-04 04:00:09 +00:00
|
|
|
}
|