Remove JS Timeline and Scroll Bottlenecks

This commit is contained in:
Gaël Métais
2019-03-13 19:27:57 +01:00
parent b6e8768767
commit 5e82eea9f1
21 changed files with 1 additions and 3070 deletions
-5
View File
@@ -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'
-190
View File
@@ -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<max ; i++) {
millisecondsArray[i] |= 1;
}
}
});
// Pre-fill array of 200 elements
$scope.timeline = Array.apply(null, new Array(numberOfIntervals + 1)).map(Number.prototype.valueOf,0);
// Create the timeline from the milliseconds array
millisecondsArray.forEach(function(value, timestamp) {
if (value === 1) {
$scope.timeline[Math.floor(timestamp / $scope.timelineIntervalDuration)] += 1;
}
});
// Get the maximum value of the array (needed for display)
$scope.timelineMax = Math.max.apply(Math, $scope.timeline);
}
function initProfiler() {
$scope.profilerData = $scope.executionTree;
}
$scope.changeScript = function() {
initExecutionTree();
initTimeline();
initProfiler();
};
$scope.findLineIndexByTimestamp = function(timestamp) {
var lineIndex = 0;
for (var i = 0; i < $scope.executionTree.length; i ++) {
var delta = $scope.executionTree[i].data.timestamp - timestamp;
if (delta < $scope.timelineIntervalDuration) {
lineIndex = i;
}
if (delta > 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);
});
}
};
}]);
@@ -93,511 +93,6 @@
};
});
function getJQueryContextButtonHTML(context, onASingleLine) {
if (context.length === 0) {
return '<span class="offenderButton">Empty jQuery object</span>';
}
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 <b>document</b>)';
case 'windowScroll':
return '(triggering the scroll event on <b>window</b>)';
case 'window.onscroll':
return '(calling the <b>window.onscroll</b> 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 '<b>' + args[0] + '</b>';
case 'getElementsByClassName':
case 'getElementsByTagName':
case 'querySelector':
case 'querySelectorAll':
return '<b>' + args[0] + '</b> 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 <b>' + args[0] + '</b> 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 '<b>' + args[0] + '</b> on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
case 'jQuery - find':
if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') {
return '<b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return '<b>' + args[0] + '</b>';
}
break;
case 'jQuery - html':
if (args[0] !== undefined) {
return 'set content "<b>' + args[0] + '</b>" 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 <b>' + args[0] + '</b>';
case 'jQuery - prepend':
return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - prependTo':
return 'prepend ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to <b>' + args[0] + '</b>';
case 'jQuery - before':
return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - insertBefore':
return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' before <b>' + args[0] + '</b>';
case 'jQuery - after':
return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - insertAfter':
return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' after <b>' + args[0] + '</b>';
case 'jQuery - remove':
case 'jQuery - detach':
if (args[0]) {
return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by <b>' + args[0] + '</b>';
} 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 <b>' + args[0] + '</b>';
case 'jQuery - replaceAll':
return 'replace <b>' + args[0] + '</b> with ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - text':
if (args[0] !== undefined) {
return 'set text "<b>' + args[0] + '</b>" 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 <b>' + args[0] + '</b>';
case 'jQuery - wrapInner':
return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within <b>' + args[0] + '</b>';
case 'jQuery - css':
case 'jQuery - attr':
case 'jQuery - prop':
if (isStringOfObject(args[0])) {
return 'set <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else if (args[1]) {
return 'set <b>' + args[0] + '</b> : <b>' + args[1] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'get <b>' + args[0] + '</b> 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 <b>' + args[0] + '</b> 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 <b>' + args[0] + '</b> 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 '<b>' + args[0].replace(/&quot;\(function\)&quot;/g, '(function)') + '</b>';
} else if (args[1] && isPureString(args[1])) {
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[1] + '</b>';
} else {
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - off':
if (args[0]) {
if (args[1]) {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[1] + '</b>';
} else {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
} else {
return 'unbind all events';
}
break;
case 'jQuery - live':
case 'jQuery - bind':
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - die':
case 'jQuery - unbind':
if (args[0]) {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'unbind all events';
}
break;
case 'jQuery - delegate':
return 'bind <b>' + args[1] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[0] + '</b>';
case 'jQuery - undelegate':
if (args[0]) {
if (args[1]) {
return 'unbind <b>' + args[1] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[0] + '</b>';
} else {
return 'unbind namespace <b>' + args[0] + '</b>';
}
} 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 <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - val':
if (args[0]) {
return 'set value <b>' + args[0] + '</b> to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'get value from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - hasClass':
case 'jQuery - addClass':
case 'jQuery - removeClass':
return '<b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - toggleClass':
if (args[0]) {
if (args[1]) {
return 'toggle <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to <b>' + args[1] + '</b>';
} else {
return 'toggle <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
} else {
return 'magic no-argument toggleClass';
}
break;
case 'jQuery - children':
if (args[0]) {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by <b>' + args[0] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - closest':
if (args[1]) {
return 'closest <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' in context <b>' + args[1] + '</b>';
} else {
return 'closest <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - next':
case 'jQuery - nextAll':
if (args[0]) {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - nextUntil':
if (args[0]) {
if (args[1]) {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b>';
}
} 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 <b>' + args[0] + '</b>';
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - prevUntil':
if (args[0]) {
if (args[1]) {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - parent':
case 'jQuery - parents':
if (args[0]) {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - parentsUntil':
if (args[0]) {
if (args[1]) {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - siblings':
if (args[0]) {
return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
default:
return '';
}
}
function escapeHTML(html) {
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
return String(html).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
function joinArgs(args) {
var html = '<b>' + args[0] + '</b>';
if (args[1]) {
html += ', <b>' + args[1] + '</b>';
if (args[2]) {
html += ', <b>' + args[2] + '</b>';
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 += '<div class="icon-warning"></div>';
} else {
html += '<div class="icon-question"></div>';
}
html += '<div class="detailsOverlay">';
html += '<div class="closeBtn">✖</div>';
if (node.data.callDetails.context && node.data.callDetails.context.length === 0) {
html += '<h4>Called on 0 jQuery element</h4><p class="advice">Useless function call, as the jQuery object is empty.</p>';
} else if (node.eventNotDelegated) {
html += '<p class="advice">This binding should use Event Delegation instead of binding each element one by one.</p>';
}
if (node.data.resultsNumber === 0) {
html += '<p class="advice">The query returned 0 results. Could it be unused or dead code?</p>';
} else if (node.data.resultsNumber > 0) {
html += '<p>The query returned ' + node.data.resultsNumber + ' ' + (node.data.resultsNumber > 1 ? 'results' : 'result') + '.</p>';
}
if (node.data.backtrace) {
html += '<h4>Backtrace</h4>';
html += '<div class="table">';
html += getBacktraceHTML(node.data.backtrace);
html += '</div>';
}
html += '</div>';
}
return html;
}
offendersDirectives.directive('profilerLine', ['$filter', function($filter) {
var numberWithCommas = $filter('number');
function getProfilerLineHTML(index, node) {
return '<div class="index">' + (index + 1) + '</div>' +
'<div class="type">' + node.data.type + (node.children ? '<div class="children">' + recursiveChildrenHTML(node) + '</div>' : '') + '</div>' +
'<div class="value">' + getTimelineParamsHTML(node, false) + '</div>' +
'<div class="details">' + getTimelineDetailsHTML(node) + '</div>' +
'<div class="startTime ' + node.data.loadingStep + '">' + numberWithCommas(node.data.timestamp, 0) + ' ms</div>';
}
function recursiveChildrenHTML(node) {
var html = '';
if (node.children) {
node.children.forEach(function(child) {
html += '<div class="child"><span>' + child.data.type + '<div class="childArgs">' + getTimelineParamsHTML(child, true) + '</div></span>' + recursiveChildrenHTML(child) + '</div>';
});
}
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: '<div></div>',
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;
-3
View File
@@ -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');
}