Commit graph

36 commits

Author SHA1 Message Date
Timo Tijhof
008b6528b6 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-09-15 18:13:09 -07:00
Timo Tijhof
7c6713b4d9 resourceloader: Make getVersionHash() final
This is in preparation for making all version hashes the empty string
in debug mode, which will make things faster to work with, but also
helps solve T235672 in an easy way client-side without having to
add additional complexity to the mw.loader client that is specific
to debug mode.

Bug: T235672
Change-Id: I43204f22dfbcf5d236b35adc5b35df3da8021bad
2021-09-15 18:10:49 -07:00
Timo Tijhof
b7c70526a9 resourceloader: Add missing Module->setConfig() calls in tests and installer
There is a fallback in Module->getConfig() to the global services
container. This is not meant to be used in practice, but there were
two places where this was missing: WebInstallerOutput, and various
PHPUnit tests.

* Add missing setConfig() to WebInstallerOutput.

* Add missing setConfig() to various tests. Most tests were already
  doing this correctly and using the standard mock from
  ResourceLoaderTestCase. Upon switching the last few tests as well,
  I uncovered various errors due to the mock missing some settings
  that the tested code uses, so these have been added now to
  ResourceLoaderTestCase.

Bug: T277728
Change-Id: I44f16ec4e00423fb6f641e58fffc1d40e4571f01
2021-06-13 21:20:58 +00:00
Daimona Eaytoy
535d7abf59 phpunit: Mass-replace setMethods with onlyMethods and adjust
Ended up using
  grep -Prl '\->setMethods\(' . | xargs sed -r -i 's/setMethods\(/onlyMethods\(/g'

special-casing setMethods( null ) -> onlyMethods( [] )

and then manual fix of failing test (from PS2 onwards).

Bug: T278010
Change-Id: I012dca7ae774bb430c1c44d50991ba0b633353f1
2021-04-16 20:15:00 +02:00
Roan Kattouw
b267f7aa90 resourceloader: Allow modules to mark themselves as ES6-only
Modules that set "es6": true in their module definition will error when
a non-ES6 client tries to load them.

To detect ES6 support, this looks for native Promise support,
RegExp.prototype.flags, and non-BMP characters in variable names. All
browsers that lack full ES6 support fail at least one of those checks.

To flag modules as requiring ES6, this adds a ! to the end of their
version string. This takes up much less space than adding another
register() parameter (which would have to be at the end). It's hacky,
but we expect this feature to be relatively temporary, until we require
ES6 for running any JS at all (probably in about a year).

For distinguishing different types of errors thrown from
sortDependencies(), use e.name. We can't subclass Error properly because
that requires ES6.

Bug: T272104
Change-Id: I45670c910ff12eb422ae54c9fcf372e45c7b2bf1
2021-03-04 14:53:36 -05:00
Reedy
229b2c15e8 Fix a plethora of class and function call case mismatches
Bug: T231412
Change-Id: I597a25de3294a6673424f30475760280ef209a8a
2020-05-26 14:14:46 +01:00
Brad Jorsch
036cde7a04 resourceloader: Move site-level mw.config from startup to mediawiki.base
This data isn't needed for startup, and we can shave off a few K from
startup by moving it to mediawiki.base instead.

It was requested that this be done as a "package file", which
necessitated some other minor structural changes to mediawiki.base as
well.

Bug: T235350
Change-Id: I525a5203533089d5a542f83a847be58a10cb6319
2020-02-07 19:44:57 +00:00
Fomafix
2fc229116d resourceloader: Add $context to static functions in ResourceLoader
This change allows to use the context in the functions.

The following internal static functions from ResourceLoader get now a
reference to the ResourceLoaderContext object:
* makeLoaderImplementScript
* makeLoaderStateScript
* makeLoaderRegisterScript
* makeLoaderSourcesScript

ResouceLoader::encodeJsonForScript is duplicated to
ResourceLoaderContext::encodeJson loading the debug mode from context.

ResourceLoader::encodeJsonForScript is kept for other usages without
context.

The debug mode is loaded from $context->getDebug() instead of from
ResourceLoader::inDebugMode(). This does not support to enable the debug
mode by setting the cookie 'resourceLoaderDebug' or the configuration
variable wgResourceLoaderDebug. Only the URL parameter debug=true
enables the debug mode. This should be sufficient for the subsequent
ResourceLoader requests. The tests don't need the global variable
wgResourceLoaderDebug anymore. The initial ResourceLoader context in
OutputPage still uses ResourceLoader::inDebugMode() with cookie and
global configuration variable.

