From 678d976f0b646c98ca679f0dbb47602da72a947c Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sun, 20 Oct 2013 01:09:53 -0700 Subject: [PATCH] mediawiki.inspect: add CSS report This patch extends mediawiki#inspect to add a notion of 'reports'. Instead of there being one single view for all ResourceLoader module debug data, you can now request that a specific report be run. Each report highlights a set of metrics that are unified by some common theme. The previous, unqualified report is now branded the 'size' report. In addition to it, this patch also adds a CSS report, which prints the count of selectors and the count of selectors that match against the current time Change-Id: I0e7a47b5a93a66ecb103fe30a3de8a2ca8a4eb84 --- maintenance/jsduck/categories.json | 1 + resources/mediawiki/mediawiki.inspect.js | 130 +++++++++++++++++++---- resources/mediawiki/mediawiki.js | 5 +- 3 files changed, 114 insertions(+), 22 deletions(-) diff --git a/maintenance/jsduck/categories.json b/maintenance/jsduck/categories.json index e98e9c0fbe..f96902d8ff 100644 --- a/maintenance/jsduck/categories.json +++ b/maintenance/jsduck/categories.json @@ -21,6 +21,7 @@ "classes": [ "mw.Title", "mw.inspect", + "mw.inspect.reports", "mw.notification", "mw.user", "mw.util", diff --git a/resources/mediawiki/mediawiki.inspect.js b/resources/mediawiki/mediawiki.inspect.js index 5cecc164bf..8268f12c61 100644 --- a/resources/mediawiki/mediawiki.inspect.js +++ b/resources/mediawiki/mediawiki.inspect.js @@ -4,8 +4,16 @@ * @author Ori Livneh * @since 1.22 */ +/*jshint devel:true */ ( function ( mw, $ ) { + function sortByProperty( array, prop, descending ) { + var order = descending ? -1 : 1; + return array.sort( function ( a, b ) { + return a[prop] > b[prop] ? order : a[prop] < b[prop] ? -order : 0; + } ); + } + /** * @class mw.inspect * @singleton @@ -45,6 +53,32 @@ return payload; }, + /** + * 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 Selector counts + * @return {number} return.selectors Total number of selectors + * @return {number} return.matched Number of matched selectors + */ + 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++; + if ( document.querySelector( rule.selectorText ) !== null ) { + selectors.matched++; + } + } ); + document.body.removeChild( style ); + return selectors; + }, + /** * Get a list of all loaded ResourceLoader modules. * @@ -57,13 +91,53 @@ }, /** - * Print a breakdown of all loaded modules and their size in kilobytes - * to the debug console. Modules are ordered from largest to smallest. + * 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; } + console.table( data ); + return; + } catch (e) {} + try { + console.log( JSON.stringify( data, null, 2 ) ); + return; + } catch (e) {} + mw.log( data ); + }, + + /** + * Generate and print one more reports. When invoked with no arguments, + * print all reports. + * + * @param {string...} [reports] Report names to run, or unset to print + * all available reports. */ - inspectModules: function () { - var console = window.console; + runReports: function () { + var reports = arguments.length > 0 ? + Array.prototype.slice.call( arguments ) : + $.map( inspect.reports, function ( v, k ) { return k; } ); + + $.each( reports, function ( index, name ) { + inspect.dumpTable( inspect.reports[name]() ); + } ); + }, - $( function () { + /** + * @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. + */ + size: function () { // Map each module to a descriptor object. var modules = $.map( inspect.getLoadedModules(), function ( module ) { return { @@ -73,9 +147,7 @@ } ); // Sort module descriptors by size, largest first. - modules.sort( function ( a, b ) { - return b.size - a.size; - } ); + sortByProperty( modules, 'size', true ); // Convert size to human-readable string. $.each( modules, function ( i, module ) { @@ -84,22 +156,40 @@ ( module.size !== null ? module.size + ' B' : null ); } ); - if ( console ) { - if ( console.table ) { - console.table( modules ); - } else { - $.each( modules, function ( i, module ) { - console.log( [ module.name, module.size ].join( '\t' ) ); - } ); - } - } - } ); + return modules; + }, + + /** + * For each module with styles, count the number of selectors, and + * count how many match against some element currently in the DOM. + */ + css: function () { + var modules = []; + + $.each( inspect.getLoadedModules(), function ( index, 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 + } ); + } ); + sortByProperty( modules, 'allSelectors', true ); + return modules; + }, } }; if ( mw.config.get( 'debug' ) ) { - inspect.getModuleSize = function () { return null; }; - mw.log( 'mw.inspect: Module sizes are not available in debug mode.' ); + mw.log( 'mw.inspect: reports are not available in debug mode.' ); } mw.inspect = inspect; diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js index 4138ac8d97..cc996e541e 100644 --- a/resources/mediawiki/mediawiki.js +++ b/resources/mediawiki/mediawiki.js @@ -1702,12 +1702,13 @@ var mw = ( function ( $, undefined ) { }, /** - * @inheritdoc mw.inspect#inspectModules + * @inheritdoc mw.inspect#runReports * @method */ inspect: function () { + var args = slice.call( arguments ); mw.loader.using( 'mediawiki.inspect', function () { - mw.inspect.inspectModules(); + mw.inspect.runReports.apply( mw.inspect, args ); } ); } -- 2.20.1