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 * Calculate the byte size of a ResourceLoader module.
26 * @param {string} moduleName The name of the module
27 * @return {number|null} Module size in bytes or null
29 getModuleSize: function ( moduleName
) {
30 var module
= mw
.loader
.moduleRegistry
[ moduleName
],
33 if ( mw
.loader
.getState( moduleName
) !== 'ready' ) {
37 if ( !module
.style
&& !module
.script
) {
42 if ( module
.style
&& $.isArray( module
.style
.css
) ) {
43 $.each( module
.style
.css
, function ( i
, stylesheet
) {
44 payload
+= $.byteLength( stylesheet
);
49 if ( $.isFunction( module
.script
) ) {
50 payload
+= $.byteLength( module
.script
.toString() );
57 * Given CSS source, count both the total number of selectors it
58 * contains and the number which match some element in the current
61 * @param {string} css CSS source
62 * @return Selector counts
63 * @return {number} return.selectors Total number of selectors
64 * @return {number} return.matched Number of matched selectors
66 auditSelectors: function ( css
) {
67 var selectors
= { total
: 0, matched
: 0 },
68 style
= document
.createElement( 'style' ),
71 style
.textContent
= css
;
72 document
.body
.appendChild( style
);
73 // Standards-compliant browsers use .sheet.cssRules, IE8 uses .styleSheet.rules…
74 sheet
= style
.sheet
|| style
.styleSheet
;
75 rules
= sheet
.cssRules
|| sheet
.rules
;
76 $.each( rules
, function ( index
, rule
) {
78 if ( document
.querySelector( rule
.selectorText
) !== null ) {
82 document
.body
.removeChild( style
);
87 * Get a list of all loaded ResourceLoader modules.
89 * @return {Array} List of module names
91 getLoadedModules: function () {
92 return $.grep( mw
.loader
.getModuleNames(), function ( module
) {
93 return mw
.loader
.getState( module
) === 'ready';
98 * Print tabular data to the console, using console.table, console.log,
99 * or mw.log (in declining order of preference).
101 * @param {Array} data Tabular data represented as an array of objects
102 * with common properties.
104 dumpTable: function ( data
) {
106 // Bartosz made me put this here.
107 if ( window
.opera
) { throw window
.opera
; }
108 // Use Function.prototype#call to force an exception on Firefox,
109 // which doesn't define console#table but doesn't complain if you
111 console
.table
.call( console
.table
, data
);
115 console
.log( $.toJSON( data
, null, 2 ) );
122 * Generate and print one more reports. When invoked with no arguments,
125 * @param {string...} [reports] Report names to run, or unset to print
126 * all available reports.
128 runReports: function () {
129 var reports
= arguments
.length
> 0 ?
130 Array
.prototype.slice
.call( arguments
) :
131 $.map( inspect
.reports
, function ( v
, k
) { return k
; } );
133 $.each( reports
, function ( index
, name
) {
134 inspect
.dumpTable( inspect
.reports
[name
]() );
139 * @class mw.inspect.reports
144 * Generate a breakdown of all loaded modules and their size in
145 * kilobytes. Modules are ordered from largest to smallest.
148 // Map each module to a descriptor object.
149 var modules
= $.map( inspect
.getLoadedModules(), function ( module
) {
152 size
: inspect
.getModuleSize( module
)
156 // Sort module descriptors by size, largest first.
157 sortByProperty( modules
, 'size', true );
159 // Convert size to human-readable string.
160 $.each( modules
, function ( i
, module
) {
161 module
.size
= module
.size
> 1024 ?
162 ( module
.size
/ 1024 ).toFixed( 2 ) + ' KB' :
163 ( module
.size
!== null ? module
.size
+ ' B' : null );
170 * For each module with styles, count the number of selectors, and
171 * count how many match against some element currently in the DOM.
176 $.each( inspect
.getLoadedModules(), function ( index
, name
) {
177 var css
, stats
, module
= mw
.loader
.moduleRegistry
[name
];
180 css
= module
.style
.css
.join();
181 } catch (e
) { return; } // skip
183 stats
= inspect
.auditSelectors( css
);
186 allSelectors
: stats
.total
,
187 matchedSelectors
: stats
.matched
,
188 percentMatched
: stats
.total
!== 0 ?
189 ( stats
.matched
/ stats
.total
* 100 ).toFixed( 2 ) + '%' : null
192 sortByProperty( modules
, 'allSelectors', true );
198 if ( mw
.config
.get( 'debug' ) ) {
199 mw
.log( 'mw.inspect: reports are not available in debug mode.' );
202 mw
.inspect
= inspect
;
204 }( mediaWiki
, jQuery
) );