wiki.techinc.nl/includes/ResourceLoader/UserOptionsModule.php

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

93 lines
2.7 KiB
PHP
Raw Normal View History

<?php
namespace MediaWiki\ResourceLoader;
use MediaWiki\MediaWikiServices;
use MediaWiki\User\Options\UserOptionsLookup;
/**
* 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
*
* @file
* @author Trevor Parscal
* @author Roan Kattouw
*/
/**
resourceloader: Merge 'user.tokens' module into 'user.options' For back-compat, keep 'user.tokens' as deprecated alias to 'user.options' for one release cycle (to be removed in MW 1.36). == user.options == As before, 'user.options' arrives immediately on every page view, embedded in the HTML. It has an async dependency on 'user.defaults', which is not downloaded until there is a known demand on 'user.options'. Once that arrives, the implementation closure of 'user.options' will execute, and the module becomes 'ready'. == user.options "empty" == Before this change, UserOptionsModule used isKnownEmpty to consider the module "empty" for logged-out users (as well as for logged-in users that haven't yet set any preferences). This was a mistake. It is invalid in ResourceLoader to mark a module as "empty" if that module has dependencies (see also T191596 and c3f200849). This broke the state machine. The impact was minimal given that it is unlikely for features to read keys from mw.user.options for logged-out users, which if attempted would have simply returned null for all keys. == New HTML == The user.options module is always embedded (never empty), and always has a dependency on user.defaults. == Cached HTML == The cached HTML for anons sets user.options's state to ready without waiting for any dependency. Per the above, this was already causing subtle bugs with mw.user.options.get() likely returning null for anons, which was fairly innocent. For tokens a bottom value of null would be problematic as the default for tokens must be "+\" instead. To make sure that is available for cached page views, set this directly in mediawiki.base.js. The cached HTML does contain an implement call for 'user.tokens' that contains the same defaults, but new code will not be asking for or waiting for user.tokens, so that is unused. Bug: T235457 Change-Id: I51e01d6fa604578cd2906337bde5a4760633c027
2020-03-13 22:46:14 +00:00
* Module for per-user private data that is transmitted on all HTML web responses.
*
* This module is embedded by ClientHtml and sent to the browser
resourceloader: Bundle `user.defaults` as part of `mediawiki.base` == Background == The `user.options` module is private, and thus has to be embedded in the page HTML. This data is quite large. For example, on enwiki the finalized mw.user.options object is about 3KB serialized/compressed (7KB uncompressed). The `user.defaults` module is an implementation detail of `user.options`, and was created to accomplish mainly two things: * Save significant data transfers by allowing it to be cached client-side without being part of the article. * Ensure consistency between articles and allow faster deployment of changes, by not being part of the cacheable article HTML. All our pageviews already load `user.defaults`, as a dependency of the popular `mediawiki.api` and `mediawiki.user` modules. These are used by `mediawiki.page.ready` (queued on all pages), and on Wikipedia these are also loaded on all pages by ULS, VisualEditor, EventLogging, and more. As such, in practice, bundling "user.defaults" with "mediawiki.base" will not cause the data to be loaded more often than before. == What == * Add virtual "user.json" package file with the same data that was previously exported by ResourceLoaderUserDefaultsModule, and pass it to mw.user.options.set() from base module's entry point. An alternative way would be to use a "user.js" file, which would return a generated "mw.user.options.set()" expression. I went for exporting it as JSON for improved maintainability (reducing the amount of JS code written in PHP), and because it performs slightly better. The JS file would implicitly come with a file closure (tiny bit more bytes), and would then be lazy executed (tiny bit more time). The chosen approach allows the browser to compile the JSON off-the-main-thread ahead of time while the module response downloads. Then when the module executes, we can reference the JSON object and use it directly. * Update internal dependency from `user.options`. * Remove `user.defaults` module without deprecation. It is an internal module with no direct use anywhere in Git (Codeseach), and no use anywhere on-wiki (Global Search). Change-Id: Id3916f94f75078808951863dea2b3a9c71b0e30c
2021-09-10 21:13:06 +00:00
* by OutputPage as part of the HTML `<head>`.
*
* @ingroup ResourceLoader
* @internal
*/
class UserOptionsModule extends Module {
/** @inheritDoc */
protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
/**
* @param Context $context
* @return string JavaScript code
*/
public function getScript( Context $context ) {
$user = $context->getUserObj();
resourceloader: Merge 'user.tokens' module into 'user.options' For back-compat, keep 'user.tokens' as deprecated alias to 'user.options' for one release cycle (to be removed in MW 1.36). == user.options == As before, 'user.options' arrives immediately on every page view, embedded in the HTML. It has an async dependency on 'user.defaults', which is not downloaded until there is a known demand on 'user.options'. Once that arrives, the implementation closure of 'user.options' will execute, and the module becomes 'ready'. == user.options "empty" == Before this change, UserOptionsModule used isKnownEmpty to consider the module "empty" for logged-out users (as well as for logged-in users that haven't yet set any preferences). This was a mistake. It is invalid in ResourceLoader to mark a module as "empty" if that module has dependencies (see also T191596 and c3f200849). This broke the state machine. The impact was minimal given that it is unlikely for features to read keys from mw.user.options for logged-out users, which if attempted would have simply returned null for all keys. == New HTML == The user.options module is always embedded (never empty), and always has a dependency on user.defaults. == Cached HTML == The cached HTML for anons sets user.options's state to ready without waiting for any dependency. Per the above, this was already causing subtle bugs with mw.user.options.get() likely returning null for anons, which was fairly innocent. For tokens a bottom value of null would be problematic as the default for tokens must be "+\" instead. To make sure that is available for cached page views, set this directly in mediawiki.base.js. The cached HTML does contain an implement call for 'user.tokens' that contains the same defaults, but new code will not be asking for or waiting for user.tokens, so that is unused. Bug: T235457 Change-Id: I51e01d6fa604578cd2906337bde5a4760633c027
2020-03-13 22:46:14 +00:00
$tokens = [
// Replacement is tricky - T287542
'patrolToken' => $user->getEditToken( 'patrol' ),
'watchToken' => $user->getEditToken( 'watch' ),
'csrfToken' => $user->getEditToken(),
resourceloader: Merge 'user.tokens' module into 'user.options' For back-compat, keep 'user.tokens' as deprecated alias to 'user.options' for one release cycle (to be removed in MW 1.36). == user.options == As before, 'user.options' arrives immediately on every page view, embedded in the HTML. It has an async dependency on 'user.defaults', which is not downloaded until there is a known demand on 'user.options'. Once that arrives, the implementation closure of 'user.options' will execute, and the module becomes 'ready'. == user.options "empty" == Before this change, UserOptionsModule used isKnownEmpty to consider the module "empty" for logged-out users (as well as for logged-in users that haven't yet set any preferences). This was a mistake. It is invalid in ResourceLoader to mark a module as "empty" if that module has dependencies (see also T191596 and c3f200849). This broke the state machine. The impact was minimal given that it is unlikely for features to read keys from mw.user.options for logged-out users, which if attempted would have simply returned null for all keys. == New HTML == The user.options module is always embedded (never empty), and always has a dependency on user.defaults. == Cached HTML == The cached HTML for anons sets user.options's state to ready without waiting for any dependency. Per the above, this was already causing subtle bugs with mw.user.options.get() likely returning null for anons, which was fairly innocent. For tokens a bottom value of null would be problematic as the default for tokens must be "+\" instead. To make sure that is available for cached page views, set this directly in mediawiki.base.js. The cached HTML does contain an implement call for 'user.tokens' that contains the same defaults, but new code will not be asking for or waiting for user.tokens, so that is unused. Bug: T235457 Change-Id: I51e01d6fa604578cd2906337bde5a4760633c027
2020-03-13 22:46:14 +00:00
];
$script = 'mw.user.tokens.set(' . $context->encodeJson( $tokens ) . ');' . "\n";
resourceloader: Merge 'user.tokens' module into 'user.options' For back-compat, keep 'user.tokens' as deprecated alias to 'user.options' for one release cycle (to be removed in MW 1.36). == user.options == As before, 'user.options' arrives immediately on every page view, embedded in the HTML. It has an async dependency on 'user.defaults', which is not downloaded until there is a known demand on 'user.options'. Once that arrives, the implementation closure of 'user.options' will execute, and the module becomes 'ready'. == user.options "empty" == Before this change, UserOptionsModule used isKnownEmpty to consider the module "empty" for logged-out users (as well as for logged-in users that haven't yet set any preferences). This was a mistake. It is invalid in ResourceLoader to mark a module as "empty" if that module has dependencies (see also T191596 and c3f200849). This broke the state machine. The impact was minimal given that it is unlikely for features to read keys from mw.user.options for logged-out users, which if attempted would have simply returned null for all keys. == New HTML == The user.options module is always embedded (never empty), and always has a dependency on user.defaults. == Cached HTML == The cached HTML for anons sets user.options's state to ready without waiting for any dependency. Per the above, this was already causing subtle bugs with mw.user.options.get() likely returning null for anons, which was fairly innocent. For tokens a bottom value of null would be problematic as the default for tokens must be "+\" instead. To make sure that is available for cached page views, set this directly in mediawiki.base.js. The cached HTML does contain an implement call for 'user.tokens' that contains the same defaults, but new code will not be asking for or waiting for user.tokens, so that is unused. Bug: T235457 Change-Id: I51e01d6fa604578cd2906337bde5a4760633c027
2020-03-13 22:46:14 +00:00
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
resourceloader: Bundle `user.defaults` as part of `mediawiki.base` == Background == The `user.options` module is private, and thus has to be embedded in the page HTML. This data is quite large. For example, on enwiki the finalized mw.user.options object is about 3KB serialized/compressed (7KB uncompressed). The `user.defaults` module is an implementation detail of `user.options`, and was created to accomplish mainly two things: * Save significant data transfers by allowing it to be cached client-side without being part of the article. * Ensure consistency between articles and allow faster deployment of changes, by not being part of the cacheable article HTML. All our pageviews already load `user.defaults`, as a dependency of the popular `mediawiki.api` and `mediawiki.user` modules. These are used by `mediawiki.page.ready` (queued on all pages), and on Wikipedia these are also loaded on all pages by ULS, VisualEditor, EventLogging, and more. As such, in practice, bundling "user.defaults" with "mediawiki.base" will not cause the data to be loaded more often than before. == What == * Add virtual "user.json" package file with the same data that was previously exported by ResourceLoaderUserDefaultsModule, and pass it to mw.user.options.set() from base module's entry point. An alternative way would be to use a "user.js" file, which would return a generated "mw.user.options.set()" expression. I went for exporting it as JSON for improved maintainability (reducing the amount of JS code written in PHP), and because it performs slightly better. The JS file would implicitly come with a file closure (tiny bit more bytes), and would then be lazy executed (tiny bit more time). The chosen approach allows the browser to compile the JSON off-the-main-thread ahead of time while the module response downloads. Then when the module executes, we can reference the JSON object and use it directly. * Update internal dependency from `user.options`. * Remove `user.defaults` module without deprecation. It is an internal module with no direct use anywhere in Git (Codeseach), and no use anywhere on-wiki (Global Search). Change-Id: Id3916f94f75078808951863dea2b3a9c71b0e30c
2021-09-10 21:13:06 +00:00
// Optimisation: Exclude the defaults, which we load separately and allow the browser
// to cache across page views. The defaults are loaded before this code executes,
// as part of the "mediawiki.base" module.
$options = $userOptionsLookup->getOptions( $user, UserOptionsLookup::EXCLUDE_DEFAULTS );
resourceloader: Bundle `user.defaults` as part of `mediawiki.base` == Background == The `user.options` module is private, and thus has to be embedded in the page HTML. This data is quite large. For example, on enwiki the finalized mw.user.options object is about 3KB serialized/compressed (7KB uncompressed). The `user.defaults` module is an implementation detail of `user.options`, and was created to accomplish mainly two things: * Save significant data transfers by allowing it to be cached client-side without being part of the article. * Ensure consistency between articles and allow faster deployment of changes, by not being part of the cacheable article HTML. All our pageviews already load `user.defaults`, as a dependency of the popular `mediawiki.api` and `mediawiki.user` modules. These are used by `mediawiki.page.ready` (queued on all pages), and on Wikipedia these are also loaded on all pages by ULS, VisualEditor, EventLogging, and more. As such, in practice, bundling "user.defaults" with "mediawiki.base" will not cause the data to be loaded more often than before. == What == * Add virtual "user.json" package file with the same data that was previously exported by ResourceLoaderUserDefaultsModule, and pass it to mw.user.options.set() from base module's entry point. An alternative way would be to use a "user.js" file, which would return a generated "mw.user.options.set()" expression. I went for exporting it as JSON for improved maintainability (reducing the amount of JS code written in PHP), and because it performs slightly better. The JS file would implicitly come with a file closure (tiny bit more bytes), and would then be lazy executed (tiny bit more time). The chosen approach allows the browser to compile the JSON off-the-main-thread ahead of time while the module response downloads. Then when the module executes, we can reference the JSON object and use it directly. * Update internal dependency from `user.options`. * Remove `user.defaults` module without deprecation. It is an internal module with no direct use anywhere in Git (Codeseach), and no use anywhere on-wiki (Global Search). Change-Id: Id3916f94f75078808951863dea2b3a9c71b0e30c
2021-09-10 21:13:06 +00:00
$keysToExclude = [];
$this->getHookRunner()->onResourceLoaderExcludeUserOptions( $keysToExclude, $context );
foreach ( $keysToExclude as $excludedKey ) {
unset( $options[ $excludedKey ] );
}
resourceloader: Merge 'user.tokens' module into 'user.options' For back-compat, keep 'user.tokens' as deprecated alias to 'user.options' for one release cycle (to be removed in MW 1.36). == user.options == As before, 'user.options' arrives immediately on every page view, embedded in the HTML. It has an async dependency on 'user.defaults', which is not downloaded until there is a known demand on 'user.options'. Once that arrives, the implementation closure of 'user.options' will execute, and the module becomes 'ready'. == user.options "empty" == Before this change, UserOptionsModule used isKnownEmpty to consider the module "empty" for logged-out users (as well as for logged-in users that haven't yet set any preferences). This was a mistake. It is invalid in ResourceLoader to mark a module as "empty" if that module has dependencies (see also T191596 and c3f200849). This broke the state machine. The impact was minimal given that it is unlikely for features to read keys from mw.user.options for logged-out users, which if attempted would have simply returned null for all keys. == New HTML == The user.options module is always embedded (never empty), and always has a dependency on user.defaults. == Cached HTML == The cached HTML for anons sets user.options's state to ready without waiting for any dependency. Per the above, this was already causing subtle bugs with mw.user.options.get() likely returning null for anons, which was fairly innocent. For tokens a bottom value of null would be problematic as the default for tokens must be "+\" instead. To make sure that is available for cached page views, set this directly in mediawiki.base.js. The cached HTML does contain an implement call for 'user.tokens' that contains the same defaults, but new code will not be asking for or waiting for user.tokens, so that is unused. Bug: T235457 Change-Id: I51e01d6fa604578cd2906337bde5a4760633c027
2020-03-13 22:46:14 +00:00
// Optimisation: Only output this function call if the user has non-default settings.
if ( $options ) {
$script .= 'mw.user.options.set(' . $context->encodeJson( $options ) . ');' . "\n";
resourceloader: Merge 'user.tokens' module into 'user.options' For back-compat, keep 'user.tokens' as deprecated alias to 'user.options' for one release cycle (to be removed in MW 1.36). == user.options == As before, 'user.options' arrives immediately on every page view, embedded in the HTML. It has an async dependency on 'user.defaults', which is not downloaded until there is a known demand on 'user.options'. Once that arrives, the implementation closure of 'user.options' will execute, and the module becomes 'ready'. == user.options "empty" == Before this change, UserOptionsModule used isKnownEmpty to consider the module "empty" for logged-out users (as well as for logged-in users that haven't yet set any preferences). This was a mistake. It is invalid in ResourceLoader to mark a module as "empty" if that module has dependencies (see also T191596 and c3f200849). This broke the state machine. The impact was minimal given that it is unlikely for features to read keys from mw.user.options for logged-out users, which if attempted would have simply returned null for all keys. == New HTML == The user.options module is always embedded (never empty), and always has a dependency on user.defaults. == Cached HTML == The cached HTML for anons sets user.options's state to ready without waiting for any dependency. Per the above, this was already causing subtle bugs with mw.user.options.get() likely returning null for anons, which was fairly innocent. For tokens a bottom value of null would be problematic as the default for tokens must be "+\" instead. To make sure that is available for cached page views, set this directly in mediawiki.base.js. The cached HTML does contain an implement call for 'user.tokens' that contains the same defaults, but new code will not be asking for or waiting for user.tokens, so that is unused. Bug: T235457 Change-Id: I51e01d6fa604578cd2906337bde5a4760633c027
2020-03-13 22:46:14 +00:00
}
resourceloader: Restore minification of embedded 'user.options' == Why and what == It is important that we don't cache the result of minifying the `user.options` blob, because it varies on every page (details below). But, it is okay to minify it. Today, we don't minify it because the only content of this blob is one line of JSON, and that JSON is already generated without spaces. I would like to start minifying it so that: 1. The "mw.loader.implement" wrapper will get minified. Right now we maintain a copy of the wrapper that is minified by hand. In the next patch, I will remove this, which will introduce whitespace for "user.options" unless we enable the minifier. 2. We can remove more complexity and state internally without worrying about whether it will still be minified. 3. We can make the output even smaller by not having to preserve the `/*nomin*/` instruction behind. This instruction is used today mainly for cases where minification might break the output, so it is important to preserve in case it is concatenated and passed to the minifier a second time later. But, for user options we don't need this protection, and so we can save a few bytes by removing this instruction at the same time. == Background == Act 1 In 2014, with task T84960, we determined that caching of `user.tokens` minification is problematic for system stability and also not useful. * This module contains security tokens that are different for every pageview and for every user. This means every time we generate a web response, we have different tokens, and thus generate different <script> content, and thus there is no use of caching the result, because we would never use it. The next time we get a different script, and will have to minify it again. That's okay, it's small and takes no time at all to minify. * If we stored it in the cache, it would not only be useless, it would also compromise the effectiveness of the php-apcu cache for all other parts of MediaWiki, because when APCu is full, it will have to delete unrelated caches to make space, thus causing more calculations to be repeated in other places. In commit 6fa48939 (I6016e4b0) we simply changed the script generation to disable caching when minifying the private 'user.tokens' module, which solved the task. Act 2 In 2015, with commit b7eb243d92 (Id6f514206), the minification logic was changed from "per response" to "per module within response", and as part of that the logic was also generalised from being just for `user.tokens` to be for "private modules", which is essentially the same (since user.tokens is the most common private module), but was preparation for a few other things: * Some extensions (like AdvancedSearch) also create their own private modules and thus benefit from this automatically. * In later years we would add support for previewing user scripts and gadgets, which turns a public module temporarily into a private one to be able to execute it with the previewed script content. These also don't need to be cached, and this correctly disabled caching for those. * We have "user.options", which is similar to "user.tokens", but does not change on every page view. It does not need to be cached because it is so small that is about as fast to just minify it than to go through the cost of hashing, keying and querying the cache. * We have merged `user.tokens` into `user.options`. Act 3 Then, with commit ca30efa30 (Ic1d802ee20) this was automation was removed in favour of the FILTER_NOMIN instruction which disabled both caching *and* minification. The was accepted because we realized that we don't need minification for the "user.options" blob because it is just one line of JSON, and the JSON is already generated without whitespace. Change-Id: I6d125fc89d8964325ec068a0746b00810e155858
2021-10-09 00:48:48 +00:00
return $script;
}
/**
* @return bool
*/
public function supportsURLLoading() {
return false;
}
/**
* @return string
*/
public function getGroup() {
return self::GROUP_PRIVATE;
}
}