jQYLT.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. 'bind',
  60. 'unbind',
  61. // more events
  62. 'blur',
  63. 'change',
  64. 'click',
  65. 'dblclick',
  66. 'error',
  67. 'focus',
  68. 'focusin',
  69. 'focusout',
  70. 'hover',
  71. 'keydown',
  72. 'keypress',
  73. 'keyup',
  74. 'load',
  75. 'mousedown',
  76. 'mouseenter',
  77. 'mouseleave',
  78. 'mousemove',
  79. 'mouseout',
  80. 'mouseover',
  81. 'mouseup',
  82. 'resize',
  83. 'scroll',
  84. 'select',
  85. 'submit',
  86. 'toggle',
  87. 'unload',
  88. // attributes
  89. 'attr',
  90. 'prop',
  91. 'removeAttr',
  92. 'removeProp',
  93. 'val',
  94. 'hasClass',
  95. 'addClass',
  96. 'removeClass',
  97. 'toggleClass'
  98. ];
  99. // spy calls to jQuery functions
  100. phantomas.once('init', function() {
  101. phantomas.evaluate(function(jQueryFunctions) {
  102. (function(phantomas) {
  103. var jQuery;
  104. // TODO: create a helper - phantomas.spyGlobalVar() ?
  105. window.__defineSetter__('jQuery', function(val) {
  106. var version;
  107. var jQueryFn;
  108. var oldJQuery = jQuery;
  109. if (!val || !val.fn) {
  110. phantomas.log('jQuery: unable to detect version!');
  111. return;
  112. }
  113. version = val.fn.jquery;
  114. jQuery = val;
  115. jQueryFn = val.fn;
  116. // Older jQuery (v?.?) compatibility
  117. if (!jQueryFn) {
  118. jQueryFn = jQuery;
  119. }
  120. phantomas.log('jQuery: loaded v' + version);
  121. phantomas.setMetric('jQueryVersion', version);
  122. phantomas.emit('jQueryLoaded', version);
  123. phantomas.pushContext({
  124. type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded',
  125. callDetails: {
  126. arguments: ['version ' + version]
  127. },
  128. backtrace: phantomas.getBacktrace()
  129. });
  130. // jQuery.ready.promise
  131. // works for jQuery 1.8.0+ (released Aug 09 2012)
  132. phantomas.spy(val.ready, 'promise', function(func) {
  133. phantomas.incrMetric('jQueryOnDOMReadyFunctions');
  134. phantomas.pushContext({
  135. type: 'jQuery - onDOMReady',
  136. callDetails: {
  137. arguments: [func]
  138. },
  139. backtrace: phantomas.getBacktrace()
  140. });
  141. }) || phantomas.log('jQuery: can not measure jQueryOnDOMReadyFunctions (jQuery used on the page is too old)!');
  142. // Sizzle calls - jQuery.find
  143. // works for jQuery 1.3+ (released Jan 13 2009)
  144. phantomas.spy(val, 'find', function(selector, context) {
  145. phantomas.incrMetric('jQuerySizzleCalls');
  146. phantomas.emit('onSizzleCall', selector + ' (context: ' + (phantomas.getDOMPath(context) || 'unknown') + ')');
  147. phantomas.enterContext({
  148. type: 'jQuery - find',
  149. callDetails: {
  150. context: {
  151. length: this.length,
  152. firstElementPath: phantomas.getDOMPath(context)
  153. },
  154. arguments: [selector]
  155. },
  156. backtrace: phantomas.getBacktrace()
  157. });
  158. }, function(result) {
  159. var moreData = {
  160. resultsNumber : (result && result.length) ? result.length : 0
  161. };
  162. phantomas.leaveContext(moreData);
  163. }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
  164. // Add spys on many jQuery functions
  165. jQueryFunctions.forEach(function(functionName) {
  166. var capitalizedName = functionName.substring(0,1).toUpperCase() + functionName.substring(1);
  167. phantomas.spy(jQueryFn, functionName, function(args) {
  168. // Clean args
  169. args = [].slice.call(arguments);
  170. args.forEach(function(arg, index) {
  171. if (arg instanceof Object) {
  172. if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) {
  173. arg = phantomas.getDOMPath(arg[0]) || 'unknown';
  174. } else if (arg instanceof HTMLElement) {
  175. arg = phantomas.getDOMPath(arg) || 'unknown';
  176. } else if (typeof arg === 'function') {
  177. arg = '(function)';
  178. } else {
  179. try {
  180. arg = JSON.stringify(arg);
  181. } catch(e) {
  182. arg = '[Object]';
  183. }
  184. }
  185. }
  186. if ((typeof arg === 'string' || arg instanceof String) && arg.length > 200) {
  187. arg = arg.substring(0, 200) + '...';
  188. }
  189. if (typeof arg === 'function') {
  190. arg = '(function)';
  191. }
  192. if (arg === true) {
  193. arg = 'true';
  194. }
  195. if (arg === false) {
  196. arg = 'false';
  197. }
  198. if (arg === null) {
  199. arg = 'null';
  200. }
  201. if (typeof arg !== 'number' && typeof arg !== 'string' && !(arg instanceof String)) {
  202. arg = 'undefined';
  203. }
  204. args[index] = arg;
  205. });
  206. phantomas.enterContext({
  207. type: 'jQuery - ' + functionName,
  208. callDetails: {
  209. context: {
  210. length: this.length,
  211. firstElementPath: phantomas.getDOMPath(this[0])
  212. },
  213. arguments: args
  214. },
  215. backtrace: phantomas.getBacktrace()
  216. });
  217. }, function(result) {
  218. phantomas.leaveContext();
  219. }) || phantomas.log('jQuery: can not track jQuery - ' + capitalizedName + ' (this version of jQuery doesn\'t support it)');
  220. });
  221. });
  222. window.__defineGetter__('jQuery', function() {
  223. return jQuery;
  224. });
  225. })(window.__phantomas);
  226. }, jQueryFunctions);
  227. });
  228. // count Sizzle calls to detect duplicated queries
  229. var Collection = require('../../../../../../node_modules/phantomas/lib/collection'),
  230. sizzleCalls = new Collection(),
  231. jQueryLoading = new Collection();
  232. phantomas.on('onSizzleCall', function(request) {
  233. sizzleCalls.push(request);
  234. });
  235. phantomas.on('jQueryLoaded', function(version) {
  236. jQueryLoading.push(version);
  237. });
  238. phantomas.on('report', function() {
  239. sizzleCalls.sort().forEach(function(id, cnt) {
  240. if (cnt > 1) {
  241. phantomas.incrMetric('jQuerySizzleCallsDuplicated');
  242. phantomas.addOffender('jQuerySizzleCallsDuplicated', '%s: %d', id, cnt);
  243. }
  244. });
  245. jQueryLoading.forEach(function(version) {
  246. phantomas.incrMetric('jQueryDifferentVersions');
  247. phantomas.addOffender('jQueryDifferentVersions', '%s', version);
  248. });
  249. });
  250. };