jQYLT.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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 = '1.0.a';
  10. exports.module = function(phantomas) {
  11. 'use strict';
  12. phantomas.setMetric('jQueryVersion', ''); // @desc version of jQuery framework (if loaded) [string]
  13. phantomas.setMetric('jQueryVersionsLoaded'); // @desc number of loaded jQuery "instances" (even in the same version)
  14. phantomas.setMetric('jQueryOnDOMReadyFunctions'); // @desc number of functions bound to onDOMReady event
  15. phantomas.setMetric('jQueryWindowOnLoadFunctions'); // @desc number of functions bound to windowOnLoad event
  16. phantomas.setMetric('jQuerySizzleCalls'); // @desc number of calls to Sizzle (including those that will be resolved using querySelectorAll)
  17. phantomas.setMetric('jQueryEventTriggers'); // @desc number of jQuery event triggers
  18. var jQueryFunctions = [
  19. // DOM manipulations
  20. 'html',
  21. 'append',
  22. 'appendTo',
  23. 'prepend',
  24. 'prependTo',
  25. 'before',
  26. 'insertBefore',
  27. 'after',
  28. 'insertAfter',
  29. 'remove',
  30. 'detach',
  31. 'empty',
  32. 'clone',
  33. 'replaceWith',
  34. 'replaceAll',
  35. 'text',
  36. 'wrap',
  37. 'wrapAll',
  38. 'wrapInner',
  39. 'unwrap',
  40. // Style manipulations
  41. 'css',
  42. 'offset',
  43. 'position',
  44. 'height',
  45. 'innerHeight',
  46. 'outerHeight',
  47. 'width',
  48. 'innerWidth',
  49. 'outerWidth',
  50. 'scrollLeft',
  51. 'scrollTop',
  52. // Animations
  53. 'hide',
  54. 'show',
  55. 'toggle',
  56. 'animate',
  57. 'fadeIn',
  58. 'fadeOut',
  59. 'fadeTo',
  60. 'fadeToggle',
  61. 'slideDown',
  62. 'slideUp',
  63. 'slideToggle',
  64. // Generic events
  65. 'on',
  66. 'off',
  67. 'live',
  68. 'die',
  69. 'delegate',
  70. 'undelegate',
  71. 'one',
  72. 'bind',
  73. 'unbind',
  74. // More events
  75. 'blur',
  76. 'change',
  77. 'click',
  78. 'dblclick',
  79. 'error',
  80. 'focus',
  81. 'focusin',
  82. 'focusout',
  83. 'hover',
  84. 'keydown',
  85. 'keypress',
  86. 'keyup',
  87. 'load',
  88. 'mousedown',
  89. 'mouseenter',
  90. 'mouseleave',
  91. 'mousemove',
  92. 'mouseout',
  93. 'mouseover',
  94. 'mouseup',
  95. 'resize',
  96. 'scroll',
  97. 'select',
  98. 'submit',
  99. 'unload',
  100. // Attributes
  101. 'attr',
  102. 'prop',
  103. 'removeAttr',
  104. 'removeProp',
  105. 'val',
  106. 'hasClass',
  107. 'addClass',
  108. 'removeClass',
  109. 'toggleClass',
  110. ];
  111. var jQueryTraversalFunctions = [
  112. 'children',
  113. 'closest',
  114. 'find',
  115. 'next',
  116. 'nextAll',
  117. 'nextUntil',
  118. 'offsetParent',
  119. 'parent',
  120. 'parents',
  121. 'parentsUntil',
  122. 'prev',
  123. 'prevAll',
  124. 'prevUntil',
  125. 'siblings'
  126. ];
  127. jQueryFunctions = jQueryFunctions.concat(jQueryTraversalFunctions);
  128. // spy calls to jQuery functions
  129. phantomas.on('init', function() {
  130. phantomas.evaluate(function(jQueryFunctions, jQueryTraversalFunctions) {
  131. (function(phantomas) {
  132. var oldJQuery;
  133. phantomas.spyGlobalVar('jQuery', function(jQuery) {
  134. var version;
  135. if (!jQuery || !jQuery.fn) {
  136. phantomas.log('jQuery: unable to detect version!');
  137. return;
  138. }
  139. // Tag the current version of jQuery to avoid multiple reports of jQuery being loaded
  140. // when it's actually only restored via $.noConflict(true) - see comments in #435
  141. if (jQuery.__phantomas === true) {
  142. phantomas.log('jQuery: this instance has already been seen by phantomas');
  143. return;
  144. }
  145. jQuery.__phantomas = true;
  146. // report the version of jQuery
  147. version = jQuery.fn.jquery;
  148. phantomas.emit('jQueryLoaded', version);
  149. phantomas.pushContext({
  150. type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded',
  151. callDetails: {
  152. arguments: ['version ' + version]
  153. },
  154. backtrace: phantomas.getBacktrace()
  155. });
  156. oldJQuery = version;
  157. // jQuery.ready.promise
  158. // works for jQuery 1.8.0+ (released Aug 09 2012)
  159. phantomas.spy(jQuery.ready, 'promise', function(func) {
  160. phantomas.incrMetric('jQueryOnDOMReadyFunctions');
  161. phantomas.addOffender('jQueryOnDOMReadyFunctions', phantomas.getCaller(3));
  162. phantomas.pushContext({
  163. type: 'jQuery - onDOMReady',
  164. callDetails: {
  165. arguments: [func]
  166. },
  167. backtrace: phantomas.getBacktrace()
  168. });
  169. }) || phantomas.log('jQuery: can not measure jQueryOnDOMReadyFunctions (jQuery used on the page is too old)!');
  170. // Sizzle calls - jQuery.find
  171. // works for jQuery 1.3+ (released Jan 13 2009)
  172. phantomas.spy(jQuery, 'find', function(selector, context) {
  173. phantomas.incrMetric('jQuerySizzleCalls');
  174. phantomas.addOffender('jQuerySizzleCalls', '%s (in %s)', selector, (phantomas.getDOMPath(context) || 'unknown'));
  175. phantomas.enterContext({
  176. type: 'jQuery - Sizzle call',
  177. callDetails: {
  178. context: {
  179. length: 1,
  180. elements: [phantomas.getDOMPath(context)]
  181. },
  182. arguments: [selector]
  183. },
  184. backtrace: phantomas.getBacktrace()
  185. });
  186. }, function(result) {
  187. var moreData = {
  188. resultsNumber : (result && result.length) ? result.length : 0
  189. };
  190. phantomas.leaveContext(moreData);
  191. }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
  192. phantomas.spy(jQuery.fn, 'init', function(selector, context) {
  193. if (typeof selector === 'string' && /^#([\w\-]*)$/.exec(selector) !== null && !context) {
  194. phantomas.enterContext({
  195. type: 'jQuery - find',
  196. callDetails: {
  197. arguments: [selector]
  198. },
  199. backtrace: phantomas.getBacktrace()
  200. });
  201. }
  202. }, function(result) {
  203. var data = phantomas.getContextData();
  204. if (data.type === 'jQuery - find' &&
  205. !data.callDetails.context &&
  206. data.callDetails.arguments.length === 1 &&
  207. /^#([\w\-]*)$/.exec(data.callDetails.arguments[0]) !== null) {
  208. var moreData = {
  209. resultsNumber : (result && result.length) ? result.length : 0
  210. };
  211. phantomas.leaveContext(moreData);
  212. }
  213. });
  214. if (!jQuery.event) {
  215. phantomas.spy(jQuery.event, 'trigger', function(ev, data, elem) {
  216. var path = phantomas.getDOMPath(elem),
  217. type = ev.type || ev;
  218. phantomas.log('Event: triggered "%s" on "%s"', type, path);
  219. phantomas.incrMetric('jQueryEventTriggers');
  220. phantomas.addOffender('jQueryEventTriggers', '"%s" on "%s"', type, path);
  221. });
  222. }
  223. // jQuery events bound to window' onLoad event (#451)
  224. phantomas.spy(jQuery.fn, 'on', function(eventName, func) {
  225. if ((eventName === 'load') && (this[0] === window)) {
  226. phantomas.incrMetric('jQueryWindowOnLoadFunctions');
  227. phantomas.addOffender('jQueryWindowOnLoadFunctions', phantomas.getCaller(2));
  228. }
  229. });
  230. // Add spys on many jQuery functions
  231. jQueryFunctions.forEach(function(functionName) {
  232. phantomas.spy(jQuery.fn, functionName, function(args) {
  233. // Clean args
  234. args = [].slice.call(arguments);
  235. args.forEach(function(arg, index) {
  236. if (arg instanceof Array) {
  237. arg = '[Array]';
  238. }
  239. if (arg instanceof Object) {
  240. if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) {
  241. arg = phantomas.getDOMPath(arg[0]) || 'unknown';
  242. } else if (arg instanceof HTMLElement) {
  243. arg = phantomas.getDOMPath(arg) || 'unknown';
  244. } else if (typeof arg === 'function') {
  245. arg = '(function)';
  246. } else {
  247. try {
  248. for (var key in arg) {
  249. if (typeof arg[key] === 'function') {
  250. arg[key] = '(function)';
  251. }
  252. }
  253. arg = JSON.stringify(arg);
  254. } catch(e) {
  255. arg = '[Object]';
  256. }
  257. }
  258. }
  259. if ((typeof arg === 'string' || arg instanceof String) && arg.length > 200) {
  260. arg = arg.substring(0, 200) + '...';
  261. }
  262. if (typeof arg === 'function') {
  263. arg = '(function)';
  264. }
  265. if (arg === true) {
  266. arg = 'true';
  267. }
  268. if (arg === false) {
  269. arg = 'false';
  270. }
  271. if (arg === null) {
  272. arg = 'null';
  273. }
  274. if (typeof arg !== 'number' && typeof arg !== 'string' && !(arg instanceof String)) {
  275. arg = 'undefined';
  276. }
  277. args[index] = arg;
  278. });
  279. var elements = [];
  280. for (var i = 0 ; i < this.length ; i++) {
  281. elements.push(phantomas.getDOMPath(this[i]));
  282. }
  283. phantomas.enterContext({
  284. type: 'jQuery - ' + functionName,
  285. callDetails: {
  286. context: {
  287. length: this.length,
  288. elements: elements
  289. },
  290. arguments: args
  291. },
  292. backtrace: phantomas.getBacktrace()
  293. });
  294. }, function(result) {
  295. if (jQueryTraversalFunctions.indexOf(functionName) >= 0) {
  296. var moreData = {
  297. resultsNumber : (result && result.length) ? result.length : 0
  298. };
  299. phantomas.leaveContext(moreData);
  300. } else {
  301. phantomas.leaveContext();
  302. }
  303. }) || phantomas.log('jQuery: can not track jQuery - ' + functionName + ' (this version of jQuery doesn\'t support it)');
  304. });
  305. });
  306. })(window.__phantomas);
  307. }, jQueryFunctions, jQueryTraversalFunctions);
  308. });
  309. phantomas.on('jQueryLoaded', function(version) {
  310. phantomas.log('jQuery: loaded v' + version);
  311. phantomas.setMetric('jQueryVersion', version);
  312. // report multiple jQuery "instances" (issue #435)
  313. phantomas.incrMetric('jQueryVersionsLoaded');
  314. phantomas.addOffender('jQueryVersionsLoaded', 'v%s', version);
  315. });
  316. };