From 5e82eea9f1595d0c373e8d05bcb5f560a92b33e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Wed, 13 Mar 2019 19:27:57 +0100 Subject: [PATCH] Remove JS Timeline and Scroll Bottlenecks --- front/src/css/timeline.css | 338 --------- front/src/js/app.js | 5 - front/src/js/controllers/timelineCtrl.js | 190 ------ .../src/js/directives/offendersDirectives.js | 644 ------------------ front/src/js/services/menuService.js | 3 - front/src/less/timeline.less | 387 ----------- front/src/main.html | 2 - front/src/views/resultSubHeader.html | 1 - front/src/views/rule.html | 33 - front/src/views/timeline.html | 100 --- lib/metadata/policies.js | 48 -- lib/metadata/scoreProfileGeneric.json | 17 - lib/runner.js | 4 - lib/server/controllers/frontController.js | 2 +- lib/tools/jsExecutionTransformer.js | 210 ------ .../custom_modules/modules/domQYLT/domQYLT.js | 387 ----------- .../custom_modules/modules/jQYLT/jQYLT.js | 381 ----------- .../javaScriptBottleYLT.js | 69 -- .../modules/jsTreeYLT/jsTreeYLT.js | 31 - .../modules/scrollListener/scrollListener.js | 69 -- .../modules/windowPerfYLT/windowPerfYLT.js | 150 ---- 21 files changed, 1 insertion(+), 3070 deletions(-) delete mode 100644 front/src/css/timeline.css delete mode 100644 front/src/js/controllers/timelineCtrl.js delete mode 100644 front/src/less/timeline.less delete mode 100644 front/src/views/timeline.html delete mode 100644 lib/tools/jsExecutionTransformer.js delete mode 100644 lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js delete mode 100644 lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js delete mode 100644 lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js delete mode 100644 lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js delete mode 100644 lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js delete mode 100644 lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js diff --git a/front/src/css/timeline.css b/front/src/css/timeline.css deleted file mode 100644 index 11920fb..0000000 --- a/front/src/css/timeline.css +++ /dev/null @@ -1,338 +0,0 @@ -/* Timeline colors, related to Window Performances */ -.execution { - text-align: center; -} -.selectScript { - padding-bottom: 2em; - font-size: 0.9em; -} -.selectScript select { - max-width: 30em; -} -.selectScript.empty { - font-size: 0.8em; -} -.selectScript.empty select { - width: 10em; -} -.timeline { - margin: 2em 0 5em; -} -.timeline .chart { - position: relative; - width: 100%; - border-bottom: 1px solid #000; -} -.timeline .startTime, -.timeline .endTime { - position: absolute; - bottom: 0.5em; - font-size: 0.8em; -} -.timeline .startTime { - left: 0em; -} -.timeline .endTime { - right: 0em; -} -.timeline .chartPoints { - display: table; - height: 100px; - width: 99%; - margin: 0 auto; -} -.timeline .interval { - display: table-cell; - position: relative; - height: 100px; - width: 0.5%; -} -.timeline .interval .color { - position: absolute; - bottom: 0; - width: 100%; -} -.timeline .interval .color.clickable { - cursor: pointer; -} -.timeline div.interval:hover { - background: #9C4274; -} -.timeline .interval:hover .color { - background: #F04DA7; -} -.timeline .domComplete.interval { - background: #EDE3FF; -} -.timeline .domComplete .color { - background: #C2A3FF; -} -.timeline .domContentLoadedEnd.interval { - background: #D8F0F0; -} -.timeline .domContentLoadedEnd .color { - background: #7ECCCC; -} -.timeline .domContentLoaded.interval { - background: #E0FFD1; -} -.timeline .domContentLoaded .color { - background: #A7E846; -} -.timeline .domInteractive.interval { - background: #FFFCCC; -} -.timeline .domInteractive .color { - background: #FFE433; -} -.timeline .domCreation.interval { - background: #FFE0CC; -} -.timeline .domCreation .color { - background: #FF6600; -} -.timeline .tooltip.detailsOverlay { - position: absolute; - display: none; - width: auto; - padding: 0.5em 1em; - top: -1.5em; - right: 1em; -} -.timeline .interval:hover .tooltip { - display: block; -} -.timeline .legend { - display: table; - width: 100%; - margin-top: 1em; -} -.timeline .legend > div { - display: table-row; -} -.timeline .legend > div > div { - position: relative; - display: table-cell; - margin-top: 1em; -} -.timeline .titles { - font-weight: bold; -} -.timeline .titles > div { - padding: 0 1em 0 2em; -} -.timeline .tips { - font-size: 0.7em; -} -.timeline .tips > div { - padding: 1em 1em 0 0; -} -.timeline .legend .color { - display: block; - position: absolute; - left: 0; - height: 1.5em; - width: 1.5em; - border-radius: 0.2em; -} -.filters { - margin: 1em auto; - padding: 0.5em; - min-width: 30em; - width: 30%; - border: 1px dotted #aaa; - text-align: left; -} -.subFilters { - margin-left: 3em; - font-size: 0.9em; -} -.table { - display: table; - width: 100%; - border-spacing: 0.25em; -} -.table > div, -.table > a { - display: table-row; -} -.table > .headers > div { - font-weight: bold; - padding: 0.5em 1em; -} -.table > div > div, -.table > a > div { - padding: 0.1em 1em; - background: #f2f2f2; - display: table-cell; - text-align: left; -} -.table > div.jsError > .type, -.table > div.jsError > .value { - color: #e74c3c; - font-weight: bold; -} -.table > div.windowPerformance > div, -.table > div.windowPerformance > div.startTime { - background: #EBD8E2; -} -.table > div.showingDetails > div { - background: #f1c40f; -} -.table > div.highlight > div.startTime { - background-color: #C0F090; -} -.table > div.highlight-remove { - transition: 3s; -} -.table > div.highlight-remove > div.startTime { - transition: background-color 3s ease-in; -} -.table > div > .index { - color: #bbb; - word-break: normal; -} -.table > div > .type { - white-space: nowrap; -} -.table .children { - margin-top: 0.2em; - font-size: 0.8em; - line-height: 1.6em; -} -.table .child { - margin-left: 0.5em; -} -.table .child > .child { - margin-left: 1em; -} -.table .child:before { - content: "↳"; -} -.table .child .childArgs { - display: none; -} -.table .child span { - position: relative; -} -.table .child span:hover { - background: #EBD8E2; -} -.table .child span:hover div { - display: inline-block; -} -.table .child span:hover .childArgs { - display: block; - position: absolute; - padding: 0 1em 0 2em; - left: 100%; - top: 0; - background: #EBD8E2; - line-height: 1.3em; - height: 1.3em; - z-index: 2; -} -.table .showingDetails .child span:hover { - background: inherit; -} -.table .showingDetails .child span:hover .childArgs { - display: none; -} -.table > div > .value { - width: 70%; - word-break: break-all; -} -.table > div > .details { - position: relative; -} -.table .details .icon-question { - color: #f1c40f; - cursor: pointer; -} -.table .icon-warning { - display: inline-block; - width: 0.8em; -} -.detailsOverlay { - display: none; - position: absolute; - right: 3em; - top: -3em; - width: 45em; - min-height: 1em; - padding: 0 1em 1em; - background: #fff; - border: 2px solid #f1c40f; - border-radius: 0.5em; - z-index: 2; -} -@media screen and (max-width: 1024px) { - .detailsOverlay { - width: 25em; - } -} -.showDetails .detailsOverlay { - display: block; -} -.detailsOverlay .closeBtn { - position: absolute; - top: 0.5em; - right: 0.5em; - color: #f1c40f; - cursor: pointer; -} -.detailsOverlay .advice { - color: #e74c3c; - font-weight: bold; -} -.detailsOverlay .trace { - word-break: break-all; -} -.table > div > .duration, -.table > div > .startTime { - text-align: center; - white-space: nowrap; -} -.table > div > .startTime.domComplete { - background: #EDE3FF; -} -.table > div > .startTime.domContentLoadedEnd { - background: #D8F0F0; -} -.table > div > .startTime.domContentLoaded { - background: #E0FFD1; -} -.table > div > .startTime.domInteractive { - background: #FFFCCC; -} -.table > div > .startTime.domCreation { - background: #FFE0CC; -} -.execution .icon-warning { - color: #e74c3c; - cursor: pointer; -} -.queryWithoutResultsFilterOn > div { - display: none; -} -.queryWithoutResultsFilterOn > div.queryWithoutResults { - display: table-row; -} -.jQueryCallOnEmptyObjectFilterOn > div { - display: none; -} -.jQueryCallOnEmptyObjectFilterOn > div.jQueryCallOnEmptyObject { - display: table-row; -} -.eventNotDelegatedFilterOn > div { - display: none; -} -.eventNotDelegatedFilterOn > div.eventNotDelegated { - display: table-row; -} -.jsErrorFilterOn > div { - display: none; -} -.jsErrorFilterOn > div.jsError { - display: table-row; -} diff --git a/front/src/js/app.js b/front/src/js/app.js index ef3d2b2..cdaf143 100644 --- a/front/src/js/app.js +++ b/front/src/js/app.js @@ -7,7 +7,6 @@ var yltApp = angular.module('YellowLabTools', [ 'queueCtrl', 'ruleCtrl', 'screenshotCtrl', - 'timelineCtrl', 'runsFactory', 'resultsFactory', 'apiService', @@ -64,10 +63,6 @@ yltApp.config(['$routeProvider', '$locationProvider', templateUrl: 'views/dashboard.html', controller: 'DashboardCtrl' }). - when('/result/:runId/timeline', { - templateUrl: 'views/timeline.html', - controller: 'TimelineCtrl' - }). when('/result/:runId/screenshot', { templateUrl: 'views/screenshot.html', controller: 'ScreenshotCtrl' diff --git a/front/src/js/controllers/timelineCtrl.js b/front/src/js/controllers/timelineCtrl.js deleted file mode 100644 index e23fee1..0000000 --- a/front/src/js/controllers/timelineCtrl.js +++ /dev/null @@ -1,190 +0,0 @@ -var timelineCtrl = angular.module('timelineCtrl', []); - -timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$timeout', 'Menu', 'Results', 'API', function($scope, $rootScope, $routeParams, $location, $timeout, Menu, Results, API) { - $scope.runId = $routeParams.runId; - $scope.Menu = Menu.setCurrentPage('timeline', $scope.runId); - - function loadResults() { - // Load result if needed - if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) { - Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) { - $rootScope.loadedResult = result; - $scope.result = result; - render(); - }); - } else { - $scope.result = $rootScope.loadedResult; - render(); - } - } - - function render() { - initFilters(); - initScriptFiltering(); - initExecutionTree(); - initTimeline(); - $timeout(initProfiler, 100); - } - - function initFilters() { - var hash = $location.hash(); - var filter = null; - - if (hash.indexOf('filter=') === 0) { - filter = hash.substr(7); - } - - $scope.warningsFilterOn = (filter !== null); - $scope.warningsFilters = { - queryWithoutResults: (filter === null || filter === 'queryWithoutResults'), - jQueryCallOnEmptyObject: (filter === null || filter === 'jQueryCallOnEmptyObject'), - eventNotDelegated: (filter === null || filter === 'eventNotDelegated'), - jsError: (filter === null || filter === 'jsError') - }; - } - - function initScriptFiltering() { - var offenders = $scope.result.rules.totalRequests.offendersObj.list.byType.js; - $scope.scripts = []; - - offenders.forEach(function(filePath) { - var shortPath = filePath; - - if (filePath.length > 100) { - shortPath = filePath.substr(0, 98) + '...'; - } - - var scriptObj = { - fullPath: filePath, - shortPath: shortPath - }; - - $scope.scripts.push(scriptObj); - }); - } - - function initExecutionTree() { - var originalExecutions = $scope.result.javascriptExecutionTree.children || []; - - // Detect the last event of all (before filtering) and read time - var lastEvent = originalExecutions[originalExecutions.length - 1]; - $scope.endTime = lastEvent.data.timestamp + (lastEvent.data.time || 0); - - // Filter - $scope.executionTree = []; - originalExecutions.forEach(function(node) { - - // Filter by script (if enabled) - if ($scope.selectedScript) { - if (node.data.backtrace && node.data.backtrace.indexOf($scope.selectedScript.fullPath + ':') === -1) { - return; - } - if (node.data.type === "jQuery loaded" || node.data.type === "jQuery version change") { - return; - } - } - - $scope.executionTree.push(node); - }); - } - - function initTimeline() { - - // Split the timeline into 200 intervals - var numberOfIntervals = 199; - $scope.timelineIntervalDuration = $scope.endTime / numberOfIntervals; - - // Pre-fill array of as many elements as there are milleseconds - var millisecondsArray = Array.apply(null, new Array($scope.endTime + 1)).map(Number.prototype.valueOf,0); - - // Create the milliseconds array from the execution tree - $scope.executionTree.forEach(function(node) { - if (node.data.time !== undefined) { - - // Ignore artefacts (durations > 100ms) - var time = Math.min(node.data.time, 100) || 1; - - for (var i=node.data.timestamp, max=node.data.timestamp + time ; i 0) { - break; - } - } - - return lineIndex; - }; - - - $scope.backToDashboard = function() { - $location.path('/result/' + $scope.runId); - }; - - $scope.testAgain = function() { - API.relaunchTest($scope.result); - }; - - loadResults(); - -}]); - -timelineCtrl.directive('scrollOnClick', ['$animate', '$timeout', function($animate, $timeout) { - return { - restrict: 'A', - link: function (scope, element, attributes) { - // When the user clicks on the timeline, find the right profiler line and scroll to it - element.on('click', function() { - var lineIndex = scope.findLineIndexByTimestamp(attributes.scrollOnClick); - var lineElement = angular.element(document.getElementById('line_' + lineIndex)); - - // Animate the background color to "flash" the row - lineElement.addClass('highlight'); - $timeout(function() { - $animate.removeClass(lineElement, 'highlight'); - scope.$digest(); - }, 50); - - - window.scrollTo(0, lineElement[0].offsetTop); - }); - } - }; -}]); diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js index 434598a..9375973 100644 --- a/front/src/js/directives/offendersDirectives.js +++ b/front/src/js/directives/offendersDirectives.js @@ -93,511 +93,6 @@ }; }); - - function getJQueryContextButtonHTML(context, onASingleLine) { - if (context.length === 0) { - return 'Empty jQuery object'; - } - - if (context.length === 1) { - return getDomElementButtonHTML(context.elements[0], onASingleLine); - } - - var html = context.length + ' elements (' + getDomElementButtonHTML(context.elements[0], onASingleLine) + ', ' + getDomElementButtonHTML(context.elements[1], onASingleLine); - if (context.length === 3) { - html += ', ' + getDomElementButtonHTML(context.elements[0], onASingleLine); - } else if (context.length > 3) { - html += ' and ' + (context.length - 2) + ' more...'; - } - return html + ')'; - } - - function isJQuery(node) { - return node.data.type.indexOf('jQuery ') === 0; - } - - function getNonJQueryHTML(node, onASingleLine) { - var type = node.data.type; - - if (node.windowPerformance) { - switch (type) { - case 'documentScroll': - return '(triggering the scroll event on document)'; - - case 'windowScroll': - return '(triggering the scroll event on window)'; - - case 'window.onscroll': - return '(calling the window.onscroll function)'; - - default: - return ''; - } - } - - if (!node.data.callDetails) { - return ''; - } - - var args = node.data.callDetails.arguments; - var ctxt = node.data.callDetails.context; - - - switch (type) { - case 'getElementById': - case 'createElement': - return '' + args[0] + ''; - - case 'getElementsByClassName': - case 'getElementsByTagName': - case 'querySelector': - case 'querySelectorAll': - return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); - - case 'appendChild': - return 'append ' + getDomElementButtonHTML(args[0], onASingleLine) + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); - - case 'insertBefore': - return 'insert ' + getDomElementButtonHTML(args[0], onASingleLine) + ' into ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine) + ' before ' + getDomElementButtonHTML(args[1], onASingleLine); - - case 'addEventListener': - return 'bind ' + args[0] + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); - - case 'getComputedStyle': - return getDomElementButtonHTML(args[0], onASingleLine) + (args[1] || ''); - - case 'error': - return args[0]; - - case 'jQuery - onDOMReady': - return '(function)'; - - case 'documentScroll': - return 'The scroll event just triggered on document'; - - case 'windowScroll': - return 'The scroll event just triggered on window'; - - case 'window.onscroll': - return 'The window.onscroll function just got called'; - - default: - return ''; - } - } - - function getJQueryHTML(node, onASingleLine) { - var type = node.data.type; - var unescapedArgs = node.data.callDetails.arguments; - var args = []; - var ctxt = node.data.callDetails.context; - - // escape HTML in args - for (var i = 0 ; i < 4 ; i ++) { - if (unescapedArgs[i] !== undefined) { - args[i] = escapeHTML(unescapedArgs[i]); - } - } - - if (type === 'jQuery loaded' || type === 'jQuery version change') { - return args[0]; - } - - switch (type) { - case 'jQuery - onDOMReady': - case 'jQuery - windowOnLoad': - return '(function)'; - - case 'jQuery - Sizzle call': - return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); - - case 'jQuery - find': - if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') { - return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return '' + args[0] + ''; - } - break; - - case 'jQuery - html': - if (args[0] !== undefined) { - return 'set content "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'get content from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - append': - return 'append ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - appendTo': - return 'append ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[0] + ''; - - case 'jQuery - prepend': - return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - prependTo': - return 'prepend ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[0] + ''; - - case 'jQuery - before': - return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - insertBefore': - return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' before ' + args[0] + ''; - - case 'jQuery - after': - return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - insertAfter': - return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' after ' + args[0] + ''; - - case 'jQuery - remove': - case 'jQuery - detach': - if (args[0]) { - return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by ' + args[0] + ''; - } else { - return getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - empty': - case 'jQuery - clone': - case 'jQuery - unwrap': - case 'jQuery - show': - case 'jQuery - hide': - case 'jQuery - animate': - case 'jQuery - fadeIn': - case 'jQuery - fadeOut': - case 'jQuery - fadeTo': - case 'jQuery - fadeToggle': - case 'jQuery - slideDown': - case 'jQuery - slideUp': - case 'jQuery - slideToggle': - return getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - replaceWith': - return 'replace ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' with ' + args[0] + ''; - - case 'jQuery - replaceAll': - return 'replace ' + args[0] + ' with ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - text': - if (args[0] !== undefined) { - return 'set text "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'get text from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - wrap': - case 'jQuery - wrapAll': - return 'wrap ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within ' + args[0] + ''; - - case 'jQuery - wrapInner': - return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within ' + args[0] + ''; - - case 'jQuery - css': - case 'jQuery - attr': - case 'jQuery - prop': - if (isStringOfObject(args[0])) { - return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else if (args[1]) { - return 'set ' + args[0] + ' : ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'get ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - offset': - case 'jQuery - height': - case 'jQuery - innerHeight': - case 'jQuery - width': - case 'jQuery - innerWidth': - case 'jQuery - scrollLeft': - case 'jQuery - scrollTop': - case 'jQuery - position': - if (args[0]) { - return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - outerHeight': - case 'jQuery - outerWidth': - if (args[0] && args[0] !== 'true') { - return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else if (args[0] === 'true') { - return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' (with include margins option)'; - } else { - return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - toggle': - if (args[0] === 'true') { - return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to visible'; - } else if (args[0] === 'false') { - return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to hidden'; - } else { - return getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - on': - case 'jQuery - one': - if (isStringOfObject(args[0])) { - return '' + args[0].replace(/"\(function\)"/g, '(function)') + ''; - } else if (args[1] && isPureString(args[1])) { - return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + ''; - } else { - return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - off': - if (args[0]) { - if (args[1]) { - return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + ''; - } else { - return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - } else { - return 'unbind all events'; - } - break; - - case 'jQuery - live': - case 'jQuery - bind': - return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - die': - case 'jQuery - unbind': - if (args[0]) { - return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'unbind all events'; - } - break; - - case 'jQuery - delegate': - return 'bind ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[0] + ''; - - case 'jQuery - undelegate': - if (args[0]) { - if (args[1]) { - return 'unbind ' + args[1] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[0] + ''; - } else { - return 'unbind namespace ' + args[0] + ''; - } - } else { - return 'unbind all events'; - } - break; - - case 'jQuery - blur': - case 'jQuery - change': - case 'jQuery - click': - case 'jQuery - dblclick': - case 'jQuery - focus': - case 'jQuery - keydown': - case 'jQuery - keypress': - case 'jQuery - keyup': - case 'jQuery - mousedown': - case 'jQuery - mouseenter': - case 'jQuery - mouseleave': - case 'jQuery - mousemove': - case 'jQuery - mouseout': - case 'jQuery - mouseover': - case 'jQuery - mouseup': - case 'jQuery - resize': - case 'jQuery - scroll': - case 'jQuery - select': - case 'jQuery - submit': - if (args[0]) { - return 'bind on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'triggered on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - error': - case 'jQuery - focusin': - case 'jQuery - focusout': - case 'jQuery - hover': - case 'jQuery - load': - case 'jQuery - unload': - return 'bind on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - removeAttr': - case 'jQuery - removeProp': - return 'remove ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - val': - if (args[0]) { - return 'set value ' + args[0] + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } else { - return 'get value from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - hasClass': - case 'jQuery - addClass': - case 'jQuery - removeClass': - return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - toggleClass': - if (args[0]) { - if (args[1]) { - return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[1] + ''; - } else { - return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - } else { - return 'magic no-argument toggleClass'; - } - break; - - case 'jQuery - children': - if (args[0]) { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by ' + args[0] + ''; - } else { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - closest': - if (args[1]) { - return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' in context ' + args[1] + ''; - } else { - return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - next': - case 'jQuery - nextAll': - if (args[0]) { - return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; - } else { - return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - nextUntil': - if (args[0]) { - if (args[1]) { - return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + ''; - } else { - return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ''; - } - } else { - return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - offsetParent': - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - - case 'jQuery - prev': - case 'jQuery - prevAll': - if (args[0]) { - return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; - } else { - return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - prevUntil': - if (args[0]) { - if (args[1]) { - return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + ''; - } else { - return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ''; - } - } else { - return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - parent': - case 'jQuery - parents': - if (args[0]) { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; - } else { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - parentsUntil': - if (args[0]) { - if (args[1]) { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + ''; - } else { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ''; - } - } else { - return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - case 'jQuery - siblings': - if (args[0]) { - return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; - } else { - return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine); - } - break; - - default: - return ''; - } - } - - function escapeHTML(html) { - var entityMap = { - "&": "&", - "<": "<", - ">": ">", - '"': '"', - "'": ''', - "/": '/' - }; - - return String(html).replace(/[&<>"'\/]/g, function (s) { - return entityMap[s]; - }); - } - - function joinArgs(args) { - var html = '' + args[0] + ''; - if (args[1]) { - html += ', ' + args[1] + ''; - if (args[2]) { - html += ', ' + args[2] + ''; - if (args[3]) { - html += ', and more...'; - } - } - } - return html; - } - - function isStringOfObject(str) { - return typeof str === 'string' && str[0] === '{' && str[str.length - 1] === '}'; - } - - function isPureString(str) { - return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown'; - } - - function getTimelineParamsHTML(node, onASingleLine) { - if (isJQuery(node)) { - return getJQueryHTML(node, onASingleLine); - } else { - return getNonJQueryHTML(node, onASingleLine); - } - } - function getBacktraceHTML(backtrace) { var html = ''; var parsedBacktrace = parseBacktrace(backtrace); @@ -687,145 +182,6 @@ return out; } - function getTimelineDetailsHTML(node) { - var html = ''; - - if (node.data.type != 'jQuery loaded' && node.data.type != 'jQuery version change' && !node.windowPerformance) { - if (node.warning || node.error) { - html += '
'; - } else { - html += '
'; - } - - html += '
'; - html += '
'; - - if (node.data.callDetails.context && node.data.callDetails.context.length === 0) { - html += '

Called on 0 jQuery element

Useless function call, as the jQuery object is empty.

'; - } else if (node.eventNotDelegated) { - html += '

This binding should use Event Delegation instead of binding each element one by one.

'; - } - - if (node.data.resultsNumber === 0) { - html += '

The query returned 0 results. Could it be unused or dead code?

'; - } else if (node.data.resultsNumber > 0) { - html += '

The query returned ' + node.data.resultsNumber + ' ' + (node.data.resultsNumber > 1 ? 'results' : 'result') + '.

'; - } - - if (node.data.backtrace) { - html += '

Backtrace

'; - html += '
'; - html += getBacktraceHTML(node.data.backtrace); - html += '
'; - } - - html += '
'; - } - - return html; - } - - - offendersDirectives.directive('profilerLine', ['$filter', function($filter) { - - var numberWithCommas = $filter('number'); - - function getProfilerLineHTML(index, node) { - return '
' + (index + 1) + '
' + - '
' + node.data.type + (node.children ? '
' + recursiveChildrenHTML(node) + '
' : '') + '
' + - '
' + getTimelineParamsHTML(node, false) + '
' + - '
' + getTimelineDetailsHTML(node) + '
' + - '
' + numberWithCommas(node.data.timestamp, 0) + ' ms
'; - } - - function recursiveChildrenHTML(node) { - var html = ''; - - if (node.children) { - node.children.forEach(function(child) { - html += '
' + child.data.type + '
' + getTimelineParamsHTML(child, true) + '
' + recursiveChildrenHTML(child) + '
'; - }); - } - - return html; - } - - function onDetailsClick(row) { - // Close if it's already open - if (row.classList.contains('showDetails')) { - closeDetails(row); - return; - } - - // Close any other open details overlay - var openOnes = document.getElementsByClassName('showDetails'); - if (openOnes.length > 0) { - openOnes[0].classList.remove('showDetails'); - } - - // Make it appear - row.classList.add('showDetails'); - - // Bind the close button - row.querySelector('.closeBtn').addEventListener('click', function() { - closeDetails(row); - }); - } - - function closeDetails(row) { - row.classList.remove('showDetails'); - - // Unbind the close button - row.querySelector('.closeBtn').removeEventListener('click', closeDetails); - } - - return { - restrict: 'E', - scope: { - index: '=', - node: '=' - }, - template: '
', - replace: true, - link: function(scope, element) { - - if (scope.node.error) { - element.addClass('jsError'); - } else if (scope.node.windowPerformance) { - element.addClass('windowPerformance'); - } - - element.append(getProfilerLineHTML(scope.index, scope.node)); - element[0].id = 'line_' + scope.index; - - if (scope.node.warning) { - element[0].classList.add('warning'); - - if (scope.node.queryWithoutResults) { - element[0].classList.add('queryWithoutResults'); - } - - if (scope.node.jQueryCallOnEmptyObject) { - element[0].classList.add('jQueryCallOnEmptyObject'); - } - - if (scope.node.eventNotDelegated) { - element[0].classList.add('eventNotDelegated'); - } - } - - - // Bind click on the details icon - var detailsIcon = element[0].querySelector('.details div'); - if (detailsIcon) { - detailsIcon.addEventListener('click', function() { - onDetailsClick(this.parentNode.parentNode); - }); - } - } - }; - }]); - function shortenUrl(url, maxLength) { if (!maxLength) { maxLength = 110; diff --git a/front/src/js/services/menuService.js b/front/src/js/services/menuService.js index 7b56c74..9ef3c37 100644 --- a/front/src/js/services/menuService.js +++ b/front/src/js/services/menuService.js @@ -22,9 +22,6 @@ menuService.factory('Menu', ['$location', function($location) { case 'dashboard': $location.path('/result/' + currentRunId); break; - case 'timeline': - $location.path('/result/' + currentRunId + '/timeline'); - break; default: console.err('Undefined Menu.changePage() destination'); } diff --git a/front/src/less/timeline.less b/front/src/less/timeline.less deleted file mode 100644 index d59beac..0000000 --- a/front/src/less/timeline.less +++ /dev/null @@ -1,387 +0,0 @@ -/* Timeline colors, related to Window Performances */ -@domCreationColor: #FF6600; -@domCreationBg: #FFE0CC; -@domContentLoadedColor: #A7E846; -@domContentLoadedBg: #E0FFD1; -@domContentLoadedEndColor: #7ECCCC; -@domContentLoadedEndBg: #D8F0F0; -@domCompleteColor: #C2A3FF; -@domCompleteBg: #EDE3FF; -@domInteractiveColor: #FFE433; -@domInteractiveBg: #FFFCCC; - -.execution { - text-align: center; -} - -.selectScript { - padding-bottom: 2em; - font-size: 0.9em; - - select { - max-width: 30em; - } - - &.empty { - font-size: 0.8em; - - select { - width: 10em; - } - } -} - -.timeline { - margin: 2em 0 5em; -} -.timeline .chart { - position: relative; - width: 100%; - border-bottom: 1px solid #000; -} -.timeline .startTime, .timeline .endTime { - position: absolute; - bottom: 0.5em; - font-size: 0.8em; -} -.timeline .startTime { - left: 0em; -} -.timeline .endTime { - right: 0em; -} -.timeline .chartPoints { - display: table; - height: 100px; - width: 99%; - margin: 0 auto; -} -.timeline .interval { - display: table-cell; - position: relative; - height: 100px; - width: 0.5%; -} -.timeline .interval .color { - position: absolute; - bottom: 0; - width: 100%; - - &.clickable { - cursor: pointer; - } -} -.timeline div.interval:hover { - background: #9C4274; -} -.timeline .interval:hover .color { - background: #F04DA7; -} -.timeline .domComplete.interval { - background: @domCompleteBg; -} -.timeline .domComplete .color { - background: @domCompleteColor; -} -.timeline .domContentLoadedEnd.interval { - background: @domContentLoadedEndBg; -} -.timeline .domContentLoadedEnd .color { - background: @domContentLoadedEndColor; -} -.timeline .domContentLoaded.interval { - background: @domContentLoadedBg; -} -.timeline .domContentLoaded .color { - background: @domContentLoadedColor; -} -.timeline .domInteractive.interval { - background: @domInteractiveBg; -} -.timeline .domInteractive .color { - background: @domInteractiveColor; -} -.timeline .domCreation.interval { - background: @domCreationBg; -} -.timeline .domCreation .color { - background: @domCreationColor; -} -.timeline .tooltip.detailsOverlay { - position: absolute; - display: none; - width: auto; - padding: 0.5em 1em; - top: -1.5em; - right: 1em; -} -.timeline .interval:hover .tooltip { - display: block; -} - -.timeline .legend { - display: table; - width: 100%; - margin-top: 1em; -} -.timeline .legend > div { - display: table-row; -} -.timeline .legend > div > div { - position: relative; - display: table-cell; - margin-top: 1em; -} -.timeline .titles { - font-weight: bold; -} -.timeline .titles > div { - padding: 0 1em 0 2em; -} -.timeline .tips { - font-size: 0.7em; -} -.timeline .tips > div { - padding: 1em 1em 0 0; -} -.timeline .legend .color { - display: block; - position: absolute; - left: 0; - height: 1.5em; - width: 1.5em; - border-radius: 0.2em; -} - -.filters { - margin: 1em auto; - padding: 0.5em; - min-width: 30em; - width: 30%; - border: 1px dotted #aaa; - text-align: left; -} - -.subFilters { - margin-left: 3em; - font-size: 0.9em; -} - -.table { - display: table; - width: 100%; - border-spacing: 0.25em; -} - -.table > div, -.table > a { - display: table-row; -} - -.table > .headers > div { - font-weight: bold; - padding: 0.5em 1em; -} - -.table > div > div, -.table > a > div { - padding: 0.1em 1em; - background: #f2f2f2; - display: table-cell; - text-align: left; -} -.table > div.jsError > .type, .table > div.jsError > .value { - color: #e74c3c; - font-weight: bold; -} -.table > div.windowPerformance > div, .table > div.windowPerformance > div.startTime { - background: #EBD8E2; -} -.table > div.showingDetails > div { - background: #f1c40f; -} - -.table > div.highlight { - > div.startTime { - background-color: #C0F090; - } -} -.table > div.highlight-remove { - transition: 3s; - - > div.startTime { - transition: background-color 3s ease-in; - } -} - -.table > div > .index { - color: #bbb; - word-break: normal; -} - -.table > div > .type { - white-space:nowrap; -} - -.table .children { - margin-top: 0.2em; - font-size: 0.8em; - line-height: 1.6em; -} - -.table .child { - margin-left: 0.5em; - - > .child { - margin-left: 1em; - } - - &:before { - content: "↳"; - } - - .childArgs { - display: none; - } - - span { - position: relative; - } - - span:hover { - background: #EBD8E2; - - div { - display: inline-block; - } - - .childArgs { - display: block; - position: absolute; - padding: 0 1em 0 2em; - left: 100%; - top: 0; - background: #EBD8E2; - line-height: 1.3em; - height: 1.3em; - z-index: 2; - } - } -} - -.table .showingDetails .child span:hover { - background: inherit; - - .childArgs { - display: none; - } -} - -.table > div > .value { - width: 70%; - word-break: break-all; -} - -.table > div > .details { - position: relative; -} -.table .details .icon-question { - color: #f1c40f; - cursor: pointer; -} -.table .icon-warning { - display: inline-block; - width: 0.8em; -} - -.detailsOverlay { - display: none; - position: absolute; - right: 3em; - top: -3em; - width: 45em; - min-height: 1em; - padding: 0 1em 1em; - background: #fff; - border: 2px solid #f1c40f; - border-radius: 0.5em; - z-index: 2; -} -@media screen and (max-width: 1024px) { - .detailsOverlay { - width: 25em; - } -} -.showDetails .detailsOverlay { - display: block; -} -.detailsOverlay .closeBtn { - position: absolute; - top: 0.5em; - right: 0.5em; - color: #f1c40f; - cursor: pointer; -} -.detailsOverlay .advice { - color: #e74c3c; - font-weight: bold; -} -.detailsOverlay .trace { - word-break: break-all; -} - -.table > div > .duration, .table > div > .startTime { - text-align: center; - white-space:nowrap; -} -.table > div > .startTime.domComplete { - background: @domCompleteBg; -} -.table > div > .startTime.domContentLoadedEnd { - background: @domContentLoadedEndBg; -} -.table > div > .startTime.domContentLoaded { - background: @domContentLoadedBg; -} -.table > div > .startTime.domInteractive { - background: @domInteractiveBg; -} -.table > div > .startTime.domCreation { - background: @domCreationBg; -} -.execution .icon-warning { - color: #e74c3c; - cursor: pointer; -} -.queryWithoutResultsFilterOn { - > div { - display: none; - &.queryWithoutResults { - display: table-row; - } - } -} -.jQueryCallOnEmptyObjectFilterOn { - > div { - display: none; - &.jQueryCallOnEmptyObject { - display: table-row; - } - } -} -.eventNotDelegatedFilterOn { - > div { - display: none; - &.eventNotDelegated { - display: table-row; - } - } -} -.jsErrorFilterOn { - > div { - display: none; - &.jsError { - display: table-row; - } - } -} \ No newline at end of file diff --git a/front/src/main.html b/front/src/main.html index eec3f07..231289c 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -14,7 +14,6 @@ - @@ -51,7 +50,6 @@ - diff --git a/front/src/views/resultSubHeader.html b/front/src/views/resultSubHeader.html index 27de375..d31262a 100644 --- a/front/src/views/resultSubHeader.html +++ b/front/src/views/resultSubHeader.html @@ -4,5 +4,4 @@
New test
Test again
- \ No newline at end of file diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 2348306..105da75 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -229,19 +229,6 @@ -
-

The table below shows the interactions between the JavaScript and the DOM on a scroll event.

-
-
-
-
Type
-
Params
-
-
- -
-
-

This is the colors palette, sized by total occurrences:

@@ -378,26 +365,6 @@
-
-

{{rule.value}} offenders

- Please open the JS timeline -
- -
-

{{rule.value}} offenders

- Please open the JS timeline, filtered by "Queries without results" -
- -
-

{{rule.value}} offenders

- Please open the JS timeline, filtered by "jQuery calls on empty object" -
- -
-

{{rule.value}} offenders

- Please open the JS timeline, filtered by "Events not delegated" -
-

Breakpoints distribution graph

diff --git a/front/src/views/timeline.html b/front/src/views/timeline.html deleted file mode 100644 index b774c9a..0000000 --- a/front/src/views/timeline.html +++ /dev/null @@ -1,100 +0,0 @@ -
-
-
- Filter timeline and profiler by script: - -
- -

JavaScript Timeline

-

This graph gives a quick view of when the JavaScript interactions with the DOM occur during the loading of the page.

- -
-
-
-
-
-
-
- Timestamp: {{$index * timelineIntervalDuration | number: 0}} ms -
-
-
-
-
0 ms
-
{{endTime | number: 0}} ms
-
-
-
-
DOM creation
-
DOM interactive
-
DOM content loaded event
-
Page completion
-
Page is complete
-
-
-
Executing JavaScript and DOM queries here is a bad practice and slows down the DOM construction.
-
Some frameworks do things here, but it's not reliable and should be avoided.
-
Also known as "document ready". This is where you should execute top-priority scripts, like binding action buttons or launch a video player.
-
Here you can execute mid-priority tasks. Loading a script with createElement('script') is one way to do so.
-
The page is considered loaded, it's time for low priority things : trackers, social plugins, easter egg...
-
-
-
- -

JavaScript Profiler

-

- The table below shows the interactions between the JavaScript and the DOM. It is useful to understand what happens while the page loads. -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- {{queryWithoutResultsFilterOn}} -
-
-
-
-
Type
-
Params
-
-
Timestamp
-
- - - -
- - -
\ No newline at end of file diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index 7346224..17ca8ac 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -66,24 +66,6 @@ var policies = { }; } }, - "DOMaccesses": { - "tool": "jsExecutionTransformer", - "label": "DOM access", - "message": "

This metric counts the number of calls to DOM related functions (both native DOM functions and jQuery functions) on page load.

The more your JavaScript code accesses the DOM, the slower the page will load.

Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.

Try to reduce the number of queries by refactoring your JavaScript code.

Binding too many events also has a cost. Try to use event delegation as much as possible.

", - "isOkThreshold": 200, - "isBadThreshold": 2000, - "isAbnormalThreshold": 4000, - "hasOffenders": false - }, - "queriesWithoutResults": { - "tool": "jsExecutionTransformer", - "label": "Queries without result", - "message": "

Number of queries that return no result. Both native and jQuery DOM requests are counted.

It suggests the query is not used on the page, probably because it is some dead code.

Or maybe the code is trying to find an HTML block that is not always here. Look at the JS Timeline to see if the scripts correctly figures out the HTML block is not here and immediatly stops interacting further with the DOM.

", - "isOkThreshold": 0, - "isBadThreshold": 150, - "isAbnormalThreshold": 250, - "hasOffenders": false - }, "DOMqueriesAvoidable": { "tool": "phantomas", "label": "Duplicated DOM queries", @@ -147,18 +129,6 @@ var policies = { }; } }, - "DOMaccessesOnScroll": { - "tool": "jsExecutionTransformer", - "label": "DOM access on scroll", - "message": "

This rule counts the number of DOM-accessing functions calls on a scroll event, such as queries, readings, writings, bindings and jQuery functions.

Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.

One of the main reasons of a poor scrolling experience is when too much JS is executed on each scroll event. Note that some devices such as smartphones and MacBooks send more scroll events than others.

Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use throttling or debouncing.

", - "isOkThreshold": 1, - "isBadThreshold": 20, - "isAbnormalThreshold": 35, - "hasOffenders": true, - "offendersTransformFn": function(offenders) { - return offenders; - } - }, "jsErrors": { "tool": "phantomas", "label": "JavaScript errors", @@ -348,24 +318,6 @@ var policies = { "isAbnormalThreshold": 2, "hasOffenders": true }, - "jQueryCallsOnEmptyObject": { - "tool": "jsExecutionTransformer", - "label": "Calls on empty objects", - "message": "

This metric counts the number of jQuery functions called on an empty jQuery object. The call was useless.

This can be helpful to detect dead or unused code.

", - "isOkThreshold": 1, - "isBadThreshold": 100, - "isAbnormalThreshold": 180, - "hasOffenders": false - }, - "jQueryNotDelegatedEvents": { - "tool": "jsExecutionTransformer", - "label": "Events not delegated", - "message": "

This is the number of events that are bound with the .bind() or the .on() function without using event delegation.

This means jQuery binds each element contained in the object one by one. This is bad for performance.

", - "isOkThreshold": 1, - "isBadThreshold": 100, - "isAbnormalThreshold": 180, - "hasOffenders": false - }, "cssParsingErrors": { "tool": "phantomas", "label": "CSS syntax error", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index eb453e8..b4f75e7 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -32,21 +32,6 @@ "DOMidDuplicated": 1 } }, - "domManipulations": { - "label": "DOM manipulations", - "policies": { - "DOMaccesses": 3, - "queriesWithoutResults": 1, - "DOMqueriesAvoidable": 1 - } - }, - "scroll": { - "label": "Scroll bottlenecks", - "policies": { - "eventsScrollBound": 1, - "DOMaccessesOnScroll": 4 - } - }, "badJavascript": { "label": "Bad JavaScript", "policies": { @@ -116,8 +101,6 @@ "pageWeight": 3, "requests": 3, "domComplexity": 2, - "domManipulations": 2, - "scroll": 1, "badJavascript": 2, "jQuery": 1, "cssSyntaxError": 1, diff --git a/lib/runner.js b/lib/runner.js index 3648a73..aae7275 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -2,7 +2,6 @@ var Q = require('q'); var debug = require('debug')('ylt:runner'); var phantomasWrapper = require('./tools/phantomas/phantomasWrapper'); -var jsExecutionTransformer = require('./tools/jsExecutionTransformer'); var colorDiff = require('./tools/colorDiff'); var mediaQueriesChecker = require('./tools/mediaQueriesChecker'); var isHttp2 = require('./tools/isHttp2'); @@ -28,9 +27,6 @@ var Runner = function(params) { .then(function(phantomasResults) { data.toolsResults.phantomas = phantomasResults; - // Treat the JS Execution Tree from offenders - data = jsExecutionTransformer.transform(data); - // Compare colors data = colorDiff.compareAllColors(data); diff --git a/lib/server/controllers/frontController.js b/lib/server/controllers/frontController.js index 83b66c3..1a652fb 100644 --- a/lib/server/controllers/frontController.js +++ b/lib/server/controllers/frontController.js @@ -10,7 +10,7 @@ var FrontController = function(app) { var assetsPath = (app.get('env') === 'development') ? '../../../front/src' : '../../../front/build'; // Routes templating - var routes = ['/', '/about', '/result/:runId', '/result/:runId/timeline', '/result/:runId/screenshot', '/result/:runId/rule/:policy', '/queue/:runId']; + var routes = ['/', '/about', '/result/:runId', '/result/:runId/screenshot', '/result/:runId/rule/:policy', '/queue/:runId']; routes.forEach(function(route) { app.get(route, function(req, res) { diff --git a/lib/tools/jsExecutionTransformer.js b/lib/tools/jsExecutionTransformer.js deleted file mode 100644 index a28cff6..0000000 --- a/lib/tools/jsExecutionTransformer.js +++ /dev/null @@ -1,210 +0,0 @@ -var debug = require('debug')('ylt:jsExecutionTransformer'); - -var offendersHelpers = require('../offendersHelpers'); -var Collection = require('./phantomas/custom_modules/util/collection'); - -var jsExecutionTransformer = function() { - - this.transform = function(data) { - var javascriptExecutionTree = {}; - var jQueryFunctionsCollection = new Collection(); - - var metrics = { - domInteractive: 0, - domContentLoaded: 0, - domContentLoadedEnd: 0, - domComplete: 0, - - DOMaccesses: 0, - DOMaccessesOnScroll: 0, - queriesWithoutResults: 0 - }; - - var offenders = {}; - - var hasjQuery = (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded > 0); - if (hasjQuery) { - metrics.jQueryCalls = 0; - metrics.jQueryCallsOnEmptyObject = 0; - metrics.jQueryNotDelegatedEvents = 0; - } - - try { - - debug('Starting JS execution transformation'); - javascriptExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.javascriptExecutionTree[0]); - - if (javascriptExecutionTree.children) { - javascriptExecutionTree.children.forEach(function(node, index) { - - var contextLength = (node.data.callDetails && node.data.callDetails.context) ? node.data.callDetails.context.length : null; - - if (isABindWithoutEventDelegation(node, contextLength)) { - metrics.jQueryNotDelegatedEvents += contextLength; - node.warning = true; - node.eventNotDelegated = true; - } - - if (node.data.resultsNumber === 0) { - metrics.queriesWithoutResults ++; - node.queryWithoutResults = true; - node.warning = true; - } - - if (contextLength === 0) { - metrics.jQueryCallsOnEmptyObject ++; - node.jQueryCallOnEmptyObject = true; - node.warning = true; - } - - if (node.data.type.indexOf('jQuery - ') === 0) { - metrics.jQueryCalls ++; - jQueryFunctionsCollection.push(node.data.type); - } - - // Mark errors with an error flag - if (node.data.type === 'error' || node.data.type === 'jQuery version change') { - node.error = true; - } - - // Mark a performance flag - if (['domInteractive', 'domContentLoaded', 'domContentLoadedEnd', 'domComplete'].indexOf(node.data.type) >= 0) { - node.windowPerformance = true; - - // Adjust the navigation timings (cause their not very well synchronised) - switch(node.data.type) { - case 'domInteractive': - javascriptExecutionTree.data.domInteractive = node.data.timestamp; - break; - case 'domContentLoaded': - javascriptExecutionTree.data.domContentLoaded = node.data.timestamp; - break; - case 'domContentLoadedEnd': - javascriptExecutionTree.data.domContentLoadedEnd = node.data.timestamp; - break; - case 'domComplete': - javascriptExecutionTree.data.domComplete = node.data.timestamp; - break; - } - } - // Fix rare bug when domComplete was never triggered - if (index === javascriptExecutionTree.children.length - 1 && !javascriptExecutionTree.data.domComplete) { - javascriptExecutionTree.data.domComplete = node.data.timestamp + 1000; - } - - // Transform domPaths into objects - changeListOfDomPaths(node); - - // Count the number of DOM accesses, by counting the tree leafs - metrics.DOMaccesses += countTreeLeafs(node); - }); - - } - debug('JS execution transformation complete'); - - - if (data.toolsResults.phantomas.offenders.scrollExecutionTree) { - debug('Starting scroll execution transformation'); - offenders.DOMaccessesOnScroll = JSON.parse(data.toolsResults.phantomas.offenders.scrollExecutionTree[0]); - if (offenders.DOMaccessesOnScroll.children) { - offenders.DOMaccessesOnScroll.children.forEach(function(node) { - - // Mark a event flag - if (['documentScroll', 'windowScroll', 'window.onscroll'].indexOf(node.data.type) >= 0) { - node.windowPerformance = true; - } - - // Transform domPaths into objects - changeListOfDomPaths(node); - - // Count the number of DOM accesses, by counting the tree leafs - metrics.DOMaccessesOnScroll += countTreeLeafs(node); - }); - } - debug('Scroll execution transformation complete'); - } else { - debug('Could not parse scrollExecutionTree'); - } - - } catch(err) { - throw err; - } - - data.javascriptExecutionTree = javascriptExecutionTree; - - data.toolsResults.jsExecutionTransformer = { - metrics: metrics, - offenders: offenders - }; - - return data; - }; - - function treeRecursiveParser(node, fn) { - if (node.children) { - node.children.forEach(function(child) { - treeRecursiveParser(child, fn); - }); - } - fn(node); - } - - function changeListOfDomPaths(rootNode) { - treeRecursiveParser(rootNode, function(node) { - - if (node.data.callDetails && node.data.callDetails.context && node.data.callDetails.context.length > 0) { - node.data.callDetails.context.elements = node.data.callDetails.context.elements.map(offendersHelpers.domPathToDomElementObj, offendersHelpers); - } - - if (node.data.type === 'appendChild' || node.data.type === 'insertBefore' || node.data.type === 'getComputedStyle') { - node.data.callDetails.arguments[0] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[0]); - } - - if (node.data.type === 'insertBefore') { - node.data.callDetails.arguments[1] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[1]); - } - }); - } - - // Returns the number of leafs (nodes without children) - function countTreeLeafs(rootNode) { - var count = 0; - - treeRecursiveParser(rootNode, function(node) { - if (!node.children && - !node.error && - !node.windowPerformance && - node.data.type !== 'jQuery loaded') { - count ++; - } - }); - - return count; - } - - function isPureString(str) { - return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown' && str !== 'null'; - } - - function isABindWithoutEventDelegation(node, contextLength) { - // Count only on larger bindings - if (contextLength <= 3) { - return false; - } - - if (node.data.type === 'jQuery - on' && node.data.callDetails.arguments[1] && !isPureString(node.data.callDetails.arguments[1])) { - return true; - } - - if (node.data.type.indexOf('jQuery - ') === 0 && node.children && node.children.length === 1) { - var child = node.children[0]; - if (child.data.type === 'jQuery - on' && child.data.callDetails.arguments[1] && !isPureString(child.data.callDetails.arguments[1])) { - return true; - } - } - - return false; - } -}; - -module.exports = new jsExecutionTransformer(); \ No newline at end of file diff --git a/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js deleted file mode 100644 index 5e963da..0000000 --- a/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js +++ /dev/null @@ -1,387 +0,0 @@ -/** - * Analyzes DOM queries done via native DOM methods - */ -/* global Element: true, Document: true, Node: true, window: true */ - -exports.version = '0.10.a'; - -exports.module = function(phantomas) { - 'use strict'; - - phantomas.setMetric('DOMqueries'); // @desc number of all DOM queries @offenders - phantomas.setMetric('DOMqueriesWithoutResults'); // @desc number of DOM queries that retutned nothing @offenders - phantomas.setMetric('DOMqueriesById'); // @desc number of document.getElementById calls - phantomas.setMetric('DOMqueriesByClassName'); // @desc number of document.getElementsByClassName calls - phantomas.setMetric('DOMqueriesByTagName'); // @desc number of document.getElementsByTagName calls - phantomas.setMetric('DOMqueriesByQuerySelectorAll'); // @desc number of document.querySelector(All) calls - phantomas.setMetric('DOMinserts'); // @desc number of DOM nodes inserts - phantomas.setMetric('DOMqueriesDuplicated'); // @desc number of DOM queries called more than once - phantomas.setMetric('DOMqueriesAvoidable'); // @desc number of repeated uses of a duplicated query - - // fake native DOM functions - phantomas.on('init', function() { - phantomas.evaluate(function() { - (function(phantomas) { - function querySpy(type, query, fnName, context, hasNoResults) { - phantomas.emit('domQuery', type, query, fnName, context, hasNoResults); // @desc DOM query has been made - } - - phantomas.spy(Document.prototype, 'getElementById', function(id) { - phantomas.incrMetric('DOMqueriesById'); - phantomas.addOffender('DOMqueriesById', '#%s (in %s)', id, '#document'); - - phantomas.enterContext({ - type: 'getElementById', - callDetails: { - arguments: ['#' + id] - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result, args) { - var id = args ? '#' + args[0] : undefined; - - querySpy('id', id, 'getElementById', '#document', (result === null)); - - var moreData = { - resultsNumber : (result === null) ? 0 : 1 - }; - phantomas.leaveContext(moreData); - }); - - // selectors by class name - function selectorClassNameSpyBefore(className) { - /*jshint validthis: true */ - - var context = phantomas.getDOMPath(this); - - phantomas.incrMetric('DOMqueriesByClassName'); - phantomas.addOffender('DOMqueriesByClassName', '.%s (in %s)', className, context); - - phantomas.enterContext({ - type: 'getElementsByClassName', - callDetails: { - context: { - length: 1, - elements: [context] - }, - arguments: ['.' + className] - }, - backtrace: phantomas.getBacktrace() - }); - } - - function selectorClassNameAfter(result, args) { - /*jshint validthis: true */ - - var className = args ? '.' + args[0] : undefined; - var context = phantomas.getDOMPath(this); - - querySpy('class', className, 'getElementsByClassName', context, (result.length === 0)); - - var moreData = { - resultsNumber : (result && result.length > 0) ? result.length : 0 - }; - phantomas.leaveContext(moreData); - } - - phantomas.spy(Document.prototype, 'getElementsByClassName', selectorClassNameSpyBefore, selectorClassNameAfter); - phantomas.spy(Element.prototype, 'getElementsByClassName', selectorClassNameSpyBefore, selectorClassNameAfter); - - // selectors by tag name - function selectorTagNameSpyBefore(tagName) { - /*jshint validthis: true */ - - var context = phantomas.getDOMPath(this); - - phantomas.incrMetric('DOMqueriesByTagName'); - phantomas.addOffender('DOMqueriesByTagName', '%s (in %s)', tagName, context); - - phantomas.enterContext({ - type: 'getElementsByTagName', - callDetails: { - context: { - length: 1, - elements: [context] - }, - arguments: [tagName] - }, - backtrace: phantomas.getBacktrace() - }); - } - - function selectorTagNameSpyAfter(result, args) { - /*jshint validthis: true */ - - var tagName = args ? args[0].toLowerCase() : undefined; - var context = phantomas.getDOMPath(this); - - querySpy('tag name', tagName, 'getElementsByTagName', context, (result.length === 0)); - - var moreData = { - resultsNumber : (result && result.length > 0) ? result.length : 0 - }; - phantomas.leaveContext(moreData); - } - - phantomas.spy(Document.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter); - phantomas.spy(Element.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter); - - - // selector queries - function selectorQuerySpy(selector, context) { - phantomas.incrMetric('DOMqueriesByQuerySelectorAll'); - phantomas.addOffender('DOMqueriesByQuerySelectorAll', '%s (in %s)', selector, context); - } - - function selectorQuerySpyBefore(selector) { - /*jshint validthis: true */ - - var context = phantomas.getDOMPath(this); - selectorQuerySpy(selector, context); - - phantomas.enterContext({ - type: 'querySelector', - callDetails: { - context: { - length: 1, - elements: [context] - }, - arguments: [selector] - }, - backtrace: phantomas.getBacktrace() - }); - } - - function selectorQuerySpyAfter(result, args) { - /*jshint validthis: true */ - - var selector = args ? args[0] : undefined; - var context = phantomas.getDOMPath(this); - - querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0)); - - var moreData = { - resultsNumber : result ? 1 : 0 - }; - phantomas.leaveContext(moreData); - } - - function selectorAllQuerySpyBefore(selector) { - /*jshint validthis: true */ - - var context = phantomas.getDOMPath(this); - selectorQuerySpy(selector, context); - - phantomas.enterContext({ - type: 'querySelectorAll', - callDetails: { - context: { - length: 1, - elements: [context] - }, - arguments: [selector] - }, - backtrace: phantomas.getBacktrace() - }); - } - - function selectorAllQuerySpryAfter(result, args) { - /*jshint validthis: true */ - - var selector = args ? args[0] : undefined; - var context = phantomas.getDOMPath(this); - - querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0)); - - var moreData = { - resultsNumber : (result && result.length > 0) ? result.length : 0 - }; - phantomas.leaveContext(moreData); - } - - phantomas.spy(Document.prototype, 'querySelector', selectorQuerySpyBefore, selectorQuerySpyAfter); - phantomas.spy(Document.prototype, 'querySelectorAll', selectorAllQuerySpyBefore, selectorAllQuerySpryAfter); - phantomas.spy(Element.prototype, 'querySelector', selectorQuerySpyBefore, selectorQuerySpyAfter); - phantomas.spy(Element.prototype, 'querySelectorAll', selectorAllQuerySpyBefore, selectorAllQuerySpryAfter); - - - // count DOM inserts - function appendChild(child, element, context, appended) { - /*jshint validthis: true */ - - // ignore appending to the node that's not yet added to DOM tree - if (!element.parentNode) { - return; - } - - // don't count elements added to fragments as a DOM inserts (issue #350) - // DocumentFragment > div[0] - if (context.indexOf('DocumentFragment') === 0) { - return; - } - - phantomas.incrMetric('DOMinserts'); - phantomas.addOffender('DOMinserts', '"%s" appended to "%s"', appended, context); - - //phantomas.log('DOM insert: node "%s" appended to "%s"', appended, context); - } - - function appendChildSpyBefore(child) { - /*jshint validthis: true */ - - var context = phantomas.getDOMPath(this); - var appended = phantomas.getDOMPath(child); - appendChild(child, this, context, appended); - - phantomas.enterContext({ - type: 'appendChild', - callDetails: { - context: { - length: 1, - elements: [context] - }, - arguments: [appended] - }, - backtrace: phantomas.getBacktrace() - }); - } - - function insertBeforeSpyBefore(child, refElement) { - /*jshint validthis: true */ - - var context = phantomas.getDOMPath(this); - var appended = phantomas.getDOMPath(child); - var referent = phantomas.getDOMPath(refElement); - appendChild(child, this, context, appended); - - phantomas.enterContext({ - type: 'insertBefore', - callDetails: { - context: { - length: 1, - elements: [context] - }, - arguments: [ - appended, - referent - ] - }, - backtrace: phantomas.getBacktrace() - }); - } - - phantomas.spy(Node.prototype, 'appendChild', appendChildSpyBefore, function(result) { - phantomas.leaveContext(); - }); - phantomas.spy(Node.prototype, 'insertBefore', insertBeforeSpyBefore, function(result) { - phantomas.leaveContext(); - }); - - - phantomas.spy(Document.prototype, 'createElement', function(tagName) { - - phantomas.enterContext({ - type: 'createElement', - callDetails: { - arguments: [tagName] - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result, args) { - phantomas.leaveContext(); - }); - - - phantomas.spy(Document.prototype, 'createTextNode', function(text) { - - phantomas.enterContext({ - type: 'createTextNode', - callDetails: { - arguments: [text] - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result, args) { - phantomas.leaveContext(); - }); - - - phantomas.spy(Document.prototype, 'createDocumentFragment', function() { - - phantomas.enterContext({ - type: 'createDocumentFragment', - callDetails: { - arguments: [] - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result, args) { - phantomas.leaveContext(); - }); - - - phantomas.spy(window, 'getComputedStyle', function(element, pseudoElement) { - var target = phantomas.getDOMPath(element); - - phantomas.enterContext({ - type: 'getComputedStyle', - callDetails: { - arguments: [target, pseudoElement] - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result, args) { - phantomas.leaveContext(); - }); - - })(window.__phantomas); - }); - }); - - // report DOM queries that return no results (issue #420) - phantomas.on('domQuery', function(type, query, fnName, context, hasNoResults) { - // ignore DOM queries within DOM fragments (used internally by jQuery) - if (context.indexOf('body') !== 0 && context.indexOf('#document') !== 0) { - return; - } - - if (hasNoResults === true) { - phantomas.incrMetric('DOMqueriesWithoutResults'); - phantomas.addOffender('DOMqueriesWithoutResults', '%s (in %s) using %s', query, context, fnName); - } - }); - - // count DOM queries by either ID, tag name, class name and selector query - // @see https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-doctype - var Collection = require('../../util/collection'), - DOMqueries = new Collection(); - - phantomas.on('domQuery', function(type, query, fnName, context) { - phantomas.log('DOM query: by %s - "%s" (using %s) in %s', type, query, fnName, context); - phantomas.incrMetric('DOMqueries'); - - if (context && ( - context.indexOf('html') === 0 || - context.indexOf('body') === 0 || - context.indexOf('head') === 0 || - context.indexOf('#document') === 0 - )) { - DOMqueries.push(type + ' "' + query + '" with ' + fnName + ' (in context ' + context + ')'); - } - }); - - phantomas.on('report', function() { - DOMqueries.sort().forEach(function(query, cnt) { - if (cnt > 1) { - phantomas.incrMetric('DOMqueriesDuplicated'); - phantomas.incrMetric('DOMqueriesAvoidable', cnt - 1); - phantomas.addOffender('DOMqueriesDuplicated', '%s: %d queries', query, cnt); - } - }); - }); -}; \ No newline at end of file diff --git a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js deleted file mode 100644 index faf3042..0000000 --- a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js +++ /dev/null @@ -1,381 +0,0 @@ -/** - * Analyzes jQuery activity - * - * @see http://code.jquery.com/jquery-1.10.2.js - * @see http://code.jquery.com/jquery-2.0.3.js - */ -/* global document: true, window: true */ -/* jshint -W030 */ - -exports.version = '1.0.a'; - -exports.module = function(phantomas) { - 'use strict'; - - phantomas.setMetric('jQueryVersion', ''); // @desc version of jQuery framework (if loaded) [string] - phantomas.setMetric('jQueryVersionsLoaded'); // @desc number of loaded jQuery "instances" (even in the same version) - phantomas.setMetric('jQueryOnDOMReadyFunctions'); // @desc number of functions bound to onDOMReady event - phantomas.setMetric('jQueryWindowOnLoadFunctions'); // @desc number of functions bound to windowOnLoad event - phantomas.setMetric('jQuerySizzleCalls'); // @desc number of calls to Sizzle (including those that will be resolved using querySelectorAll) - phantomas.setMetric('jQueryEventTriggers'); // @desc number of jQuery event triggers - - var jQueryFunctions = [ - // DOM manipulations - 'html', - 'append', - 'appendTo', - 'prepend', - 'prependTo', - 'before', - 'insertBefore', - 'after', - 'insertAfter', - 'remove', - 'detach', - 'empty', - 'clone', - 'replaceWith', - 'replaceAll', - 'text', - 'wrap', - 'wrapAll', - 'wrapInner', - 'unwrap', - - // Style manipulations - 'css', - 'offset', - 'position', - 'height', - 'innerHeight', - 'outerHeight', - 'width', - 'innerWidth', - 'outerWidth', - 'scrollLeft', - 'scrollTop', - - // Animations - 'hide', - 'show', - 'toggle', - 'animate', - 'fadeIn', - 'fadeOut', - 'fadeTo', - 'fadeToggle', - 'slideDown', - 'slideUp', - 'slideToggle', - - // Generic events - 'on', - 'off', - 'live', - 'die', - 'delegate', - 'undelegate', - 'one', - 'bind', - 'unbind', - - // More events - 'blur', - 'change', - 'click', - 'dblclick', - 'error', - 'focus', - 'focusin', - 'focusout', - 'hover', - 'keydown', - 'keypress', - 'keyup', - 'load', - 'mousedown', - 'mouseenter', - 'mouseleave', - 'mousemove', - 'mouseout', - 'mouseover', - 'mouseup', - 'resize', - 'scroll', - 'select', - 'submit', - 'unload', - - // Attributes - 'attr', - 'prop', - 'removeAttr', - 'removeProp', - 'val', - 'hasClass', - 'addClass', - 'removeClass', - 'toggleClass', - ]; - - var jQueryTraversalFunctions = [ - 'children', - 'closest', - 'find', - 'next', - 'nextAll', - 'nextUntil', - 'offsetParent', - 'parent', - 'parents', - 'parentsUntil', - 'prev', - 'prevAll', - 'prevUntil', - 'siblings' - ]; - - jQueryFunctions = jQueryFunctions.concat(jQueryTraversalFunctions); - - // spy calls to jQuery functions - phantomas.on('init', function() { - phantomas.evaluate(function(jQueryFunctions, jQueryTraversalFunctions) { - (function(phantomas) { - var oldJQuery; - - phantomas.spyGlobalVar('jQuery', function(jQuery) { - var version; - - if (!jQuery || !jQuery.fn) { - phantomas.log('jQuery: unable to detect version!'); - return; - } - - // Tag the current version of jQuery to avoid multiple reports of jQuery being loaded - // when it's actually only restored via $.noConflict(true) - see comments in #435 - if (jQuery.__phantomas === true) { - phantomas.log('jQuery: this instance has already been seen by phantomas'); - return; - } - jQuery.__phantomas = true; - - // report the version of jQuery - version = jQuery.fn.jquery; - phantomas.emit('jQueryLoaded', version); - - phantomas.pushContext({ - type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded', - callDetails: { - arguments: ['version ' + version] - }, - backtrace: phantomas.getBacktrace() - }); - oldJQuery = version; - - // jQuery.ready.promise - // works for jQuery 1.8.0+ (released Aug 09 2012) - phantomas.spy(jQuery.ready, 'promise', function(func) { - phantomas.incrMetric('jQueryOnDOMReadyFunctions'); - phantomas.addOffender('jQueryOnDOMReadyFunctions', phantomas.getCaller(3)); - - phantomas.pushContext({ - type: 'jQuery - onDOMReady', - callDetails: { - arguments: [func] - }, - backtrace: phantomas.getBacktrace() - }); - - }) || phantomas.log('jQuery: can not measure jQueryOnDOMReadyFunctions (jQuery used on the page is too old)!'); - - - // Sizzle calls - jQuery.find - // works for jQuery 1.3+ (released Jan 13 2009) - phantomas.spy(jQuery, 'find', function(selector, context) { - phantomas.incrMetric('jQuerySizzleCalls'); - phantomas.addOffender('jQuerySizzleCalls', '%s (in %s)', selector, (phantomas.getDOMPath(context) || 'unknown')); - - phantomas.enterContext({ - type: 'jQuery - Sizzle call', - callDetails: { - context: { - length: 1, - elements: [phantomas.getDOMPath(context)] - }, - arguments: [selector] - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result) { - var moreData = { - resultsNumber : (result && result.length) ? result.length : 0 - }; - phantomas.leaveContext(moreData); - }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!'); - - - phantomas.spy(jQuery.fn, 'init', function(selector, context) { - if (typeof selector === 'string' && /^#([\w\-]*)$/.exec(selector) !== null && !context) { - - phantomas.enterContext({ - type: 'jQuery - find', - callDetails: { - arguments: [selector] - }, - backtrace: phantomas.getBacktrace() - }); - - } - - }, function(result) { - var data = phantomas.getContextData(); - - if (data.type === 'jQuery - find' && - !data.callDetails.context && - data.callDetails.arguments.length === 1 && - /^#([\w\-]*)$/.exec(data.callDetails.arguments[0]) !== null) { - - var moreData = { - resultsNumber : (result && result.length) ? result.length : 0 - }; - phantomas.leaveContext(moreData); - } - }); - - - if (!jQuery.event) { - phantomas.spy(jQuery.event, 'trigger', function(ev, data, elem) { - var path = phantomas.getDOMPath(elem), - type = ev.type || ev; - - phantomas.log('Event: triggered "%s" on "%s"', type, path); - - phantomas.incrMetric('jQueryEventTriggers'); - phantomas.addOffender('jQueryEventTriggers', '"%s" on "%s"', type, path); - }); - } - - // jQuery events bound to window' onLoad event (#451) - phantomas.spy(jQuery.fn, 'on', function(eventName, func) { - if ((eventName === 'load') && (this[0] === window)) { - phantomas.incrMetric('jQueryWindowOnLoadFunctions'); - phantomas.addOffender('jQueryWindowOnLoadFunctions', phantomas.getCaller(2)); - } - }); - - // Add spys on many jQuery functions - jQueryFunctions.forEach(function(functionName) { - - phantomas.spy(jQuery.fn, functionName, function(args) { - - // Clean args - args = [].slice.call(arguments); - args.forEach(function(arg, index) { - - if (arg instanceof Array) { - arg = '[Array]'; - } - - if (arg instanceof Object) { - - if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) { - - arg = phantomas.getDOMPath(arg[0]) || 'unknown'; - - } else if (arg instanceof HTMLElement) { - - arg = phantomas.getDOMPath(arg) || 'unknown'; - - } else if (typeof arg === 'function') { - - arg = '(function)'; - - } else { - - try { - - for (var key in arg) { - if (typeof arg[key] === 'function') { - arg[key] = '(function)'; - } - } - - arg = JSON.stringify(arg); - } catch(e) { - arg = '[Object]'; - } - - } - } - - if ((typeof arg === 'string' || arg instanceof String) && arg.length > 200) { - arg = arg.substring(0, 200) + '...'; - } - - if (typeof arg === 'function') { - arg = '(function)'; - } - - if (arg === true) { - arg = 'true'; - } - - if (arg === false) { - arg = 'false'; - } - - if (arg === null) { - arg = 'null'; - } - - if (typeof arg !== 'number' && typeof arg !== 'string' && !(arg instanceof String)) { - arg = 'undefined'; - } - - args[index] = arg; - }); - - var elements = []; - for (var i = 0 ; i < this.length ; i++) { - elements.push(phantomas.getDOMPath(this[i])); - } - - phantomas.enterContext({ - type: 'jQuery - ' + functionName, - callDetails: { - context: { - length: this.length, - elements: elements - }, - arguments: args - }, - backtrace: phantomas.getBacktrace() - }); - - }, function(result) { - if (jQueryTraversalFunctions.indexOf(functionName) >= 0) { - var moreData = { - resultsNumber : (result && result.length) ? result.length : 0 - }; - phantomas.leaveContext(moreData); - } else { - phantomas.leaveContext(); - } - }) || phantomas.log('jQuery: can not track jQuery - ' + functionName + ' (this version of jQuery doesn\'t support it)'); - }); - }); - })(window.__phantomas); - }, jQueryFunctions, jQueryTraversalFunctions); - }); - - - phantomas.on('jQueryLoaded', function(version) { - phantomas.log('jQuery: loaded v' + version); - phantomas.setMetric('jQueryVersion', version); - - // report multiple jQuery "instances" (issue #435) - phantomas.incrMetric('jQueryVersionsLoaded'); - phantomas.addOffender('jQueryVersionsLoaded', 'v%s', version); - }); -}; diff --git a/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js b/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js deleted file mode 100644 index 61489af..0000000 --- a/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Reports the use of functions known to be serious performance bottlenecks in JS - * - * @see http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ - * @see http://www.quirksmode.org/blog/archives/2005/06/three_javascrip_1.html - * @see http://www.stevesouders.com/blog/2012/04/10/dont-docwrite-scripts/ - * - * Run phantomas with --spy-eval to count eval() calls (see issue #467) - */ -/* global document: true, window: true */ - -exports.version = '0.2'; - -exports.module = function(phantomas) { - 'use strict'; - - phantomas.setMetric('documentWriteCalls'); //@desc number of calls to either document.write or document.writeln @offenders - phantomas.setMetric('evalCalls'); // @desc number of calls to eval (either direct or via setTimeout / setInterval) @offenders - - // spy calls to eval only when requested (issue #467) - var spyEval = phantomas.getParam('spy-eval') === true; - if (!spyEval) { - phantomas.log('javaScriptBottlenecks: to spy calls to eval() run phantomas with --spy-eval option'); - } - - phantomas.on('init', function() { - phantomas.evaluate(function(spyEval) { - (function(phantomas) { - function report(msg, caller, backtrace, metric) { - phantomas.log(msg + ': from ' + caller + '!'); - phantomas.log('Backtrace: ' + backtrace); - - phantomas.incrMetric(metric); - phantomas.addOffender(metric, "%s from %s", msg, caller); - } - - // spy calls to eval() - if (spyEval) { - phantomas.spy(window, 'eval', function(code) { - report('eval() called directly', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); - phantomas.log('eval\'ed code: ' + (code || '').substring(0, 150) + '(...)'); - }); - } - - // spy calls to setTimeout / setInterval with string passed instead of a function - phantomas.spy(window, 'setTimeout', function(fn, interval) { - if (typeof fn !== 'string') return; - - report('eval() called via setTimeout("' + fn + '")', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); - }); - - phantomas.spy(window, 'setInterval', function(fn, interval) { - if (typeof fn !== 'string') return; - - report('eval() called via setInterval("' + fn + '")', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); - }); - - // spy document.write(ln) - phantomas.spy(document, 'write', function(arg) { - report('document.write() used', phantomas.getCaller(), phantomas.getBacktrace(), 'documentWriteCalls'); - }); - - phantomas.spy(document, 'writeln', function(arg) { - report('document.writeln() used', phantomas.getCaller(), phantomas.getBacktrace(), 'documentWriteCalls'); - }); - })(window.__phantomas); - }, spyEval); - }); -}; diff --git a/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js b/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js deleted file mode 100644 index 697b1db..0000000 --- a/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Saves the javascript interractions with the DOM - * - * Run phantomas with --js-execution-tree option to use this module - */ - -exports.version = '0.1'; - -exports.module = function(phantomas) { - 'use strict'; - - phantomas.setMetric('javascriptExecutionTree'); - - // save data - phantomas.on('report', function() { - phantomas.log('JS execution tree: Reading execution tree JSON'); - - phantomas.evaluate(function() {(function(phantomas) { - var fullTree = phantomas.readFullTree(); - - if (fullTree === null) { - phantomas.log('JS execution tree: error, the execution tree is not correctly closed'); - return; - } - - phantomas.setMetric('javascriptExecutionTree', true, true); - phantomas.addOffender('javascriptExecutionTree', JSON.stringify(fullTree)); - - })(window.__phantomas);}); - }); -}; diff --git a/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js b/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js deleted file mode 100644 index bc250b0..0000000 --- a/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js +++ /dev/null @@ -1,69 +0,0 @@ -exports.version = '0.1'; - -exports.module = function(phantomas) { - 'use strict'; - - phantomas.setMetric('scrollExecutionTree'); - - phantomas.on('report', function() { - - phantomas.evaluate(function() { - (function(phantomas) { - - var evt = document.createEvent('CustomEvent'); - evt.initCustomEvent('scroll', false, false, null); - - function triggerScrollEvent() { - phantomas.resetTree(); - - try { - - // Chrome triggers them in this order: - - // 1. document - phantomas.pushContext({ - type: 'documentScroll' - }); - document.dispatchEvent(evt); - - // 2. window - phantomas.pushContext({ - type: 'windowScroll' - }); - window.dispatchEvent(evt); - - // No need to call window.onscroll(), it's called by the scroll event on window - - } catch(e) { - phantomas.log('ScrollListener error: %s', e); - } - } - - var firstScrollTime = Date.now(); - phantomas.log('ScrollListener: triggering a first scroll event...'); - triggerScrollEvent(); - - - // Ignore the first scroll event and only save the second one, - // because we want to detect un-throttled things, throttled ones are ok. - var secondScrollTime = Date.now(); - phantomas.log('ScrollListener: triggering a second scroll event (%dms after the first)...', secondScrollTime - firstScrollTime); - triggerScrollEvent(); - - - var fullTree = phantomas.readFullTree(); - if (fullTree !== null) { - phantomas.setMetric('scrollExecutionTree', true, true); - phantomas.addOffender('scrollExecutionTree', JSON.stringify(fullTree)); - phantomas.log('ScrollListener: scrollExecutionTree correctly extracted'); - } else { - phantomas.log('Error: scrollExecutionTree could not be extracted'); - } - - - phantomas.log('ScrollListener: end of scroll triggering'); - - })(window.__phantomas); - }); - }); -}; diff --git a/lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js b/lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js deleted file mode 100644 index 43082f1..0000000 --- a/lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Measure when the page reaches certain states - * - * @see http://w3c-test.org/webperf/specs/NavigationTiming/#dom-performancetiming-domloading - * @see https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp - */ -/* global document: true, window: true */ - -exports.version = '1.0.a'; - -exports.module = function(phantomas) { - 'use strict'; - - // times below are calculated relative to performance.timing.responseEnd (#117) - phantomas.setMetric('domInteractive'); // @desc time it took to parse the HTML and construct the DOM - phantomas.setMetric('domContentLoaded'); // @desc time it took to construct both DOM and CSSOM, no stylesheets that are blocking JavaScript execution (i.e. onDOMReady) - phantomas.setMetric('domContentLoadedEnd'); // @desc time it took to finish handling of onDOMReady event @unreliable - phantomas.setMetric('domComplete'); // @desc time it took to load all page resources, the loading spinner has stopped spinning - - // backend vs frontend time - phantomas.setMetric('timeBackend'); // @desc time to the first byte compared to the total loading time [%] - phantomas.setMetric('timeFrontend'); // @desc time to window.load compared to the total loading time [%] - - // measure dom... metrics from the moment HTML response was fully received - var responseEndTime = Date.now(); - - phantomas.on('responseEnd', function() { - responseEndTime = Date.now(); - phantomas.log('Performance timing: responseEnd = %d', responseEndTime); - }); - - phantomas.on('init', function() { - phantomas.evaluate(function(responseEndTime) { - (function(phantomas) { - phantomas.spyEnabled(false, 'installing window.performance metrics'); - - phantomas.currentStep = 'domCreation'; - - // extend window.performance - // "init" event is sometimes fired twice, pass a value set by "responseEnd" event handler (fixes #192) - if (typeof window.performance === 'undefined') { - window.performance = { - timing: { - responseEnd: responseEndTime - } - }; - - phantomas.log('Performance timing: emulating window.performance'); - } - else { - phantomas.log('Performance timing: using native window.performance'); - } - - // onDOMReady - document.addEventListener("DOMContentLoaded", function() { - - setTimeout(function() { - // use NavigationTiming if possible - var time = window.performance.timing.domContentLoadedEventEnd ? - (window.performance.timing.domContentLoadedEventEnd - window.performance.timing.responseEnd) - : - (Date.now() - responseEndTime); - - phantomas.currentStep = 'domContentLoadedEnd'; - phantomas.setMetric('domContentLoadedEnd', time, true); - phantomas.log('Performance timing: document reached "DOMContentLoadedEnd" state after %d ms', time); - - phantomas.pushContext({ - type: 'domContentLoadedEnd' - }); - }, 0); - - var time = Date.now() - responseEndTime; - - phantomas.currentStep = 'domContentLoaded'; - phantomas.setMetric('domContentLoaded', time, true); - phantomas.log('Performance timing: document reached "DOMContentLoaded" state after %d ms', time); - - phantomas.pushContext({ - type: 'domContentLoaded' - }); - }); - - // emulate Navigation Timing - document.addEventListener('readystatechange', function() { - var readyState = document.readyState, - responseEndTime = window.performance.timing.responseEnd, - time = Date.now() - responseEndTime, - metricName; - - // @see http://www.w3.org/TR/html5/dom.html#documentreadystate - switch(readyState) { - // the browser has finished parsing all of the HTML and DOM construction is complete - case 'interactive': - metricName = 'domInteractive'; - break; - - // the processing is complete and all of the resources on the page have finished downloading - case 'complete': - metricName = 'domComplete'; - phantomas.log('Performance timing: %j', window.performance.timing); - break; - - default: - phantomas.log('Performance timing: unhandled "%s" state!', readyState); - return; - } - - phantomas.currentStep = metricName; - phantomas.setMetric(metricName, time, true); - phantomas.log('Performance timing: document reached "%s" state after %d ms', readyState, time); - - phantomas.pushContext({ - type: metricName - }); - }); - - phantomas.spyEnabled(true); - })(window.__phantomas); - }, responseEndTime); - }); - - /** - * Emit metrics with backend vs frontend time - * - * Performance Golden Rule: - * "80-90% of the end-user response time is spent on the frontend. Start there." - * - * @see http://www.stevesouders.com/blog/2012/02/10/the-performance-golden-rule/ - */ - phantomas.on('report', function() { - // The “backend” time is the time it takes the server to get the first byte back to the client. - // The “frontend” time is measured from the last byte of the response (responseEnd) until all resources are fetched (domComplete) - var backendTime = parseInt(phantomas.getMetric('timeToFirstByte'), 10), - frontendTime = parseInt(phantomas.getMetric('domComplete'), 10), - totalTime = backendTime + frontendTime, - backendTimePercentage; - - if (totalTime === 0) { - return; - } - - backendTimePercentage = Math.round(backendTime / totalTime * 100); - - phantomas.setMetric('timeBackend', backendTimePercentage); - phantomas.setMetric('timeFrontend', 100 - backendTimePercentage); - - phantomas.log('Performance timing: backend vs frontend time - %d% / %d%', backendTimePercentage, 100 - backendTimePercentage); - }); -};