mediawiki-skins-Vector/resources/skins.vector.js/searchLoader.js
Nicholas Ray 8cf278d305 Add client-side performance metrics for searchLoader
As part of comparing Vue search with legacy search, we need to track how
long it takes to lazy load the wvui library. A similar metric was added
to measuring the mediawiki.searchSuggest module in
I0fa6b8904bd43c87a68e9161f00d686a0e588966.

This commit adds the following metrics which will only be used in our
synthetic tests. We are not doing RUM tests at this time.

To test locally, add the following to your LocalSettings.php and append
the query param `useskinversion=2` e.g.
(http://localhost:8181/wiki/Test?useskinversion=2):

```
$wgVectorUseCoreSearch = false;
```

Marks:

* mwVectorVueSearchLoadStart: Marks the start of loading the search
module.

* mwVectorVueSearchLoadEnd: Marks the end of loading the search
module.

Measures:

* mwVectorVueSearchLoadStartToLoadEnd: Measures the time it takes to
load the search module.

Bug: T251544
Change-Id: I14e44b45a66213821d69cd22395fedbae747da88
2020-10-09 13:26:28 -06:00

172 lines
5.1 KiB
JavaScript

/**
* Disabling this rule as it's only necessary for
* combining multiple class names and documenting the output.
* That doesn't happen in this file but the linter still throws an error.
* https://github.com/wikimedia/eslint-plugin-mediawiki/blob/master/docs/rules/class-doc.md
*/
/* eslint-disable mediawiki/class-doc */
/** @interface VectorResourceLoaderVirtualConfig */
/** @interface MediaWikiPageReadyModule */
var /** @type {VectorResourceLoaderVirtualConfig} */
config = require( /** @type {string} */ ( './config.json' ) ),
// T251544: Collect search performance metrics to compare Vue search with
// mediawiki.searchSuggest performance.
SHOULD_TEST_SEARCH = !!(
!config.wgVectorUseCoreSearch &&
window.performance &&
performance.mark &&
performance.measure &&
performance.getEntriesByName ),
LOAD_START_MARK = 'mwVectorVueSearchLoadStart',
LOAD_END_MARK = 'mwVectorVueSearchLoadEnd',
LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd',
SEARCH_FORM_ID = 'simpleSearch',
SEARCH_INPUT_ID = 'searchInput',
SEARCH_LOADING_CLASS = 'search-form__loader',
SEARCH_MODULE_NAME = config.wgVectorUseCoreSearch ?
'mediawiki.searchSuggest' :
'skins.vector.search';
/**
* Loads the search module via `mw.loader.using` on the element's
* focus event. Or, if the element is already focused, loads the
* search module immediately.
* After the search module is loaded, executes a function to remove
* the loading indicator.
*
* @param {HTMLElement} element search input.
* @param {string} moduleName resourceLoader module to load.
* @param {function(): void} afterLoadFn function to execute after search module loads.
*/
function loadSearchModule( element, moduleName, afterLoadFn ) {
function requestSearchModule() {
if ( SHOULD_TEST_SEARCH ) {
performance.mark( LOAD_START_MARK );
}
mw.loader.using( moduleName, afterLoadFn );
element.removeEventListener( 'focus', requestSearchModule );
}
if ( document.activeElement === element ) {
requestSearchModule();
} else {
element.addEventListener( 'focus', requestSearchModule );
}
}
/**
* Event callback that shows or hides the loading indicator based on the event type.
* The loading indicator states are:
* 1. Show on input event (while user is typing)
* 2. Hide on focusout event (when user removes focus from the input )
* 3. Show when input is focused, if it contains a query. (in case user re-focuses on input)
*
* @param {Event} event
*/
function renderSearchLoadingIndicator( event ) {
var form = /** @type {HTMLElement} */ ( event.currentTarget ),
input = /** @type {HTMLInputElement} */ ( event.target );
if (
!( event.currentTarget instanceof HTMLElement ) ||
!( event.target instanceof HTMLInputElement ) ||
!( input.id === SEARCH_INPUT_ID ) ) {
return;
}
if ( !form.dataset.loadingMsg ) {
form.dataset.loadingMsg = mw.msg( 'vector-search-loader' );
}
if ( event.type === 'input' ) {
form.classList.add( SEARCH_LOADING_CLASS );
} else if ( event.type === 'focusout' ) {
form.classList.remove( SEARCH_LOADING_CLASS );
} else if ( event.type === 'focusin' && input.value.trim() ) {
form.classList.add( SEARCH_LOADING_CLASS );
}
}
/**
* Attaches or detaches the event listeners responsible for activating
* the loading indicator.
*
* @param {HTMLElement} element
* @param {boolean} attach
* @param {function(Event): void} eventCallback
*/
function setLoadingIndicatorListeners( element, attach, eventCallback ) {
/** @type { "addEventListener" | "removeEventListener" } */
var addOrRemoveListener = ( attach ? 'addEventListener' : 'removeEventListener' );
[ 'input', 'focusin', 'focusout' ].forEach( function ( eventType ) {
element[ addOrRemoveListener ]( eventType, eventCallback );
} );
if ( !attach ) {
element.classList.remove( SEARCH_LOADING_CLASS );
}
}
/**
* Marks when the lazy load has completed.
*/
function markLoadEnd() {
if ( SHOULD_TEST_SEARCH && performance.getEntriesByName( LOAD_START_MARK ).length ) {
performance.mark( LOAD_END_MARK );
performance.measure( LOAD_MEASURE, LOAD_START_MARK, LOAD_END_MARK );
}
}
/**
* Initialize the loading of the search module as well as the loading indicator.
* Only initialize the loading indicator when not using the core search module.
*
* @param {Document} document
*/
function initSearchLoader( document ) {
var searchForm = document.getElementById( SEARCH_FORM_ID ),
searchInput = document.getElementById( SEARCH_INPUT_ID );
if ( !searchForm || !searchInput ) {
return;
}
/**
* 1. If we're using the search module from MediaWiki Core (searchSuggest),
* load the module.
* 2. If we're using a different search module, enable the loading indicator
* before the search module loads.
**/
if ( config.wgVectorUseCoreSearch ) {
loadSearchModule( searchInput, SEARCH_MODULE_NAME, function () {} );
} else {
setLoadingIndicatorListeners( searchForm, true, renderSearchLoadingIndicator );
loadSearchModule(
searchInput,
SEARCH_MODULE_NAME,
function () {
markLoadEnd();
setLoadingIndicatorListeners(
/** @type {HTMLElement} */ ( searchForm ),
false,
renderSearchLoadingIndicator
);
}
);
}
}
module.exports = {
initSearchLoader: initSearchLoader
};