This change adds the parameter $context with a ResourceLoaderContext
object to ResourceLoaderModule::getDeprecationInformation and deprecates
omitting the parameter. Ifa1a3bb56b731b83864022a358916c6aca5d7c10
updates this in extension ExtJSBase.

Bug: T229311
Change-Id: I5341f18625209446a6d006f60244990f65530319
2019-09-27 18:35:55 +02:00
Timo Tijhof
9f516f1d3b resourceloader: Reduce width of module hash from 7 chars to 5
In a nut shell:

* We very often (52% of modules on enwiki) pad the hash with a zero,
  which means the amount of bits we currently compute already fit in
  6 characters already for most modules. For some modules (3%) we
  even padded two zeroes.

* For the (now documented) use cases, the space of 78 Giga
  (78 billion, or 78 milliard) seems more than we need. The space of
  60 million should be enough.

  This follows-up dfd046412f from 2016, which previously shortened the hash
  down from 8 chars of base 64 (or 12 chars of hex) to 7 chars of base 32.
  Before that change, the space was 281 Tera (64^8, or 16^12).

For more details see the added inline comment for ResourceLoader::makeHash,
and also the data at <https://phabricator.wikimedia.org/T229245>.

Bug: T229245
Change-Id: I9ad11772a33b3a44cb625275b1d7353e1393ee49
2019-09-02 01:25:48 +00:00
Amir Sarabadani
c59b0ed189 resourceloader: Map group names to integers internally
This makes startup response smaller.

Change-Id: Id0ae3cd7cd22163f42b92b629c38b0a806d3ca50
2019-08-24 20:36:37 +01:00
Timo Tijhof
23d066618d resourceloader: Replace some Xml::encodeJs calls with RL's own encodeJson
Bug: T32956
Change-Id: I614fe0e80ff308b857639a27d7772f969899b468
2019-07-16 01:15:32 +01:00
Timo Tijhof
d6dd6e4d72 resourceloader: Remove use of object registering in test suites
This was done as a "clever" shortcut to make sure tests a little
but shorter, but also made them less consistent with normal code.

Remove this in favour of 'class' or 'factory' options as needed.
Also remove a bunch of unneeded register() calls.

The tests cover everything affected by this change.

Side fix - isFileModule should reject modules with 'factory'
the same way it rejected raw objects and non-FileModule 'class'
cases already. This is now covered by tests as well.

Bug: T222637
Change-Id: I3996317dbcd780cc6e0f82c84e769c08a3fc42bb
2019-07-12 01:17:44 +00:00
Timo Tijhof
c554ee8e64 resourceloader: Remove support for raw modules
Being a raw module means that when it is requested from load.php with
"only=scripts" set, then the output is *not* wrapped in an
'mw.loader.implement' closure *and* there no 'mw.loader.state()' appendix.
Instead, it is served "raw".

Before 2018, the modules 'mediawiki' and 'jquery' were raw modules.
They were needed before the client could define 'mw.loader.implement', and
could never be valid dependencies. Module 'mediawiki' merged to 'startup',
and 'jquery' became a regular module (T192623). Based on the architecture
of modules being deliverable bundles, it doesn't make sense for there to
ever be raw modules again. Anything that 'startup' needs should be bundled
with it. Anything else is a regular module.

On top of that, we never actually needed this feature because specifying
the 'only=scripts' and 'raw=1' parameters does the same thing.

The only special bit about marking modules (not requests) as "raw" was that
it allowed the client to forget to specify "raw=1" and the server would
automatically omit the 'mw.loader.state()' appendix based on whether the
module is marked as raw. As of Ie4564ec8e26ad53f2, the two remaining use
cases for raw responses now specify the 'raw=1' request parameter, and we
can get rid of the "raw module" feature and all the complexity around it.

== Startup module

In the startup module there was an interesting use of isRaw() that has
little to do with the above. The "ATTENTION" warning there applies to the
startup module only, not raw modules in general. This is now fixed by
explicitly checking for StartupModule.

