jQYLT.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /**
  2. * Analyzes jQuery activity
  3. *
  4. * @see http://code.jquery.com/jquery-1.10.2.js
  5. * @see http://code.jquery.com/jquery-2.0.3.js
  6. */
  7. /* global document: true, window: true */
  8. /* jshint -W030 */
  9. exports.version = '0.2.a';
  10. exports.module = function(phantomas) {
  11. 'use strict';
  12. phantomas.setMetric('jQueryVersion', ''); // @desc version of jQuery framework (if loaded) [string]
  13. phantomas.setMetric('jQueryOnDOMReadyFunctions'); // @desc number of functions bound to onDOMReady event
  14. phantomas.setMetric('jQuerySizzleCalls'); // @desc number of calls to Sizzle (including those that will be resolved using querySelectorAll)
  15. phantomas.setMetric('jQuerySizzleCallsDuplicated'); // @desc number of calls on the same Sizzle request
  16. phantomas.setMetric('jQueryDifferentVersions'); //@desc number of different jQuery versions loaded on the page (not counting iframes)
  17. var jQueryFunctions = [
  18. // DOM manipulations
  19. 'html',
  20. 'append',
  21. 'appendTo',
  22. 'prepend',
  23. 'prependTo',
  24. 'before',
  25. 'insertBefore',
  26. 'after',
  27. 'insertAfter',
  28. 'remove',
  29. 'detach',
  30. 'empty',
  31. 'clone',
  32. 'replaceWith',
  33. 'replaceAll',
  34. 'text',
  35. 'wrap',
  36. 'wrapAll',
  37. 'wrapInner',
  38. 'unwrap',
  39. // Style manipulations
  40. 'css',
  41. 'offset',
  42. 'position',
  43. 'height',
  44. 'innerHeight',
  45. 'outerHeight',
  46. 'width',
  47. 'innerWidth',
  48. 'outerWidth',
  49. 'scrollLeft',
  50. 'scrollTop',
  51. // generic events
  52. 'on',
  53. 'off',
  54. 'live',
  55. 'die',
  56. 'delegate',
  57. 'undelegate',
  58. 'one',
  59. 'unbind',
  60. // more events
  61. 'blur',
  62. 'change',
  63. 'click',
  64. 'dblclick',
  65. 'error',
  66. 'focus',
  67. 'focusin',
  68. 'focusout',
  69. 'hover',
  70. 'keydown',
  71. 'keypress',
  72. 'keyup',
  73. 'load',
  74. 'mousedown',
  75. 'mouseenter',
  76. 'mouseleave',
  77. 'mousemove',
  78. 'mouseout',
  79. 'mouseover',
  80. 'mouseup',
  81. 'resize',
  82. 'scroll',
  83. 'select',
  84. 'submit',
  85. 'toggle',
  86. 'unload',
  87. // attributes
  88. 'attr',
  89. 'prop',
  90. 'removeAttr',
  91. 'removeProp',
  92. 'val',
  93. 'hasClass',
  94. 'addClass',
  95. 'removeClass',
  96. 'toggleClass'
  97. ];
  98. // spy calls to jQuery functions
  99. phantomas.once('init', function() {
  100. phantomas.evaluate(function(jQueryFunctions) {
  101. (function(phantomas) {
  102. var jQuery;
  103. // TODO: create a helper - phantomas.spyGlobalVar() ?
  104. window.__defineSetter__('jQuery', function(val) {
  105. var version;
  106. var jQueryFn;
  107. var oldJQuery = jQuery;
  108. if (!val || !val.fn) {
  109. phantomas.log('jQuery: unable to detect version!');
  110. return;
  111. }
  112. version = val.fn.jquery;
  113. jQuery = val;
  114. jQueryFn = val.fn;
  115. // Older jQuery (v?.?) compatibility
  116. if (!jQueryFn) {
  117. jQueryFn = jQuery;
  118. }
  119. phantomas.log('jQuery: loaded v' + version);
  120. phantomas.setMetric('jQueryVersion', version);
  121. phantomas.emit('jQueryLoaded', version);
  122. phantomas.pushContext({
  123. type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded',
  124. callDetails: {
  125. arguments: ['version ' + version]
  126. },
  127. backtrace: phantomas.getBacktrace()
  128. });
  129. // jQuery.ready.promise
  130. // works for jQuery 1.8.0+ (released Aug 09 2012)
  131. phantomas.spy(val.ready, 'promise', function(func) {
  132. phantomas.incrMetric('jQueryOnDOMReadyFunctions');
  133. phantomas.pushContext({
  134. type: 'jQuery - onDOMReady',
  135. callDetails: {
  136. arguments: [func]
  137. },
  138. backtrace: phantomas.getBacktrace()
  139. });
  140. }) || phantomas.log('jQuery: can not measure jQueryOnDOMReadyFunctions (jQuery used on the page is too old)!');
  141. // Sizzle calls - jQuery.find
  142. // works for jQuery 1.3+ (released Jan 13 2009)
  143. phantomas.spy(val, 'find', function(selector, context) {
  144. phantomas.incrMetric('jQuerySizzleCalls');
  145. phantomas.emit('onSizzleCall', selector + ' (context: ' + (phantomas.getDOMPath(context) || 'unknown') + ')');
  146. phantomas.enterContext({
  147. type: 'jQuery - find',
  148. callDetails: {
  149. context: {
  150. length: this.length,
  151. firstElementPath: phantomas.getDOMPath(context)
  152. },
  153. arguments: [selector]
  154. },
  155. backtrace: phantomas.getBacktrace()
  156. });
  157. }, function(result) {
  158. var moreData = {
  159. resultsNumber : (result && result.length) ? result.length : 0
  160. };
  161. phantomas.leaveContext(moreData);
  162. }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
  163. // $().bind - jQuery.bind
  164. // works for jQuery v?.?
  165. phantomas.spy(jQueryFn, 'bind', function(eventTypes, func) {
  166. phantomas.enterContext({
  167. type: 'jQuery - bind',
  168. callDetails: {
  169. context: {
  170. length: this.length,
  171. firstElementPath: phantomas.getDOMPath(this[0]),
  172. selector: this.selector
  173. },
  174. arguments: [eventTypes, func]
  175. },
  176. backtrace: phantomas.getBacktrace()
  177. });
  178. }, function(result) {
  179. phantomas.leaveContext();
  180. }) || phantomas.log('jQuery: can not measure jQueryBindCalls (jQuery used on the page is too old)!');
  181. // Add spys on many jQuery functions
  182. jQueryFunctions.forEach(function(functionName) {
  183. var capitalizedName = functionName.substring(0,1).toUpperCase() + functionName.substring(1);
  184. phantomas.spy(jQueryFn, functionName, function(args) {
  185. // Clean args
  186. args = [].slice.call(arguments);
  187. args.forEach(function(arg, index) {
  188. if (arg instanceof Object) {
  189. if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) {
  190. arg = phantomas.getDOMPath(arg[0]) || 'unknown';
  191. } else if (arg instanceof HTMLElement) {
  192. arg = phantomas.getDOMPath(arg) || 'unknown';
  193. } else if (typeof arg === 'function') {
  194. arg = '(function)';
  195. } else {
  196. try {
  197. arg = JSON.stringify(arg);
  198. } catch(e) {
  199. arg = '[Object]';
  200. }
  201. }
  202. }
  203. if ((typeof arg === 'string' || arg instanceof String) && arg.length > 200) {
  204. arg = arg.substring(0, 200) + '...';
  205. }
  206. if (typeof arg === 'function') {
  207. arg = '(function)';
  208. }
  209. if (arg === true) {
  210. arg = 'true';
  211. }
  212. if (arg === false) {
  213. arg = 'false';
  214. }
  215. if (arg === null) {
  216. arg = 'null';
  217. }
  218. if (typeof arg !== 'number' && typeof arg !== 'string' && !(arg instanceof String)) {
  219. arg = 'undefined';
  220. }
  221. args[index] = arg;
  222. });
  223. phantomas.enterContext({
  224. type: 'jQuery - ' + functionName,
  225. callDetails: {
  226. context: {
  227. length: this.length,
  228. firstElementPath: phantomas.getDOMPath(this[0])
  229. },
  230. arguments: args
  231. },
  232. backtrace: phantomas.getBacktrace()
  233. });
  234. }, function(result) {
  235. phantomas.leaveContext();
  236. }) || phantomas.log('jQuery: can not track jQuery - ' + capitalizedName + ' (this version of jQuery doesn\'t support it)');
  237. });
  238. });
  239. window.__defineGetter__('jQuery', function() {
  240. return jQuery;
  241. });
  242. })(window.__phantomas);
  243. }, jQueryFunctions);
  244. });
  245. // count Sizzle calls to detect duplicated queries
  246. var Collection = require('../../../../../../node_modules/phantomas/lib/collection'),
  247. sizzleCalls = new Collection(),
  248. jQueryLoading = new Collection();
  249. phantomas.on('onSizzleCall', function(request) {
  250. sizzleCalls.push(request);
  251. });
  252. phantomas.on('jQueryLoaded', function(version) {
  253. jQueryLoading.push(version);
  254. });
  255. phantomas.on('report', function() {
  256. sizzleCalls.sort().forEach(function(id, cnt) {
  257. if (cnt > 1) {
  258. phantomas.incrMetric('jQuerySizzleCallsDuplicated');
  259. phantomas.addOffender('jQuerySizzleCallsDuplicated', '%s: %d', id, cnt);
  260. }
  261. });
  262. jQueryLoading.forEach(function(version) {
  263. phantomas.incrMetric('jQueryDifferentVersions');
  264. phantomas.addOffender('jQueryDifferentVersions', '%s', version);
  265. });
  266. });
  267. };