X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki.inspect.js;h=e2030c9e554cfbc4748859f4aff33d2727f07c34;hb=2f88ff07c7aa0f540641d3b472f31f4d3222aeb7;hp=6478fd96d3e5250d6bee7b9cd6f1b78f19a98078;hpb=3c90317ee54ad2aa4ab0b3286e3c2eb83f364b1e;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/src/mediawiki.inspect.js b/resources/src/mediawiki.inspect.js index 6478fd96d3..e2030c9e55 100644 --- a/resources/src/mediawiki.inspect.js +++ b/resources/src/mediawiki.inspect.js @@ -1,5 +1,5 @@ /*! - * Tools for inspecting page composition and performance. + * The mediawiki.inspect module. * * @author Ori Livneh * @since 1.22 @@ -9,7 +9,19 @@ ( function ( mw, $ ) { - var inspect, + // mw.inspect is a singleton class with static methods + // that itself can also be invoked as a function (mediawiki.base/mw#inspect). + // In JavaScript, that is implemented by starting with a function, + // and subsequently setting additional properties on the function object. + + /** + * Tools for inspecting page composition and performance. + * + * @class mw.inspect + * @singleton + */ + + var inspect = mw.inspect, byteLength = require( 'mediawiki.String' ).byteLength, hasOwn = Object.prototype.hasOwnProperty; @@ -33,299 +45,292 @@ } /** - * @class mw.inspect - * @singleton + * Return a map of all dependency relationships between loaded modules. + * + * @return {Object} Maps module names to objects. Each sub-object has + * two properties, 'requires' and 'requiredBy'. */ - inspect = { + inspect.getDependencyGraph = function () { + var modules = inspect.getLoadedModules(), + graph = {}; - /** - * Return a map of all dependency relationships between loaded modules. - * - * @return {Object} Maps module names to objects. Each sub-object has - * two properties, 'requires' and 'requiredBy'. - */ - getDependencyGraph: function () { - var modules = inspect.getLoadedModules(), - graph = {}; + modules.forEach( function ( moduleName ) { + var dependencies = mw.loader.moduleRegistry[ moduleName ].dependencies || []; - modules.forEach( function ( moduleName ) { - var dependencies = mw.loader.moduleRegistry[ moduleName ].dependencies || []; + if ( !hasOwn.call( graph, moduleName ) ) { + graph[ moduleName ] = { requiredBy: [] }; + } + graph[ moduleName ].requires = dependencies; - if ( !hasOwn.call( graph, moduleName ) ) { - graph[ moduleName ] = { requiredBy: [] }; + dependencies.forEach( function ( depName ) { + if ( !hasOwn.call( graph, depName ) ) { + graph[ depName ] = { requiredBy: [] }; } - graph[ moduleName ].requires = dependencies; - - dependencies.forEach( function ( depName ) { - if ( !hasOwn.call( graph, depName ) ) { - graph[ depName ] = { requiredBy: [] }; - } - graph[ depName ].requiredBy.push( moduleName ); - } ); + graph[ depName ].requiredBy.push( moduleName ); } ); - return graph; - }, + } ); + return graph; + }; - /** - * Calculate the byte size of a ResourceLoader module. - * - * @param {string} moduleName The name of the module - * @return {number|null} Module size in bytes or null - */ - getModuleSize: function ( moduleName ) { - var module = mw.loader.moduleRegistry[ moduleName ], - args, i, size; + /** + * Calculate the byte size of a ResourceLoader module. + * + * @param {string} moduleName The name of the module + * @return {number|null} Module size in bytes or null + */ + inspect.getModuleSize = function ( moduleName ) { + var module = mw.loader.moduleRegistry[ moduleName ], + args, i, size; - if ( module.state !== 'ready' ) { - return null; - } + if ( module.state !== 'ready' ) { + return null; + } + + if ( !module.style && !module.script ) { + return 0; + } - if ( !module.style && !module.script ) { - return 0; + function getFunctionBody( func ) { + return String( func ) + // To ensure a deterministic result, replace the start of the function + // declaration with a fixed string. For example, in Chrome 55, it seems + // V8 seemingly-at-random decides to sometimes put a line break between + // the opening brace and first statement of the function body. T159751. + .replace( /^\s*function\s*\([^)]*\)\s*{\s*/, 'function(){' ) + .replace( /\s*}\s*$/, '}' ); + } + + // Based on the load.php response for this module. + // For example: `mw.loader.implement("example", function(){}, {"css":[".x{color:red}"]});` + // @see mw.loader.store.set(). + args = [ + moduleName, + module.script, + module.style, + module.messages, + module.templates + ]; + // Trim trailing null or empty object, as load.php would have done. + // @see ResourceLoader::makeLoaderImplementScript and ResourceLoader::trimArray. + i = args.length; + while ( i-- ) { + if ( args[ i ] === null || ( $.isPlainObject( args[ i ] ) && $.isEmptyObject( args[ i ] ) ) ) { + args.splice( i, 1 ); + } else { + break; } + } - function getFunctionBody( func ) { - return String( func ) - // To ensure a deterministic result, replace the start of the function - // declaration with a fixed string. For example, in Chrome 55, it seems - // V8 seemingly-at-random decides to sometimes put a line break between - // the opening brace and first statement of the function body. T159751. - .replace( /^\s*function\s*\([^)]*\)\s*{\s*/, 'function(){' ) - .replace( /\s*}\s*$/, '}' ); + size = 0; + for ( i = 0; i < args.length; i++ ) { + if ( typeof args[ i ] === 'function' ) { + size += byteLength( getFunctionBody( args[ i ] ) ); + } else { + size += byteLength( JSON.stringify( args[ i ] ) ); } + } - // Based on the load.php response for this module. - // For example: `mw.loader.implement("example", function(){}, {"css":[".x{color:red}"]});` - // @see mw.loader.store.set(). - args = [ - moduleName, - module.script, - module.style, - module.messages, - module.templates - ]; - // Trim trailing null or empty object, as load.php would have done. - // @see ResourceLoader::makeLoaderImplementScript and ResourceLoader::trimArray. - i = args.length; - while ( i-- ) { - if ( args[ i ] === null || ( $.isPlainObject( args[ i ] ) && $.isEmptyObject( args[ i ] ) ) ) { - args.splice( i, 1 ); - } else { - break; + return size; + }; + + /** + * Given CSS source, count both the total number of selectors it + * contains and the number which match some element in the current + * document. + * + * @param {string} css CSS source + * @return {Object} Selector counts + * @return {number} return.selectors Total number of selectors + * @return {number} return.matched Number of matched selectors + */ + inspect.auditSelectors = function ( css ) { + var selectors = { total: 0, matched: 0 }, + style = document.createElement( 'style' ); + + style.textContent = css; + document.body.appendChild( style ); + $.each( style.sheet.cssRules, function ( index, rule ) { + selectors.total++; + // document.querySelector() on prefixed pseudo-elements can throw exceptions + // in Firefox and Safari. Ignore these exceptions. + // https://bugs.webkit.org/show_bug.cgi?id=149160 + // https://bugzilla.mozilla.org/show_bug.cgi?id=1204880 + try { + if ( document.querySelector( rule.selectorText ) !== null ) { + selectors.matched++; } + } catch ( e ) {} + } ); + document.body.removeChild( style ); + return selectors; + }; + + /** + * Get a list of all loaded ResourceLoader modules. + * + * @return {Array} List of module names + */ + inspect.getLoadedModules = function () { + return mw.loader.getModuleNames().filter( function ( module ) { + return mw.loader.getState( module ) === 'ready'; + } ); + }; + + /** + * Print tabular data to the console, using console.table, console.log, + * or mw.log (in declining order of preference). + * + * @param {Array} data Tabular data represented as an array of objects + * with common properties. + */ + inspect.dumpTable = function ( data ) { + try { + // Bartosz made me put this here. + if ( window.opera ) { throw window.opera; } + // Use Function.prototype#call to force an exception on Firefox, + // which doesn't define console#table but doesn't complain if you + // try to invoke it. + // eslint-disable-next-line no-useless-call + console.table.call( console, data ); + return; + } catch ( e ) {} + try { + console.log( JSON.stringify( data, null, 2 ) ); + return; + } catch ( e ) {} + mw.log( data ); + }; + + /** + * Generate and print reports. + * + * When invoked without arguments, prints all available reports. + * + * @param {...string} [reports] One or more of "size", "css", or "store". + */ + inspect.runReports = function () { + var reports = arguments.length > 0 ? + Array.prototype.slice.call( arguments ) : + Object.keys( inspect.reports ); + + reports.forEach( function ( name ) { + inspect.dumpTable( inspect.reports[ name ]() ); + } ); + }; + + /** + * Perform a string search across the JavaScript and CSS source code + * of all loaded modules and return an array of the names of the + * modules that matched. + * + * @param {string|RegExp} pattern String or regexp to match. + * @return {Array} Array of the names of modules that matched. + */ + inspect.grep = function ( pattern ) { + if ( typeof pattern.test !== 'function' ) { + pattern = new RegExp( mw.RegExp.escape( pattern ), 'g' ); + } + + return inspect.getLoadedModules().filter( function ( moduleName ) { + var module = mw.loader.moduleRegistry[ moduleName ]; + + // Grep module's JavaScript + if ( $.isFunction( module.script ) && pattern.test( module.script.toString() ) ) { + return true; } - size = 0; - for ( i = 0; i < args.length; i++ ) { - if ( typeof args[ i ] === 'function' ) { - size += byteLength( getFunctionBody( args[ i ] ) ); - } else { - size += byteLength( JSON.stringify( args[ i ] ) ); - } + // Grep module's CSS + if ( + $.isPlainObject( module.style ) && Array.isArray( module.style.css ) && + pattern.test( module.style.css.join( '' ) ) + ) { + // Module's CSS source matches + return true; } - return size; - }, + return false; + } ); + }; + /** + * @class mw.inspect.reports + * @singleton + */ + inspect.reports = { /** - * Given CSS source, count both the total number of selectors it - * contains and the number which match some element in the current - * document. + * Generate a breakdown of all loaded modules and their size in + * kilobytes. Modules are ordered from largest to smallest. * - * @param {string} css CSS source - * @return {Object} Selector counts - * @return {number} return.selectors Total number of selectors - * @return {number} return.matched Number of matched selectors + * @return {Object[]} Size reports */ - auditSelectors: function ( css ) { - var selectors = { total: 0, matched: 0 }, - style = document.createElement( 'style' ); - - style.textContent = css; - document.body.appendChild( style ); - $.each( style.sheet.cssRules, function ( index, rule ) { - selectors.total++; - // document.querySelector() on prefixed pseudo-elements can throw exceptions - // in Firefox and Safari. Ignore these exceptions. - // https://bugs.webkit.org/show_bug.cgi?id=149160 - // https://bugzilla.mozilla.org/show_bug.cgi?id=1204880 - try { - if ( document.querySelector( rule.selectorText ) !== null ) { - selectors.matched++; - } - } catch ( e ) {} + size: function () { + // Map each module to a descriptor object. + var modules = inspect.getLoadedModules().map( function ( module ) { + return { + name: module, + size: inspect.getModuleSize( module ) + }; } ); - document.body.removeChild( style ); - return selectors; - }, - /** - * Get a list of all loaded ResourceLoader modules. - * - * @return {Array} List of module names - */ - getLoadedModules: function () { - return mw.loader.getModuleNames().filter( function ( module ) { - return mw.loader.getState( module ) === 'ready'; + // Sort module descriptors by size, largest first. + sortByProperty( modules, 'size', true ); + + // Convert size to human-readable string. + modules.forEach( function ( module ) { + module.sizeInBytes = module.size; + module.size = humanSize( module.size ); } ); - }, - /** - * Print tabular data to the console, using console.table, console.log, - * or mw.log (in declining order of preference). - * - * @param {Array} data Tabular data represented as an array of objects - * with common properties. - */ - dumpTable: function ( data ) { - try { - // Bartosz made me put this here. - if ( window.opera ) { throw window.opera; } - // Use Function.prototype#call to force an exception on Firefox, - // which doesn't define console#table but doesn't complain if you - // try to invoke it. - // eslint-disable-next-line no-useless-call - console.table.call( console, data ); - return; - } catch ( e ) {} - try { - console.log( JSON.stringify( data, null, 2 ) ); - return; - } catch ( e ) {} - mw.log( data ); + return modules; }, /** - * Generate and print one more reports. When invoked with no arguments, - * print all reports. + * For each module with styles, count the number of selectors, and + * count how many match against some element currently in the DOM. * - * @param {...string} [reports] Report names to run, or unset to print - * all available reports. + * @return {Object[]} CSS reports */ - runReports: function () { - var reports = arguments.length > 0 ? - Array.prototype.slice.call( arguments ) : - $.map( inspect.reports, function ( v, k ) { return k; } ); + css: function () { + var modules = []; - reports.forEach( function ( name ) { - inspect.dumpTable( inspect.reports[ name ]() ); - } ); - }, + inspect.getLoadedModules().forEach( function ( name ) { + var css, stats, module = mw.loader.moduleRegistry[ name ]; - /** - * @class mw.inspect.reports - * @singleton - */ - reports: { - /** - * Generate a breakdown of all loaded modules and their size in - * kilobytes. Modules are ordered from largest to smallest. - * - * @return {Object[]} Size reports - */ - size: function () { - // Map each module to a descriptor object. - var modules = inspect.getLoadedModules().map( function ( module ) { - return { - name: module, - size: inspect.getModuleSize( module ) - }; - } ); - - // Sort module descriptors by size, largest first. - sortByProperty( modules, 'size', true ); - - // Convert size to human-readable string. - modules.forEach( function ( module ) { - module.sizeInBytes = module.size; - module.size = humanSize( module.size ); - } ); - - return modules; - }, - - /** - * For each module with styles, count the number of selectors, and - * count how many match against some element currently in the DOM. - * - * @return {Object[]} CSS reports - */ - css: function () { - var modules = []; - - inspect.getLoadedModules().forEach( function ( name ) { - var css, stats, module = mw.loader.moduleRegistry[ name ]; - - try { - css = module.style.css.join(); - } catch ( e ) { return; } // skip - - stats = inspect.auditSelectors( css ); - modules.push( { - module: name, - allSelectors: stats.total, - matchedSelectors: stats.matched, - percentMatched: stats.total !== 0 ? - ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null - } ); + try { + css = module.style.css.join(); + } catch ( e ) { return; } // skip + + stats = inspect.auditSelectors( css ); + modules.push( { + module: name, + allSelectors: stats.total, + matchedSelectors: stats.matched, + percentMatched: stats.total !== 0 ? + ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null } ); - sortByProperty( modules, 'allSelectors', true ); - return modules; - }, - - /** - * Report stats on mw.loader.store: the number of localStorage - * cache hits and misses, the number of items purged from the - * cache, and the total size of the module blob in localStorage. - * - * @return {Object[]} Store stats - */ - store: function () { - var raw, stats = { enabled: mw.loader.store.enabled }; - if ( stats.enabled ) { - $.extend( stats, mw.loader.store.stats ); - try { - raw = localStorage.getItem( mw.loader.store.getStoreKey() ); - stats.totalSizeInBytes = byteLength( raw ); - stats.totalSize = humanSize( byteLength( raw ) ); - } catch ( e ) {} - } - return [ stats ]; - } + } ); + sortByProperty( modules, 'allSelectors', true ); + return modules; }, /** - * Perform a string search across the JavaScript and CSS source code - * of all loaded modules and return an array of the names of the - * modules that matched. + * Report stats on mw.loader.store: the number of localStorage + * cache hits and misses, the number of items purged from the + * cache, and the total size of the module blob in localStorage. * - * @param {string|RegExp} pattern String or regexp to match. - * @return {Array} Array of the names of modules that matched. + * @return {Object[]} Store stats */ - grep: function ( pattern ) { - if ( typeof pattern.test !== 'function' ) { - pattern = new RegExp( mw.RegExp.escape( pattern ), 'g' ); + store: function () { + var raw, stats = { enabled: mw.loader.store.enabled }; + if ( stats.enabled ) { + $.extend( stats, mw.loader.store.stats ); + try { + raw = localStorage.getItem( mw.loader.store.getStoreKey() ); + stats.totalSizeInBytes = byteLength( raw ); + stats.totalSize = humanSize( byteLength( raw ) ); + } catch ( e ) {} } - - return inspect.getLoadedModules().filter( function ( moduleName ) { - var module = mw.loader.moduleRegistry[ moduleName ]; - - // Grep module's JavaScript - if ( $.isFunction( module.script ) && pattern.test( module.script.toString() ) ) { - return true; - } - - // Grep module's CSS - if ( - $.isPlainObject( module.style ) && Array.isArray( module.style.css ) && - pattern.test( module.style.css.join( '' ) ) - ) { - // Module's CSS source matches - return true; - } - - return false; - } ); + return [ stats ]; } }; @@ -333,6 +338,4 @@ mw.log( 'mw.inspect: reports are not available in debug mode.' ); } - mw.inspect = inspect; - }( mediaWiki, jQuery ) );