Above that warning, it talked about saving bytes, which was an optimisation
given that "raw" modules don't communicate with mw.loader, they also don't
need to be registered there because even if mw.loader would try to load
them, the server would never inform mw.loader about the module having
arrived. There are now no longer any such modules.

Bug: T201483
Change-Id: I8839036e7b2b76919b6cd3aa42ccfde4d1247899
2019-06-27 00:08:14 +00:00
Timo Tijhof
14547fd9dc resourceloader: Skip modules with circular deps in tree optimiser
Either the server needs to omit these from the registry with
state=error output to the client (and server-side error logging),
or it needs to detect them, and transport them unchanged, so that
the existing client-side logic can handle it.

This patch does the latter.

Without the source code change in this patch, the added test case
fails due to "top" and "middle1" then being registered with
an empty array as dependencies.

Bug: T223402
Change-Id: I57502d7c4e434de4737759aed325dd4200ca89bf
2019-06-13 20:11:44 +00:00
Timo Tijhof
b2d120ae32 resourceloader: Add tests for StartUpModule dep tree optimisation
Bug: T223402
Change-Id: I0450c052852068f3c78312f48a42645a8b77bc5c
2019-05-22 21:53:13 +01:00
Timo Tijhof
5c90f8d137 resourceloader: Move registering of custom sources to ServiceWiring
Remove the now-redundant mocking of this configuration from various
test classes.

Bug: T32956
Change-Id: If90a10a76b8289c4ba707382ac915441c17d831f
2019-03-29 01:46:34 +00:00
Thiemo Kreuz
c3dfa88966 Add missing empty lines between methods
This might hint at an edge-case in the PHP CodeSniffer sniff that should
detect if methods are separated by a single empty line. Feel free to
investigate. I, personally, can't invest more time in this than
suggesting this quick fix.

Change-Id: Ib3c60eac76f255b4fe929f7933de256222716576
2019-01-15 19:14:35 +00:00
Timo Tijhof
b70ab40839 resourceloader: Use 'enableModuleContentVersion' for startup module
This significantly simplifies the getVersionHash implementation for
StartupModule, and fixes a couple of bugs.

Previously, the startup module's E-Tag was determined by the
'getDefinitionSummary' method, which combined the E-Tag values
from all registered modules, plus what we thought is all information
used by 'getScript' (config vars, embedded script files, list
of base modules, ...)

However, this were various things part of the manifest that it
forgot about, including:

* Changes to the list of dependencies of a module.
* Changes to the name of module.
* Changes to the cache group of module.
* Adding or removing a foreign module source (mw.loader.addSource).

These are all quite rare, and when they do change, they usually
also involve a change that *was* tracked already. But, sometimes
they don't and that's when bugs happened.

Instead of the tracking array of getDefinitionSummary, we now
use the 'enableModuleContentVersion' option for StartupModule,
which simply calls the actual getScript() method and hashes that.

Of note: When an exception happens with the version computation of
any individual module, we catch it, log it, and continue with the
rest. Previously, the first time such error was discovered at
run-time would be in the getCombinedVersion() call from
StartupModule::getAllModuleHashes(). That public getCombinedVersion()
method of ResourceLoader had the benefit of also outputting details
of that exception in the HTTP response output. In order to keep that
behaviour, I made outputErrorAndLog() public so that StartupModule
can call it directly now. This is covered by
ResourceLoaderTest::testMakeModuleResponseStartupError.

Bug: T201686
Change-Id: I8e8d3a2cd2ccd68d2d78e988bcdd0d77fbcbf1d4
2018-08-31 00:23:23 +00:00
Timo Tijhof
ea7101e6e4 resourceloader: Add coverage for StartupModule::getAllModuleHashes
Change-Id: I94551a938cd93152eb1bb0a4748e1c0155539c4c
2018-07-07 23:49:20 -07:00
Timo Tijhof
afffa55567 resourceloader: Add coverage for StartupModule::getDefinitionSummary
Change-Id: I913c8ecd5f51db851ef408c4281cdd75a0e0b239
2018-06-24 02:59:44 +01:00
Bartosz Dziewoński
485f66f174 Use PHP 7 '??' operator instead of '?:' with 'isset()' where convenient
Find: /isset\(\s*([^()]+?)\s*\)\s*\?\s*\1\s*:\s*/
Replace with: '\1 ?? '

