wiki.techinc.nl/includes/resourceloader/ResourceLoaderUserOptionsModule.php
Timo Tijhof bed3a59dad 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-11-08 19:23:25 +00:00

85 lines
2.4 KiB
PHP

<?php
use MediaWiki\MediaWikiServices;
use MediaWiki\User\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
*/
/**
* Module for per-user private data that is transmitted on all HTML web responses.
*
* It is send to the browser from the HTML <head>. See OutputPage.
*
* @ingroup ResourceLoader
* @internal
*/
class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
protected $targets = [ 'desktop', 'mobile' ];
/**
* @param ResourceLoaderContext|null $context
* @return string[] List of module names
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
return [ 'user.defaults' ];
}
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
$user = $context->getUserObj();
$tokens = [
'patrolToken' => $user->getEditToken( 'patrol' ),
'watchToken' => $user->getEditToken( 'watch' ),
'csrfToken' => $user->getEditToken(),
];
$script = 'mw.user.tokens.set(' . $context->encodeJson( $tokens ) . ');';
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
$options = $userOptionsLookup->getOptions( $user, UserOptionsLookup::EXCLUDE_DEFAULTS );
// Optimisation: Only output this function call if the user has non-default settings.
if ( $options ) {
$script .= 'mw.user.options.set(' . $context->encodeJson( $options ) . ');';
}
return $script;
}
/**
* @return bool
*/
public function supportsURLLoading() {
return false;
}
/**
* @return string
*/
public function getGroup() {
return 'private';
}
}