jQYLT.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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. // spy calls to jQuery functions
  112. phantomas.once('init', function() {
  113. phantomas.evaluate(function(jQueryFunctions) {
  114. (function(phantomas) {
  115. var jQuery;
  116. var oldJQuery;
  117. phantomas.spyGlobalVar('jQuery', function(jQuery) {
  118. var version;
  119. if (!jQuery || !jQuery.fn) {
  120. phantomas.log('jQuery: unable to detect version!');
  121. return;
  122. }
  123. // Tag the current version of jQuery to avoid multiple reports of jQuery being loaded
  124. // when it's actually only restored via $.noConflict(true) - see comments in #435
  125. if (jQuery.__phantomas === true) {
  126. phantomas.log('jQuery: this instance has already been seen by phantomas');
  127. return;
  128. }
  129. jQuery.__phantomas = true;
  130. // report the version of jQuery
  131. version = jQuery.fn.jquery;
  132. phantomas.emit('jQueryLoaded', version);
  133. phantomas.pushContext({
  134. type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded',
  135. callDetails: {
  136. arguments: ['version ' + version]
  137. },
  138. backtrace: phantomas.getBacktrace()
  139. });
  140. oldJQuery = version;
  141. // jQuery.ready.promise
  142. // works for jQuery 1.8.0+ (released Aug 09 2012)
  143. phantomas.spy(jQuery.ready, 'promise', function(func) {
  144. phantomas.incrMetric('jQueryOnDOMReadyFunctions');
  145. phantomas.addOffender('jQueryOnDOMReadyFunctions', phantomas.getCaller(3));
  146. phantomas.pushContext({
  147. type: 'jQuery - onDOMReady',
  148. callDetails: {
  149. arguments: [func]
  150. },
  151. backtrace: phantomas.getBacktrace()
  152. });
  153. }) || phantomas.log('jQuery: can not measure jQueryOnDOMReadyFunctions (jQuery used on the page is too old)!');
  154. // Sizzle calls - jQuery.find
  155. // works for jQuery 1.3+ (released Jan 13 2009)
  156. phantomas.spy(jQuery, 'find', function(selector, context) {
  157. phantomas.incrMetric('jQuerySizzleCalls');
  158. phantomas.addOffender('jQuerySizzleCalls', '%s (in %s)', selector, (phantomas.getDOMPath(context) || 'unknown'));
  159. phantomas.enterContext({
  160. type: 'jQuery - find',
  161. callDetails: {
  162. context: {
  163. length: this.length,
  164. firstElementPath: phantomas.getDOMPath(context)
  165. },
  166. arguments: [selector]
  167. },
  168. backtrace: phantomas.getBacktrace()
  169. });
  170. }, function(result) {
  171. var moreData = {
  172. resultsNumber : (result && result.length) ? result.length : 0
  173. };
  174. phantomas.leaveContext(moreData);
  175. }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
  176. /*if (!jQuery.event) {
  177. phantomas.spy(jQuery.event, 'trigger', function(ev, data, elem) {
  178. var path = phantomas.getDOMPath(elem),
  179. type = ev.type || ev;
  180. phantomas.log('Event: triggered "%s" on "%s"', type, path);
  181. phantomas.incrMetric('jQueryEventTriggers');
  182. phantomas.addOffender('jQueryEventTriggers', '"%s" on "%s"', type, path);
  183. });
  184. }*/
  185. // jQuery events bound to window' onLoad event (#451)
  186. phantomas.spy(jQuery.fn, 'on', function(eventName, func) {
  187. if ((eventName === 'load') && (this[0] === window)) {
  188. phantomas.incrMetric('jQueryWindowOnLoadFunctions');
  189. phantomas.addOffender('jQueryWindowOnLoadFunctions', phantomas.getCaller(2));
  190. phantomas.pushContext({
  191. type: 'jQuery - windowOnLoad',
  192. callDetails: {
  193. arguments: [func]
  194. },
  195. backtrace: phantomas.getBacktrace()
  196. });
  197. }
  198. });
  199. // Add spys on many jQuery functions
  200. jQueryFunctions.forEach(function(functionName) {
  201. var capitalizedName = functionName.substring(0,1).toUpperCase() + functionName.substring(1);
  202. phantomas.spy(jQuery.fn, functionName, function(args) {
  203. // Clean args
  204. args = [].slice.call(arguments);
  205. args.forEach(function(arg, index) {
  206. if (arg instanceof Object) {
  207. if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) {
  208. arg = phantomas.getDOMPath(arg[0]) || 'unknown';
  209. } else if (arg instanceof HTMLElement) {
  210. arg = phantomas.getDOMPath(arg) || 'unknown';
  211. } else if (typeof arg === 'function') {
  212. arg = '(function)';
  213. } else {
  214. try {
  215. arg = JSON.stringify(arg);
  216. } catch(e) {
  217. arg = '[Object]';
  218. }
  219. }
  220. }
  221. if ((typeof arg === 'string' || arg instanceof String) && arg.length > 200) {
  222. arg = arg.substring(0, 200) + '...';
  223. }
  224. if (typeof arg === 'function') {
  225. arg = '(function)';
  226. }
  227. if (arg === true) {
  228. arg = 'true';
  229. }
  230. if (arg === false) {
  231. arg = 'false';
  232. }
  233. if (arg === null) {
  234. arg = 'null';
  235. }
  236. if (typeof arg !== 'number' && typeof arg !== 'string' && !(arg instanceof String)) {
  237. arg = 'undefined';
  238. }
  239. args[index] = arg;
  240. });
  241. phantomas.enterContext({
  242. type: 'jQuery - ' + functionName,
  243. callDetails: {
  244. context: {
  245. length: this.length,
  246. firstElementPath: phantomas.getDOMPath(this[0])
  247. },
  248. arguments: args
  249. },
  250. backtrace: phantomas.getBacktrace()
  251. });
  252. }, function(result) {
  253. phantomas.leaveContext();
  254. }) || phantomas.log('jQuery: can not track jQuery - ' + capitalizedName + ' (this version of jQuery doesn\'t support it)');
  255. });
  256. });
  257. })(window.__phantomas);
  258. }, jQueryFunctions);
  259. });
  260. // store the last resource that was received
  261. // try to report where given jQuery version was loaded from
  262. phantomas.on('recv', function(entry) {
  263. if (entry.isJS) {
  264. lastUrl = entry.url;
  265. }
  266. });
  267. phantomas.on('jQueryLoaded', function(version) {
  268. phantomas.log('jQuery: loaded v' + version);
  269. phantomas.setMetric('jQueryVersion', version);
  270. // report multiple jQuery "instances" (issue #435)
  271. phantomas.incrMetric('jQueryVersionsLoaded');
  272. phantomas.addOffender('jQueryVersionsLoaded', 'v%s', version);
  273. phantomas.log('jQuery: v%s (probably loaded from <%s>)', version, lastUrl);
  274. });
  275. };