(Everywhere except includes/PHPVersionCheck.php)
(Then, manually fix some line length and indentation issues)

Then manually reviewed the replacements for cases where confusing
operator precedence would result in incorrect results
(fixing those in I478db046a1cc162c6767003ce45c9b56270f3372).

Change-Id: I33b421c8cb11cdd4ce896488c9ff5313f03a38cf
2018-05-30 18:06:13 -07:00
Timo Tijhof
ee8a5e488e resourceloader: Apply safemode to startup module registry
This effectively applies safemode to the mw.loader client,
without the client itself needing specific knowledge of safemode.

Test Plan:
* Unchanged: When viewing a page in safemode, the 'user' and
  'site' modules are still not queued by OutputPage.
* New: mw.loader.getState('site'), previously would yield
  'registered', but will now yield null.
* New: mw.loader.load('site'), previously loaded the module,
  it now logs a dependency warning for unknown module, like for
  any other unknown module.
* New: mw.loader.using('site'), previously resolved, it is now
  rejected.

Bug: T185303
Change-Id: I672e3891c8e1b3c2d13655fa134d0f1d031b8247
2018-05-19 22:14:33 +02:00
Umherirrender
45da581551 Use ::class to resolve class names in tests
This helps to find renamed or misspelled classes earlier.
Phan will check the class names

Change-Id: Ie541a7baae10ab6f5c13f95ac2ff6598b8f8950c
2018-01-26 22:49:13 +01:00
Timo Tijhof
e4a52de025 resourceloader: Avoid mw-exception log spam from PHPUnit tests
Bug: T50002
Change-Id: Ida18f271883535eca2a1d26ee34120190914d87a
2017-07-15 00:37:53 +00:00
Timo Tijhof
16adb3fe31 resourceloader: Improve code coverage
* Missing cases for StartupModule::getModuleRegistrations
  (now 100% covered)
  - Raw modules are omitted from the manifest.
    E.g. The base modules ('jquery', 'mediawiki') are raw modules
    that we don't register client side (they can't load themselves).
  - Exceptions from getVersionHash() are caught.
  - Oversized versions are re-hashed.

* Missing cases for ResourceLoader::makeLoaderRegisterScript.
  (now 100% covered)

* Missing cases for ResourceLoader::getModule.
  (now 100% covered)

Change-Id: If9717a48195fc6ae776da5d0e86f323d7f60426d
2017-04-05 18:19:48 -07:00
Timo Tijhof
45bec76755 resourceloader: Don't cache stale responses in mw.loader.store
Follows-up 6fa1e56. This is already fixed for http caches by
shortening the Cache-Control max-age in case of a version mismatch.

However the client still cached it blindly in mw.loader.store.
Resolve this by communicating to the client what version of the module
was exported. The client can then compare this version to the version
it originally requested and decide not to cache it.

Adopt the module key format (name@version) from mw.loader.store
in mw.loader.implement() as well.

Bug: T117587
Change-Id: I1a7c44d0222893afefac20bef507bdd1a1a87ecd
2016-10-10 19:48:25 +00:00
Timo Tijhof
7dbad8562c resourceloader: Improve coverage in ResourceLoaderTest.php
* Fix signature of makeLoaderSourcesScript() to match
  the change in behaviour since e103ba265.

* Consistently order providers before the test.

* Simplify testRegisterValid() and remove needless @depends.

* Remove unused private method stripNoflip().

Coverage:

* Expand test coverage for register().

* Add tests for getModuleNames().

* Add tests for getModule().

* Expand test coverage for addSource().
  (case of invalid array)

* Expand test coverage for makeLoaderImplementScript().
  (case of unwrapped user script, and case of invalid scripts)

* Add tests for makeLoaderSourcesScript().

Change-Id: Ibca3e486fcd3664f171f135327a0f340ee6da9ee
2016-08-24 18:56:20 -07:00
Ori Livneh
568e3e43d9 resourceloader: Update expected length of module version hash
It's 7, not 8, as of I1e4fb08d179.

Change-Id: I882bf7075292ff849f2bc3af61217f7e38a4ec5f
2016-07-14 21:48:06 +00:00
Ori Livneh
dfd046412f resourceloader: Replace SHA1 with 32-bit FNV-1 as hash function
SHA-1 is not secure enough to be used as a cryptographic hash function, and its
implementation in JavaScript is too long and too slow for it to be a good
general-purpose hash function. And we currently throw away most of the work:
SHA-1 produces 160-bit hash values, of which we keep 48.

