This commit implements step 4 and step 5 of the plan outlined at T192623.
Before this task began, the typical JavaScript execution flow was:
* HTML triggers request for startup module (js req 1).
* Startup module contains registry, site config, and triggers
a request for the base modules (js req 2).
* After the base modules arrive (which define jQuery and mw.loader),
the startup module invokes a callback that processes RLQ,
which is what will request modules for this page (js req 3).
In past weeks, we have:
* Made mediawiki.js independent of jQuery.
* Spun off 'mediawiki.base' from mediawiki.js – for everything
that wasn't needed for defining `mw.loader`.
* Moved mediawiki.js from the base module request to being embedded
as part of startup.js.
The concept of dependencies is native to ResourceLoader, and thanks to the
use of closures in mw.loader.implement() responses, we can download any
number of interdependant modules in a single request (or parallel requests).
Then, when a response arrives, mw.loader takes care to pause or resume
execution as-needed. It is normal for ResourceLoader to batch several modules
together, including their dependencies.
As such, we can eliminate one of the two roundtrips required before a
page can request modules. Specifically, we can eliminate "js req 2" (above),
by making the two remaining base modules ("jquery" and "mediawiki.base") an
implied dependency for all other modules, which ResourceLoader will naturally
fetch and execute in the right order as part of the batch request.
Bug: T192623
Change-Id: I17cd13dffebd6ae476044d8d038dc3974a1fa176
If the jQuery promise returned by mw.loader.using() is rejected
synchronously (e.g. because one of the modules has a dependency on
non-existent module), the function passed to fail() also executes
synchronously. Note that this is true also with jQuery 3.
The exception it throws was caught by the catch() below, which
resulted in start() being called twice, which resulted in QUnit
throwing 'Uncaught Error: Called start() outside of a test
context too many times' rather than actually starting.
Change-Id: I5c6b50647c0af0fdec6547aaa59165f6b4a42642
If the mw.loader.using() call's promise is rejected, we end up
passing an Error object as the 'count' parameter to QUnit.start().
This seems to be harmless, but is very confusing when debugging.
Change-Id: I44caca5285dbced5a5876d9d7ff6236dbd3efc35
ResourceLoader errors, like invalid dependencies, are
hard to spot and only result in the special page
not finding any tests.
This is not a perfect solution but it would have
saved me a full day of troubleshooting.
Change-Id: I247174f89772b84b4cad31deffb03152921df020
HTML formatting of the queue was distributed over several OutputPage methods.
Each method demanding a snippet of HTML by calling makeResourceLoaderLink()
with a limited amount of information. As such, makeResourceLoaderLink() was
unable to provide the client with the proper state information.
Centralising it also allows it to better reduce duplication in HTML output
and maintain a more accurate state.
Problems fixed by centralising:
1. The 'user' module is special (due to per-user 'version' and 'user' params).
It is manually requested via script-src. To avoid a separate (and wrong)
request from something that requires it, we set state=loading directly.
However, because the module is in the bottom, the old HTML formatter could
only put state=loading in the bottom also. This sometimes caused a wrong
request to be fired for modules=user if something in the top queue
triggered a requirement for it.
2. Since a464d1d4 (T87871) we track states of page-style modules, with purpose
of allowing dependencies on style modules without risking duplicate loading
on pages where the styles are loaded already. This didn't work, because the
state information about page-style modules is output near the stylesheet,
which is after the script tag with mw.loader.load(). That runs first, and
mw.loader would still make a duplicate request before it learns the state.
Changes:
* Document reasons for style/script tag order in getHeadHtml (per 09537e83).
* Pass $type from getModuleStyles() to getAllowedModules(). This wasn't needed
before since a duplicate check in makeResourceLoaderLink() verified the
origin a second time.
* Declare explicit position 'top' on 'user.options' and 'user.tokens' module.
Previously, OutputPage hardcoded them in the top. The new formatter doesn't.
* Remove getHeadScripts().
* Remove getInlineHeadScripts().
* Remove getExternalHeadScripts().
* Remove buildCssLinks().
* Remove getScriptsForBottomQueue().
* Change where Skin::setupSkinUserCss() is called. This methods lets the skin
add modules to the queue. Previously it was called from buildCssLinks(),
via headElement(), via prepareQuickTemplate(), via OutputPage::output().
It's now in OutputPage::output() directly (slightly earlier). This is needed
because prepareQuickTemplate() calls bottomScripts() before headElement().
And bottomScript() would lazy-initialise the queue and lock it before
setupSkinUserCss() is called from headElement().
This makes execution order more predictable instead of being dependent on
the arbitrary order of data extraction in prepareQuickTemplate (which varies
from one skin to another).
* Compute isUserModulePreview() and isKnownEmpty() for the 'user' module early
on so. This avoids wrongful loading and fixes problem 1.
Effective changes in output:
* mw.loader.state() is now before mw.loader.load(). This fixes problem 2.
* mw.loader.state() now sets 'user.options' and 'user.tokens' to "loading".
* mw.loader.state() now sets 'user' (as "loading" or "ready"). Fixes problem 1.
* The <script async src> tag for 'startup' changed position (slightly).
Previously it was after all inline scripts and stylesheets. It's still after
all inline scripts and after most stylesheets, but before any user styles.
Since the queue is now formatted outside OutputPage, it can't inject the
meta-ResourceLoaderDynamicStyles tag and user-stylesheet hack in the middle
of existing output. This shouldn't have any noticable impact.
Bug: T87871
Change-Id: I605b8cd1e1fc009b4662a0edbc54d09dd65ee1df
Leaving behind only the so-called "plain" mode.
Also removed related unused messages (follows-up 6b758fc).
The execute() method continues to enforce 404 Not Found for arbitrary
subpage urls so that we keep the door open to add other sub resources or
test frameworks in the future.
Bug: T131389
Change-Id: I4c22666fb98e54c47ed1b4d12776af6fc43ee473
We're almost ready to drop the non-plain mode of Special:JavaScriptTest
in favour of Special:JavaScript/qunit/plain. There's a few mobile-related
extensions still using the non-karma mode for qunit testing.
However, none of them make use of the Skin selector, which was mainly a debug
thing I added in the initial version. It no longer makes sense since our tests
now enforce an anti-dependency on skin html and other context. Encouraging
testing in multiple skins in the old UI therefore no longer makes sense.
This also fixes one of the most frequent errors in resourceloader logs and gets
rid of an ugly hack in Resources.php that causes a small amount of overhead
in ResourceLoader::__construct().
> MessageBlobStore failed to find skinname-fallback
> MessageBlobStore failed to find skinname-apioutput
Change-Id: Idaacf718703883c6a7e83a17ccd3f41ebdca53d1
Page startup:
* Due to the startup module and top queue being asynchronous now,
move client-nojs/client-js class handling to OutputPage to ensure
there is no flashes of wrongly styled or unstyled content.
To preserve compatibility for unsupported browsers, undo the
class swap at runtime after the isCompatible() check.
ResourceLoader startup module:
* Load the startup module with <script async>.
* Use DOM methods instead of 'document.write' to create base module request (jquery|mediawiki).
mw.loader:
* Drop 'async' parameter from mw.loader.load().
* Remove the now-unused code paths for synchronous requests.
OutputPage:
* Drop '$loadCall' parameter from makeResourceLoaderLink().
Asynchronous is now the default and only way to load JavaScript.
This means the 'user' module "conditional document-write scripts"
are now a simple "mw.loader.load( url )" call.
* Fix incorrect @return of makeResourceLoaderLink(). This returns
an array not a string.
* Improve documentation of makeResourceLoaderLink().
* Drop '$inHead' parameter from getScriptsForBottomQueue(). No longer used.
Compatibility with the $wgResourceLoaderExperimentalAsyncLoading
feature is maintained. It just no longer needs to change the
way the queue works since it's always asynchronous. The feature
flag now only controls whether the bottom queue starts at the bottom
or starts at the top.
* Remove jQuery.ready() optimisation.
This was mostly there to avoid the setTimeout() loop jQuery does to detect
dom-ready in IE6/IE7 (which we no longer serve JavaScript at all).
And for a bug in Firefox with document.write (which is no longer used as of
this commit).
Bug: T107399
Change-Id: Icba6d7a87b239bf127a221bc6bc432cfa71a4a72
To make unit testing easier, allow any module to be loaded within
the unit test suite. Regardless of the intended 'target'.
Targets are meant for restricting front-end scope in production.
Enforcing that in the test suite causes various test suites to
get de-registered at run time client-side.
Otherwise, in order to truly run all unit tests, Jenkins would
have to re-run the entire test suite in all known targets. This
wouldn't make sense because modules have to be globally uniquely
named (no conflicts) and unit tests are atomic. They can all run
in the same suite.
To prevent modules being comitted with incompatible target
dependencies, we already have a Structure test in the PHPUnit
suite to catch those issues in the module registry.
This makes the main 'qunit' build for MobileFrontend more useful,
where currently many modules aren't being tested due to them not
being in the 'desktop' target.
Bug: T103027
Change-Id: I69f735eb56c1362189298d9859d3add576faaadb
These were never enabled or used in production and are not
compatible with the upcoming async changes (T107399). To avoid
having to maintain compatibility with this, remove it for now.
The current on-going request to operations for ESI support is unrelated
to this code.
Considered making makeResourceLoaderLink() protected as it's not
used anywhere in @wikimedia Git outside mediawiki-core. And the unit
test actually treated it as protected already. However it's called
in SpecialJavaScriptTest so leaving that as-is for now.
In Icba6d7a87b239 the signature will change again with the removal
of the $loadCall parameter, which is obsolete in an async world
due to document.write being forbidden.
Change-Id: I9f557cc794638ffd15329934865e21e1027f7cfa
Currently there's only one framework, so having an error landing page
when visiting Special:JavaScriptTest isn't helpful. DWIM and send the
user to Special:JavaScriptTest/qunit/plain if that is the only framework
that is configured.
Also add the testing help link to the "/plain" view.
Change-Id: Ifc473d080ecf6f0a9add0510480ba9dad76050e9
The patch did not improve performance. I'd like to think that the increased
control over when inline scripts are executed makes the patch worthwhile
regardless, but that is post hoc justification and possibly a bit of personal
ego. Krinkle agrees that we may use some of the ideas in this patch in the
future but he thinks we're better off not heading down this path before we
have a better sense of where we're going, and I trust his judgment.
This reverts commit e86e5f8460.
Change-Id: I151f74a41dd664b5a0aa5cfd99fcc95e2686a1e6
The current ordering of scripts and stylesheets in <head> causes all major
browsers to serialize and defer requests that could be performed in parallel.
The problem is that external stylesheets are loaded before inline scripts. As
Steven Souders explains, "all major browsers preserve the order of CSS and
JavaScript. The stylesheet has to be fully downloaded, parsed, and applied
before the inline script is executed. And the inline script must be executed
before the remaining resources can be downloaded. Therefore, resources that
follow a stylesheet and inline script are blocked from downloading."[1]
In other words: the browser could start loading body images, but it refuses to
do that until it has executed inline scripts in head. And it refuses to execute
those scripts until the external CSS is downloaded, parsed and applied. You can
see the effect of this in this image, showing the request waterfall for
[[en:Gothic Alphabet]]: [2]. Notice how no images were requested before the
browser had finished processing the three load.php requests at the top.
To fix this, we want to move the inline scripts above the external CSS. This is
a little bit tricky, because the inline scripts depend on mw.loader, which is
loaded via an external script. If we move the external script so that it too is
above the external stylesheet, we force the browser to serialize requests,
because the browser will not retrieve the external CSS until it has retrieved
and executed the external JS code. So what we want is to move the inline
scripts above the external stylesheet, but keep the external script (which the
inline scripts depend on) below the external stylesheet.
We can do this by wrapping the inline script code in a closure (which binds
'mw') and enqueuing the closure in a global array which will be processed by
the startup module at just the right time.
Net result: external CSS and JS is retrieved in parallel, retrieval of images
(and other external assets) is unblocked, but the order in which code is
evaluated remains the same.
[1]: <http://www.stevesouders.com/blog/2009/05/06/positioning-inline-scripts/>
[2]: <http://people.wikimedia.org/~ori/enwiki-waterfall.png> (excerpted from
<http://www.webpagetest.org/result/150316_0C_7MB/1/details/>.
Change-Id: I98d383a6299ffbd10210431544a505338ca8643f
The inline examples in the generated documentation due to a
404 Not Found for the javascript files.
1. Run $ ./maintenance/mwjsduck-gen
2. View http://localhost/w/docs/js/#!/api/mw.Map
3. Try "Live Preview"
Change-Id: Ic44f029c789042d82ec14e35d385f9ccc59959aa
To use, ensure npm modules are up to date by running 'npm install'
in tests/frontend. Then run 'grunt qunit' to run it in Chrome.
To run it in both Firefox and Chrome (locally), run
grunt karma:more
Moved fixture from hardcoded HTML to the testrunner so that our
tests don't depend on arbitrary HTML and thus also pass in browser
contexts where the page only loads QUnit + test suites (e.g. Karma).
Change-Id: I4e96da137340a28789b38940e75d4b6b8bc5d76a
Add an 'export' subpage to SpecialJavaScriptTest which allows
one to request a self-sufficient JavaScript payload that will
bootstrap a ResourceLoader client and load the test suites.
This is needed for using Karma (which only loads JavaScript,
no full html pages). As such elements from the Skin and OutputPage
will not exist. While all QUnit tests in MediaWiki core and
most extensions I've seen already use #qunit-fixture, this is
now required. This to prevent leakage of elements from one
test to another, but it also prevents tests from depending
on elements provided by the server.
While the Karma setup is still in the pipeline (might land before
this commit loses WIP status), for now this can be tested via
the 'Special:JavaScriptTest/qunit/plain' subpage.
Refactor:
* Use HTTP status code 404 in the response for "noframework".
* Simplify HTML footprint by using <div id="qunit"> instead of
hardcoding the full structure. This feature was added to QUnit
since v1.3.0 (Feb 2012), we're using v1.14.0 (Jan 2014).
QUnit's header is automatically derived from document.title.
* Remove redundant addModules() for 'test.mediawiki.qunit.testrunner'.
This is already added by default.
* Move allowClickjacking() call so that it applies to other modes
as well. The exported javascript needs to have wgBreakFrame set
to false so that test runners can frame it.
* Change mediawiki.special.javaScriptTest to not depend on QUnit.
It caused QUnit to load on error pages. And in theory the page
is suited for other frameworks and shouldn't load QUnit this way.
Bug: T74063
Change-Id: I3d4d0df43bb426d9579eb0349b8b5477281a7cfc
The config variable itself and the documentation property were added
in MediaWiki 1.19 (r107919 / c447423593).
The testswarm-injectjs propert was added in MediaWiki 1.20 (5e590be3d6).
We never actually ended up using TestSwarm, and this variable
is not used anywhere I can see.
Having a configuration variable for a documentation page seems
odd. I can't find another instance of this. As it's tied to development
(not for users of the wiki), link to mediawiki.org direcly.
Change-Id: Ib16607683a293b6d6661ed0411dad9a3ff551a08
- Swap "$variable type" to "type $variable"
- Added missing types
- Fixed spacing inside docs
- Makes beginning of @param/@return/@var/@throws in capital
- Changed some types to match the more common spelling
Change-Id: Ie419638e909a47aa72a274043604247830ee1a81
These modules should only be loaded when $wgEnableJavaScriptTest
is true. Move these modules to the registry that is only activated
in that context and rename the modules to be namespaced under 'test.'
so that there is no mistake when referencing these that they are
not regularly available.
Change-Id: I21e69f50b006904b12fe9f79c196c903ebff4661
Callers should use SpecialPage::getPageTitle, which is
exactly identical.
This is so that in the future we can turn SpecialPage
into a ContextSource, which requires getTitle to return
getContext()->getTitle.
Change-Id: Icdcf5d5295ef5e7f08b1d403e0c123f78738fd40
* Follows-up b2e2b2e016.
* Minor clean up of surrounding documentation comments.
* Fixed missing keys for messages in WebInstallerPage
Change-Id: Iaa692064262f3c0e10cfa5e4b1ec8c86e5d02362
* Removed spaces around array index
* Removed double spaces or added spaces to begin or end of function
calls, method signature, conditions or foreachs
* Added braces to one-line ifs
* Changed multi line conditions to one line conditions
* Realigned some arrays
Change-Id: Ia04d2a99d663b07101013c2d53b3b2e872fd9cc3
Doxygen expects parameter types to come before the
parameter name in @param tags. Used a quick regex
to switch everything around where possible. This
only fixes cases where a primitve variable (or a
primitive followed by other types) is the variable
type. Other cases will need to be fixed manually.
Change-Id: Ic59fd20856eb0489d70f3469a56ebce0efb3db13
Having all group mapping for Special:SpecialPages in the global
$wgSpecialPageGroups is not a good OO style.
Created a method SpecialPage::getGroupName, which than can be overridden
by each subclasses to the featured group name.
Added also SpecialPage::getFinalGroupName to get the groupname on
Special:SpecialPages to handle the customization and
to keep $wgSpecialPageGroups for b/c
Change-Id: I1de3a186f0a59ec5ecb8996c5f805cf164e9637f
It is better when the special page is not register,
instead of giving a hint, that this is disabled on the wiki.
See other special pages like Special:PopularPages or Special:ChangeEmail
Change-Id: I43fb118d61ddcb2536192f54ee888d55b2fbd49d
So far we've still been using ./tests/qunit/index.html in TestSwarm, today I've tested locally
to submit a url to SpecialJavaScriptTest instead and made a bunch of browsers join my swarm,
quite a few problems popped up. This commit fixes those issues so that we can actually use
SpecialJavaScriptTest in TestSwarm.
* Add QUnit configuration variable for TestSwarm's inject.js
In order to use TestSwarm, the urls that TestSwarm loads in clients that has
the QUnit test suite running on it need to include a little javascript.
This inject.js registers hooks with QUnit to listen for when the test suite finishes
and contacts the parent window (TestSwarm loads the qunit test suite url in an iframe)
to submit the results. Previously I included a copy of TestSwarm's inject.js in
./tests/qunit/data and in our testrunner.js a relative link to that.
However this is currently breaking because it is an outdated version. Updating brings
no good since someone else might use their own TestSwarm would could still run on an old
version etc. The TestSwarm submitted too always expects that it's own inject.js is used,
not some snapshot copy. I've removed the copy of it in MediaWiki and instead added a
configuration option to point to wherever the you want is located.
Also, since the old static index.html version of the unit test can't retrieve PHP based
content, this means TestSwarm submissions through the old static index.html are no longer
supported. Only through the new Special:JavaScriptTest from now on. I'll probably remove
the whole index.html soon-ish as it's getting quite annoying to maintain all that by hand,
and it's been superseded in everywhere imaginable now anyway. Even not used anymore by
intergration.mediawiki.org because that's been quiet since the Git-switchover.., and when
we update it, we can update it to point to the new SpecialPage instead.
* OutputPage::allowClickjacking() on SpecialJavaScriptTest/qunit.
When initially testing the TestSwarm setup to submit SpecialJavaScriptTest/qunit urls
(instead of the old ./tests/qunit/index.html) it was failing due to an iframe DENY.
This was a bit odd since `$wgBreakFrames = false;` by default, and although
`$wgEditPageFrameOptions = 'DENY';` by default, it wasn't obvious at all that that value
("DENY") is used for all OutputPages by default (as supposed to just action=edit and the
like). This is because OutputPage has mPreventClickjacking=true by default and when it's
true-ish it uses $wgEditPageFrameOptions for the X-Frame-Options.
* 'position' => true; for the mediawiki.tests.qunit.suites module.
QUnit has a hook for "done". Which is called when QUnit.start() is called and all queued tests
have been executed. QUnit.start() is automatically called on window.onload by QUnit.
TestSwarm uses QUnit's hook system to hook into the QUnit "done" event, and at that point
takes the stats, submits them to TestSwarm and go on with the next job.
When testing locally, I got semi-random failures reporting that only 0/0 tests were
successfully ran in IE6. This is because when QUnit.start (and consequently QUnit.done)
are first called, apparently no test suites had finished downloading and/or execution yet
(the bottom queue is asynchronous, and doesn't postpone domready nor window.onload).
When normally viewing Special:JavaScriptTest/qunit this doesn't break anything, because if
QUnit start/done is in the past and another module(), test(), or equal() etc. is called it just
picks up again and adds more results to the page and calls QUnit.done() again.
However in the case of the TestSwarm embed, it submits the results after the first done() and
cleans up the iframe. So I'm making mediawiki.tests.qunit.suites a blocking module instead, so
that there will only be one QUnit.start/done and that's the one that TestSwarm gets and after
which TestSwarm can safely garbage the iframe.
This means that basically all test suite modules and the original modules they are testing will
be loaded from the head. Shouldn't have any side effects, but might cause minor breakage in
future in modules that badly assume they're being put on the bottom.
I'm not considering that a bug in the test, it'll just help catch that bad code sooner :),
it's a test suite after all.
(Yay, my first Git commit to MediaWiki core)
Change-Id: I83f83377f2183b6deb4e901af602ac9a5628558b
* Through all QUnit updates, the html was never updated (only the JS/CSS files). Updating html now according to QUnit documentation. (introduced a new html element used for adding test document elements which are automatically cleaned up. the JS/CSS for QUnit was already referring to this but silently failing)