123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- /**
- * Analyzes jQuery activity
- *
- * @see http://code.jquery.com/jquery-1.10.2.js
- * @see http://code.jquery.com/jquery-2.0.3.js
- */
- /* global document: true, window: true */
- /* jshint -W030 */
- exports.version = '0.2.a';
- exports.module = function(phantomas) {
- 'use strict';
- phantomas.setMetric('jQueryVersion', ''); // @desc version of jQuery framework (if loaded) [string]
- phantomas.setMetric('jQueryOnDOMReadyFunctions'); // @desc number of functions bound to onDOMReady event
- phantomas.setMetric('jQuerySizzleCalls'); // @desc number of calls to Sizzle (including those that will be resolved using querySelectorAll)
- phantomas.setMetric('jQuerySizzleCallsDuplicated'); // @desc number of calls on the same Sizzle request
- phantomas.setMetric('jQueryDifferentVersions'); //@desc number of different jQuery versions loaded on the page (not counting iframes)
- var jQueryFunctions = [
- // DOM manipulations
- 'html',
- 'append',
- 'appendTo',
- 'prepend',
- 'prependTo',
- 'before',
- 'insertBefore',
- 'after',
- 'insertAfter',
- 'remove',
- 'detach',
- 'empty',
- 'clone',
- 'replaceWith',
- 'replaceAll',
- 'text',
- 'wrap',
- 'wrapAll',
- 'wrapInner',
- 'unwrap',
- // Style manipulations
- 'css',
- 'offset',
- 'position',
- 'height',
- 'innerHeight',
- 'outerHeight',
- 'width',
- 'innerWidth',
- 'outerWidth',
- 'scrollLeft',
- 'scrollTop',
- // generic events
- 'on',
- 'off',
- 'live',
- 'die',
- 'delegate',
- 'undelegate',
- 'one',
- 'unbind',
- // more events
- 'blur',
- 'change',
- 'click',
- 'dblclick',
- 'error',
- 'focus',
- 'focusin',
- 'focusout',
- 'hover',
- 'keydown',
- 'keypress',
- 'keyup',
- 'load',
- 'mousedown',
- 'mouseenter',
- 'mouseleave',
- 'mousemove',
- 'mouseout',
- 'mouseover',
- 'mouseup',
- 'resize',
- 'scroll',
- 'select',
- 'submit',
- 'toggle',
- 'unload',
- // attributes
- 'attr',
- 'prop',
- 'removeAttr',
- 'removeProp',
- 'val',
- 'hasClass',
- 'addClass',
- 'removeClass',
- 'toggleClass'
- ];
- // spy calls to jQuery functions
- phantomas.once('init', function() {
- phantomas.evaluate(function(jQueryFunctions) {
- (function(phantomas) {
- var jQuery;
- // TODO: create a helper - phantomas.spyGlobalVar() ?
- window.__defineSetter__('jQuery', function(val) {
- var version;
- var jQueryFn;
- var oldJQuery = jQuery;
- if (!val || !val.fn) {
- phantomas.log('jQuery: unable to detect version!');
- return;
- }
- version = val.fn.jquery;
- jQuery = val;
- jQueryFn = val.fn;
- // Older jQuery (v?.?) compatibility
- if (!jQueryFn) {
- jQueryFn = jQuery;
- }
- phantomas.log('jQuery: loaded v' + version);
- phantomas.setMetric('jQueryVersion', version);
- phantomas.emit('jQueryLoaded', version);
-
- phantomas.pushContext({
- type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded',
- callDetails: {
- arguments: ['version ' + version]
- },
- backtrace: phantomas.getBacktrace()
- });
- // jQuery.ready.promise
- // works for jQuery 1.8.0+ (released Aug 09 2012)
- phantomas.spy(val.ready, 'promise', function(func) {
- phantomas.incrMetric('jQueryOnDOMReadyFunctions');
- phantomas.pushContext({
- type: 'jQuery - onDOMReady',
- callDetails: {
- arguments: [func]
- },
- backtrace: phantomas.getBacktrace()
- });
- }) || phantomas.log('jQuery: can not measure jQueryOnDOMReadyFunctions (jQuery used on the page is too old)!');
- // Sizzle calls - jQuery.find
- // works for jQuery 1.3+ (released Jan 13 2009)
- phantomas.spy(val, 'find', function(selector, context) {
- phantomas.incrMetric('jQuerySizzleCalls');
- phantomas.emit('onSizzleCall', selector + ' (context: ' + (phantomas.getDOMPath(context) || 'unknown') + ')');
-
- phantomas.enterContext({
- type: 'jQuery - find',
- callDetails: {
- context: {
- length: this.length,
- firstElementPath: phantomas.getDOMPath(context)
- },
- arguments: [selector]
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result) {
- var moreData = {
- resultsNumber : (result && result.length) ? result.length : 0
- };
- phantomas.leaveContext(moreData);
- }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
-
- // $().bind - jQuery.bind
- // works for jQuery v?.?
- phantomas.spy(jQueryFn, 'bind', function(eventTypes, func) {
-
- phantomas.enterContext({
- type: 'jQuery - bind',
- callDetails: {
- context: {
- length: this.length,
- firstElementPath: phantomas.getDOMPath(this[0]),
- selector: this.selector
- },
- arguments: [eventTypes, func]
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result) {
- phantomas.leaveContext();
- }) || phantomas.log('jQuery: can not measure jQueryBindCalls (jQuery used on the page is too old)!');
- // Add spys on many jQuery functions
- jQueryFunctions.forEach(function(functionName) {
- var capitalizedName = functionName.substring(0,1).toUpperCase() + functionName.substring(1);
-
- phantomas.spy(jQueryFn, functionName, function(args) {
- // Clean args
- args = [].slice.call(arguments);
- args.forEach(function(arg, index) {
-
- if (arg instanceof Object) {
-
- if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) {
-
- arg = phantomas.getDOMPath(arg[0]) || 'unknown';
- } else if (arg instanceof HTMLElement) {
-
- arg = phantomas.getDOMPath(arg) || 'unknown';
- } else if (typeof arg === 'function') {
- arg = '(function)';
- } else {
-
- try {
- arg = JSON.stringify(arg);
- } catch(e) {
- arg = '[Object]';
- }
-
- }
- }
- if ((typeof arg === 'string' || arg instanceof String) && arg.length > 200) {
- arg = arg.substring(0, 200) + '...';
- }
- if (typeof arg === 'function') {
- arg = '(function)';
- }
- if (arg === true) {
- arg = 'true';
- }
- if (arg === false) {
- arg = 'false';
- }
- if (arg === null) {
- arg = 'null';
- }
- if (typeof arg !== 'number' && typeof arg !== 'string' && !(arg instanceof String)) {
- arg = 'undefined';
- }
- args[index] = arg;
- });
- phantomas.enterContext({
- type: 'jQuery - ' + functionName,
- callDetails: {
- context: {
- length: this.length,
- firstElementPath: phantomas.getDOMPath(this[0])
- },
- arguments: args
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result) {
- phantomas.leaveContext();
- }) || phantomas.log('jQuery: can not track jQuery - ' + capitalizedName + ' (this version of jQuery doesn\'t support it)');
- });
- });
- window.__defineGetter__('jQuery', function() {
- return jQuery;
- });
- })(window.__phantomas);
- }, jQueryFunctions);
- });
- // count Sizzle calls to detect duplicated queries
- var Collection = require('../../../../../../node_modules/phantomas/lib/collection'),
- sizzleCalls = new Collection(),
- jQueryLoading = new Collection();
- phantomas.on('onSizzleCall', function(request) {
- sizzleCalls.push(request);
- });
- phantomas.on('jQueryLoaded', function(version) {
- jQueryLoading.push(version);
- });
- phantomas.on('report', function() {
- sizzleCalls.sort().forEach(function(id, cnt) {
- if (cnt > 1) {
- phantomas.incrMetric('jQuerySizzleCallsDuplicated');
- phantomas.addOffender('jQuerySizzleCallsDuplicated', '%s: %d', id, cnt);
- }
- });
- jQueryLoading.forEach(function(version) {
- phantomas.incrMetric('jQueryDifferentVersions');
- phantomas.addOffender('jQueryDifferentVersions', '%s', version);
- });
- });
- };
|