jQYLT.js 14 KB

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