jsExecutionTransformer.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. var debug = require('debug')('ylt:jsExecutionTransformer');
  2. var offendersHelpers = require('../offendersHelpers');
  3. var Collection = require('./phantomas/custom_modules/util/collection');
  4. var jsExecutionTransformer = function() {
  5. this.transform = function(data) {
  6. var javascriptExecutionTree = {};
  7. var scrollExecutionTree = {};
  8. var jQueryFunctionsCollection = new Collection();
  9. var metrics = {
  10. DOMaccesses: 0,
  11. DOMaccessesOnScroll: 0,
  12. queriesWithoutResults: 0,
  13. jQueryCalls: 0,
  14. jQueryCallsOnEmptyObject: 0,
  15. jQueryNotDelegatedEvent: 0
  16. };
  17. var offenders = {
  18. };
  19. try {
  20. debug('Starting JS execution transformation');
  21. javascriptExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.javascriptExecutionTree[0]);
  22. if (javascriptExecutionTree.children) {
  23. javascriptExecutionTree.children.forEach(function(node) {
  24. var contextLenght = (node.data.callDetails && node.data.callDetails.context) ? node.data.callDetails.context.length : null;
  25. if ((node.data.type === 'jQuery - bind' || node.data.type === 'jQuery - on') && contextLenght > 3) {
  26. metrics.jQueryNotDelegatedEvent += contextLenght - 1;
  27. node.warning = true;
  28. }
  29. if (node.data.resultsNumber === 0) {
  30. metrics.queriesWithoutResults ++;
  31. node.warning = true;
  32. }
  33. if (contextLenght === 0) {
  34. metrics.jQueryCallsOnEmptyObject ++;
  35. node.warning = true;
  36. }
  37. if (node.data.type.indexOf('jQuery - ') === 0) {
  38. metrics.jQueryCalls ++;
  39. jQueryFunctionsCollection.push(node.data.type);
  40. }
  41. // Mark errors with an error flag
  42. if (node.data.type === 'error' || node.data.type === 'jQuery version change') {
  43. node.error = true;
  44. }
  45. // Mark a performance flag
  46. if (['domInteractive', 'domContentLoaded', 'domContentLoadedEnd', 'domComplete'].indexOf(node.data.type) >= 0) {
  47. node.windowPerformance = true;
  48. }
  49. // Read the execution tree and adjust the navigation timings (cause their not very well synchronised)
  50. switch(node.data.type) {
  51. case 'domInteractive':
  52. data.toolsResults.phantomas.metrics.domInteractive = node.data.timestamp;
  53. break;
  54. case 'domContentLoaded':
  55. data.toolsResults.phantomas.metrics.domContentLoaded = node.data.timestamp;
  56. break;
  57. case 'domContentLoadedEnd':
  58. data.toolsResults.phantomas.metrics.domContentLoadedEnd = node.data.timestamp;
  59. break;
  60. case 'domComplete':
  61. data.toolsResults.phantomas.metrics.domComplete = node.data.timestamp;
  62. break;
  63. }
  64. // Transform domPaths into objects
  65. changeListOfDomPaths(node);
  66. // Count the number of DOM accesses, by counting the tree leafs
  67. metrics.DOMaccesses += countTreeLeafs(node);
  68. });
  69. // Count the number of different jQuery functions called
  70. if (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded) {
  71. metrics.jQueryFunctionsUsed = 0;
  72. offenders.jQueryFunctionsUsed = [];
  73. jQueryFunctionsCollection.sort().forEach(function(fnName, cnt) {
  74. if (fnName === 'jQuery - find') {
  75. fnName = 'jQuery - $';
  76. }
  77. metrics.jQueryFunctionsUsed ++;
  78. offenders.jQueryFunctionsUsed.push({
  79. functionName: fnName.substring(9),
  80. count: cnt
  81. });
  82. });
  83. }
  84. }
  85. debug('JS execution transformation complete');
  86. debug('Starting scroll execution transformation');
  87. offenders.scrollExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.scrollExecutionTree[0]);
  88. if (offenders.scrollExecutionTree.children) {
  89. offenders.scrollExecutionTree.children.forEach(function(node) {
  90. // Mark a event flag
  91. if (['documentScroll', 'windowScroll', 'window.onscroll'].indexOf(node.data.type) >= 0) {
  92. node.windowPerformance = true;
  93. }
  94. // Transform domPaths into objects
  95. changeListOfDomPaths(node);
  96. // Count the number of DOM accesses, by counting the tree leafs
  97. metrics.DOMaccessesOnScroll += countTreeLeafs(node);
  98. });
  99. }
  100. debug('Scroll execution transformation complete');
  101. } catch(err) {
  102. throw err;
  103. }
  104. data.javascriptExecutionTree = javascriptExecutionTree;
  105. data.toolsResults.jsExecutionTransformer = {
  106. metrics: metrics,
  107. offenders: offenders
  108. };
  109. return data;
  110. };
  111. function treeRecursiveParser(node, fn) {
  112. if (node.children) {
  113. node.children.forEach(function(child) {
  114. treeRecursiveParser(child, fn);
  115. });
  116. }
  117. fn(node);
  118. }
  119. function changeListOfDomPaths(rootNode) {
  120. treeRecursiveParser(rootNode, function(node) {
  121. if (node.data.callDetails && node.data.callDetails.context && node.data.callDetails.context.length > 0) {
  122. node.data.callDetails.context.elements = node.data.callDetails.context.elements.map(offendersHelpers.domPathToDomElementObj, offendersHelpers);
  123. }
  124. if (node.data.type === 'appendChild' || node.data.type === 'insertBefore' || node.data.type === 'getComputedStyle') {
  125. node.data.callDetails.arguments[0] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[0]);
  126. }
  127. if (node.data.type === 'insertBefore') {
  128. node.data.callDetails.arguments[1] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[1]);
  129. }
  130. });
  131. }
  132. // Returns the number of leafs (nodes without children)
  133. function countTreeLeafs(rootNode) {
  134. var count = 0;
  135. treeRecursiveParser(rootNode, function(node) {
  136. if (!node.children &&
  137. !node.error &&
  138. !node.windowPerformance &&
  139. node.data.type !== 'jQuery loaded') {
  140. count ++;
  141. }
  142. });
  143. return count;
  144. }
  145. };
  146. module.exports = new jsExecutionTransformer();