jsExecutionTransformer.js 5.8 KB

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