Although the JavaScript implementation is not exported, SHA-1 is a well-known
hash function, and I'm willing to bet that sooner or later someone will move to
make it accessible to other modules, at which point usage will start to spread.

For ResourceLoader, the qualities we're looking for in a hash function are:

* Already implemented in PHP
* Easy to implement in JavaScript
* Fast
* Collision-resistant

The requirement that hashes be cheap to compute in JavaScript narrows the field
to 32-bit hash functions, because in JavaScript bitwise operators treat their
operands as 32 bits, and arithmetic uses double-precision floats, which have a
total precision of 53 bits. It's possible to work around these limitations, but
it's a lot of extra work.

The best match I found is the 32-bit variant of FNV-1, which is available in
PHP as of version 5.4 (as 'fnv1a32'). The fnv132 JavaScript function is
around ten times faster and eight times shorter than sha1.

Change-Id: I1e4fb08d17948538d96f241b2464d594fdc14578
2016-07-05 21:14:03 +00:00
Kunal Mehta
6e9b4f0e9c Convert all array() syntax to []
Per wikitech-l consensus:
 https://lists.wikimedia.org/pipermail/wikitech-l/2016-February/084821.html

Notes:
* Disabled CallTimePassByReference due to false positives (T127163)

Change-Id: I2c8ce713ce6600a0bb7bf67537c87044c7a45c4b
2016-02-17 01:33:00 -08:00
Timo Tijhof
5d5b269e0e resourceloader: Migrate from msg_resource table to object cache
MessageBlobStore class:
* Make logger aware.
* Log an error if json encoding fails.
* Stop using the DB table. WANObjectCache supports everything we need:
  - Batch retrieval.
  - Invalidate keys with wildcard selects or cascading check keys.
* Update tests slightly since the actual update now happens on-demand as
  part of get() instead of within updateMessage().

ResourceLoader class:
* Remove all interaction with the msg_resource table. Remove db table later.
* Refactor code to use a hash of the blob instead of a timestamp.
  Timestamps are unreliable and roll over too frequently for message blob store
  because there is no authoritative source. The timestamps were inferred based on
  when a change is observed. Message overrides from the local wiki have an
  explicit update event when the page is edited. All other messages, such as
  from MediaWiki core and extensions using LocalisationCache, have a single
  timestamp for all messages which rolls over every time the cache is rebuilt.
  A hash is deterministic, and won't cause needless invalidation (T102578).
* Remove redundant pre-fetching in makeModuleResponse().
  This is already done by preloadModuleInfo() in respond().
* Don't bother storing and retreiving empty "{}" objects.
  Instead, detect whether a module's message list is empty at runtime.

ResourceLoaderModule class:
* Make logger aware.
* Log if a module's message blob was not preloaded.

cleanupRemovedModules:
* Now that blobs have a TTL, there's no need to prune old entries.

Bug: T113092
Bug: T92357
Change-Id: Id8c26f41a82597e34013f95294cdc3971a4f52ae
2015-12-08 14:02:38 +00:00
Timo Tijhof
41b0e953b8 resourceloader: Centralise expected version hash for blank test modules
To avoid repeating this too much and make it easier to update when
underlying components incorporated into the version hash change.

Change-Id: I986988c07f5f888046b1d93c7e72f2e9585893cf
2015-11-17 04:30:04 +00:00
Timo Tijhof
b00cd0b12d resourceloader: Move startup code from PHP to startup.js
The code is easier to maintain in an actual JavaScript file.
Especially with how variables were declared and concatenated in
a different order.

Change-Id: I758acb78de1cdf2128e81c86f992807ef0dbf444
2015-07-27 16:41:16 -07:00
Timo Tijhof
2581453fac 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-18 20:39:38 +00:00
Timo Tijhof
f37cee996e 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-05-19 22:28:17 +00:00
Adrian Heine
a600ea6705 Fix typo references in comments to ResourceLoaderStartUpModule
Change-Id: I73436f76d0b42f6f9c0b3277a1b07fb6c2d7eb81
2015-01-08 18:09:10 +00:00
Renamed from tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php (Browse further)