2 * Tools for inspecting page composition and performance.
10 function sortByProperty( array
, prop
, descending
) {
11 var order
= descending
? -1 : 1;
12 return array
.sort( function ( a
, b
) {
13 return a
[prop
] > b
[prop
] ? order
: a
[prop
] < b
[prop
] ? -order
: 0;
24 * Return a map of all dependency relationships between loaded modules.
26 * @return {Object} Maps module names to objects. Each sub-object has
27 * two properties, 'requires' and 'requiredBy'.
29 getDependencyGraph: function () {
30 var modules
= inspect
.getLoadedModules(), graph
= {};
32 $.each( modules
, function ( moduleIndex
, moduleName
) {
33 var dependencies
= mw
.loader
.moduleRegistry
[moduleName
].dependencies
|| [];
35 graph
[moduleName
] = graph
[moduleName
] || { requiredBy
: [] };
36 graph
[moduleName
].requires
= dependencies
;
38 $.each( dependencies
, function ( depIndex
, depName
) {
39 graph
[depName
] = graph
[depName
] || { requiredBy
: [] };
40 graph
[depName
].requiredBy
.push( moduleName
);
47 * Calculate the byte size of a ResourceLoader module.
49 * @param {string} moduleName The name of the module
50 * @return {number|null} Module size in bytes or null
52 getModuleSize: function ( moduleName
) {
53 var module
= mw
.loader
.moduleRegistry
[ moduleName
],
56 if ( mw
.loader
.getState( moduleName
) !== 'ready' ) {
60 if ( !module
.style
&& !module
.script
) {
65 if ( module
.style
&& $.isArray( module
.style
.css
) ) {
66 $.each( module
.style
.css
, function ( i
, stylesheet
) {
67 payload
+= $.byteLength( stylesheet
);
72 if ( $.isFunction( module
.script
) ) {
73 payload
+= $.byteLength( module
.script
.toString() );
80 * Given CSS source, count both the total number of selectors it
81 * contains and the number which match some element in the current
84 * @param {string} css CSS source
85 * @return Selector counts
86 * @return {number} return.selectors Total number of selectors
87 * @return {number} return.matched Number of matched selectors
89 auditSelectors: function ( css
) {
90 var selectors
= { total
: 0, matched
: 0 },
91 style
= document
.createElement( 'style' ),
94 style
.textContent
= css
;
95 document
.body
.appendChild( style
);
96 // Standards-compliant browsers use .sheet.cssRules, IE8 uses .styleSheet.rules…
97 sheet
= style
.sheet
|| style
.styleSheet
;
98 rules
= sheet
.cssRules
|| sheet
.rules
;
99 $.each( rules
, function ( index
, rule
) {
101 if ( document
.querySelector( rule
.selectorText
) !== null ) {
105 document
.body
.removeChild( style
);
110 * Get a list of all loaded ResourceLoader modules.
112 * @return {Array} List of module names
114 getLoadedModules: function () {
115 return $.grep( mw
.loader
.getModuleNames(), function ( module
) {
116 return mw
.loader
.getState( module
) === 'ready';
121 * Print tabular data to the console, using console.table, console.log,
122 * or mw.log (in declining order of preference).
124 * @param {Array} data Tabular data represented as an array of objects
125 * with common properties.
127 dumpTable: function ( data
) {
129 // Bartosz made me put this here.
130 if ( window
.opera
) { throw window
.opera
; }
131 // Use Function.prototype#call to force an exception on Firefox,
132 // which doesn't define console#table but doesn't complain if you
134 console
.table
.call( console
.table
, data
);
138 console
.log( $.toJSON( data
, null, 2 ) );
145 * Generate and print one more reports. When invoked with no arguments,
148 * @param {string...} [reports] Report names to run, or unset to print
149 * all available reports.
151 runReports: function () {
152 var reports
= arguments
.length
> 0 ?
153 Array
.prototype.slice
.call( arguments
) :
154 $.map( inspect
.reports
, function ( v
, k
) { return k
; } );
156 $.each( reports
, function ( index
, name
) {
157 inspect
.dumpTable( inspect
.reports
[name
]() );
162 * @class mw.inspect.reports
167 * Generate a breakdown of all loaded modules and their size in
168 * kilobytes. Modules are ordered from largest to smallest.
171 // Map each module to a descriptor object.
172 var modules
= $.map( inspect
.getLoadedModules(), function ( module
) {
175 size
: inspect
.getModuleSize( module
)
179 // Sort module descriptors by size, largest first.
180 sortByProperty( modules
, 'size', true );
182 // Convert size to human-readable string.
183 $.each( modules
, function ( i
, module
) {
184 module
.size
= module
.size
> 1024 ?
185 ( module
.size
/ 1024 ).toFixed( 2 ) + ' KB' :
186 ( module
.size
!== null ? module
.size
+ ' B' : null );
193 * For each module with styles, count the number of selectors, and
194 * count how many match against some element currently in the DOM.
199 $.each( inspect
.getLoadedModules(), function ( index
, name
) {
200 var css
, stats
, module
= mw
.loader
.moduleRegistry
[name
];
203 css
= module
.style
.css
.join();
204 } catch (e
) { return; } // skip
206 stats
= inspect
.auditSelectors( css
);
209 allSelectors
: stats
.total
,
210 matchedSelectors
: stats
.matched
,
211 percentMatched
: stats
.total
!== 0 ?
212 ( stats
.matched
/ stats
.total
* 100 ).toFixed( 2 ) + '%' : null
215 sortByProperty( modules
, 'allSelectors', true );
221 if ( mw
.config
.get( 'debug' ) ) {
222 mw
.log( 'mw.inspect: reports are not available in debug mode.' );
225 mw
.inspect
= inspect
;
227 }( mediaWiki
, jQuery
) );