jsExecutionTransformer.js 6.3 KB

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