Speed up timeline page, by removing some angular

This commit is contained in:
Gaël Métais
2015-03-10 09:34:43 +01:00
parent dc2f6e17cd
commit 50571995a4
5 changed files with 612 additions and 363 deletions
-8
View File
@@ -184,14 +184,6 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
node.showDetails = !isOpen;
};
$scope.isStringOfObject = function(str) {
return typeof str === 'string' && str[0] === '{' && str[str.length - 1] === '}';
};
$scope.isPureString = function(str) {
return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown';
};
$scope.backToDashboard = function() {
$location.path('/result/' + $scope.runId);
+609 -110
View File
@@ -1,129 +1,628 @@
var offendersDirectives = angular.module('offendersDirectives', []);
(function() {
"use strict";
var offendersDirectives = angular.module('offendersDirectives', []);
offendersDirectives.directive('domTree', function() {
return {
restrict: 'E',
scope: {
tree: '='
},
template: '<div class="domTree"></div>',
replace: true,
link: function(scope, element, attrs) {
function recursiveHtmlBuilder(tree) {
var html = '';
var keys = Object.keys(tree);
keys.forEach(function(key) {
if (isNaN(tree[key])) {
html += '<div><span>' + key + '</span>' + recursiveHtmlBuilder(tree[key]) + '</div>';
} else if (tree[key] > 1) {
html += '<div><span>' + key + ' <span>(x' + tree[key] + ')</span></span></div>';
} else {
html += '<div><span>' + key + '</span></div>';
}
});
function getdomTreeHTML(tree) {
return '<div class="domTree">' + getdomTreeInnerHTML(tree) + '</div>';
}
return html;
function getdomTreeInnerHTML(tree) {
return recursiveHtmlBuilder(tree);
}
function recursiveHtmlBuilder(tree) {
var html = '';
var keys = Object.keys(tree);
keys.forEach(function(key) {
if (isNaN(tree[key])) {
html += '<div><span>' + key + '</span>' + recursiveHtmlBuilder(tree[key]) + '</div>';
} else if (tree[key] > 1) {
html += '<div><span>' + key + ' <span>(x' + tree[key] + ')</span></span></div>';
} else {
html += '<div><span>' + key + '</span></div>';
}
});
return html;
}
offendersDirectives.directive('domTree', function() {
return {
restrict: 'E',
scope: {
tree: '='
},
template: '<div class="domTree"></div>',
replace: true,
link: function(scope, element) {
element.append(getdomTreeInnerHTML(scope.tree));
}
};
});
function getDomElementButtonHTML(obj) {
if (obj.tree) {
return '<div class="offenderButton opens">' + getDomElementButtonInnerHTML(obj) + '</div>';
}
return '<div class="offenderButton">' + getDomElementButtonInnerHTML(obj) + '</div>';
}
function getDomElementButtonInnerHTML(obj) {
if (obj.type === 'html' ||
obj.type === 'body' ||
obj.type === 'head' ||
obj.type === 'window' ||
obj.type === 'document' ||
obj.type === 'fragment') {
return obj.type;
}
if (obj.type === 'notAnElement') {
return 'Incorrect element';
}
var html = '';
if (obj.type === 'domElement') {
html = 'DOM element <b>' + obj.element + '</b>';
} else if (obj.type === 'fragmentElement') {
html = 'Fragment element <b>' + obj.element + '</b>';
} else if (obj.type === 'createdElement') {
html = 'Created element <b>' + obj.element + '</b>';
}
if (obj.tree) {
html += getdomTreeHTML(obj.tree);
}
return html;
}
offendersDirectives.directive('domElementButton', function() {
return {
restrict: 'E',
scope: {
obj: '='
},
template: '<div class="offenderButton" ng-class="{opens: obj.tree}"></div>',
replace: true,
link: function(scope, element) {
element.append(getDomElementButtonInnerHTML(scope.obj));
}
};
});
function getJQueryContextButtonHTML(context) {
if (context.length === 0) {
return '<span class="offenderButton">Empty jQuery object</span>';
}
if (context.length === 1) {
return getDomElementButtonHTML(context.elements[0]);
}
var html = context.length + ' elements (' + getDomElementButtonHTML(context.elements[0]) + ', ' + getDomElementButtonHTML(context.elements[1]);
if (context.length === 3) {
html += ', ' + getDomElementButtonHTML(context.elements[0]);
} else if (context.length > 3) {
html += ' and ' + (context.length - 2) + ' more...';
}
return html + '}';
}
offendersDirectives.directive('timelineParams', function() {
function isJQuery(node) {
return node.data.type.indexOf('jQuery ') === 0;
}
function getNonJQueryHTML(node) {
var type = node.data.type;
if (!node.data.callDetails) {
return '';
}
element.append(recursiveHtmlBuilder(scope.tree));
}
};
});
var args = node.data.callDetails.arguments;
var ctxt = node.data.callDetails.context;
offendersDirectives.directive('domElementButton', function() {
return {
restrict: 'E',
scope: {
obj: '='
},
templateUrl: 'views/domElementButton.html',
replace: true
};
});
offendersDirectives.directive('jqueryContextButton', function() {
return {
restrict: 'E',
scope: {
context: '='
},
templateUrl: 'views/jqueryContextButton.html',
replace: true
};
});
switch (type) {
case 'getElementById':
case 'createElement':
return '<b>' + args[0] + '</b>';
offendersDirectives.filter('shortenUrl', function() {
return function(url, maxLength) {
if (!maxLength) {
maxLength = 110;
case 'getElementsByClassName':
case 'getElementsByTagName':
case 'querySelector':
case 'querySelectorAll':
return '<b>' + args[0] + '</b> on ' + getDomElementButtonHTML(ctxt.elements[0]);
case 'appendChild':
return 'append ' + getDomElementButtonHTML(args[0]) + ' to ' + getDomElementButtonHTML(ctxt.elements[0]);
case 'insertBefore':
return 'insert' + getDomElementButtonHTML(args[0]) + ' into ' + getDomElementButtonHTML(ctxt.elements[0]) + ' before ' + getDomElementButtonHTML(args[1]);
case 'addEventListener':
return 'bind <b>' + args[0] + '</b> to ' + getDomElementButtonHTML(ctxt.elements[0]);
case 'error':
return args[0];
default:
return '';
}
}
// Why dividing by 2.1? Because it adds a 5% margin.
var leftLength = Math.floor((maxLength - 5) / 2.1);
var rightLength = Math.ceil((maxLength - 5) / 2.1);
function getJQueryHTML(node) {
var type = node.data.type;
var args = node.data.callDetails.arguments;
var ctxt = node.data.callDetails.context;
// escape HTML in args
for (var i = 0 ; i < 4 ; i ++) {
if (args[i]) {
args[i] = escapeHTML(args[i]);
}
}
return (url.length > maxLength) ? url.substr(0, leftLength) + ' ... ' + url.substr(-rightLength) : url;
};
});
if (type === 'jQuery loaded' || type === 'jQuery version change') {
return args[0];
}
offendersDirectives.directive('urlLink', function() {
return {
restrict: 'E',
scope: {
url: '=',
maxLength: '='
},
template: '<a href="{{url}}" target="_blank" title="{{url}}">{{url | shortenUrl:maxLength}}</a>',
replace: true
};
});
switch (type) {
case 'jQuery - onDOMReady':
case 'jQuery - windowOnLoad':
return '(function)';
offendersDirectives.filter('encodeURIComponent', function() {
return window.encodeURIComponent;
});
case 'jQuery - Sizzle call':
return '<b>' + args[0] + '</b> on ' + getDomElementButtonHTML(ctxt.elements[0]);
offendersDirectives.directive('fileAndLine', function() {
return {
restrict: 'E',
scope: {
file: '=',
line: '=',
column: '='
},
template: '<span><span ng-if="file"><url-link url="file" max-length="60"></url-link></span><span ng-if="!file">&lt;inline CSS&gt;</span> @ {{line}}:{{column}}</span>',
replace: true
};
});
case 'jQuery - find':
if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') {
return '<b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
} else {
return '<b>' + args[0] + '</b>';
}
break;
offendersDirectives.directive('fileAndLineButton', function() {
return {
restrict: 'E',
scope: {
file: '=',
line: '=',
column: '='
},
template: '<div class="offenderButton opens">css file<div class="cssFileAndLine"><file-and-line file="file" line="line" column="column" button="true"></file-and-line></div></div>',
replace: true
};
});
case 'jQuery - html':
if (args[0] !== undefined) {
return 'set content "<b>' + args[0] + '</b>" to ' + getJQueryContextButtonHTML(ctxt);
} else {
return 'get content from ' + getJQueryContextButtonHTML(ctxt);
}
break;
offendersDirectives.directive('offenderButton', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
case 'jQuery - append':
return 'append ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt);
element.bind('touchstart mouseenter', function(e) {
element.addClass('mouseOver');
e.preventDefault();
});
case 'jQuery - appendTo':
return 'append' + getJQueryContextButtonHTML(ctxt) + ' to <b>' + args[0] + '</b>';
element.bind('touchend mouseleave click', function(e) {
element.removeClass('mouseOver');
e.preventDefault();
case 'jQuery - prepend':
return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - prependTo':
return 'prepend ' + getJQueryContextButtonHTML(ctxt) + ' to <b>' + args[0] + '</b>';
case 'jQuery - before':
return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - insertBefore':
return 'insert ' + getJQueryContextButtonHTML(ctxt) + ' before <b>' + args[0] + '</b>';
case 'jQuery - after':
return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - insertAfter':
return 'insert ' + getJQueryContextButtonHTML(ctxt) + ' after <b>' + args[0] + '</b>';
case 'jQuery - remove':
case 'jQuery - detach':
if (args[0]) {
return getJQueryContextButtonHTML(ctxt) + ' filtered by <b>' + args[0] + '</b>';
} else {
return getJQueryContextButtonHTML(ctxt);
}
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);
case 'jQuery - replaceWith':
return 'replace ' + getJQueryContextButtonHTML(ctxt) + ' with <b>' + args[0] + '</b>';
case 'jQuery - replaceAll':
return 'replace <b>' + args[0] + '</b> with ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - text':
if (args[0]) {
return 'set text "<b>' + args[0] + '</b>" to ' + getJQueryContextButtonHTML(ctxt);
} else {
return 'get text from ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - wrap':
case 'jQuery - wrapAll':
return 'wrap ' + getJQueryContextButtonHTML(ctxt) + ' within <b>' + args[0] + '</b>';
case 'jQuery - wrapInner':
return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt) + ' 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);
} else if (args[1]) {
return 'set <b>' + args[0] + '</b> : <b>' + args[1] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
} else {
return 'get <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - offset':
case 'jQuery - height':
case 'jQuery - innerHeight':
case 'jQuery - outerHeight':
case 'jQuery - width':
case 'jQuery - innerWidth':
case 'jQuery - outerWidth':
case 'jQuery - scrollLeft':
case 'jQuery - scrollTop':
case 'jQuery - position':
if (args[0]) {
return 'set <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
} else {
return 'get from ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - toggle':
if (args[0] === 'true') {
return getJQueryContextButtonHTML(ctxt) + ' to visible';
} else if (args[0] === 'false') {
return getJQueryContextButtonHTML(ctxt) + ' to hidden';
} else {
return getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - on':
case 'jQuery - one':
if (args[1]) {
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt) + '\'s children filtered by <b>' + args[1] + '</b>';
} else {
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - off':
if (args[0]) {
if (args[1]) {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt) + '\'s children filtered by <b>' + args[1] + '</b>';
} else {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt);
}
} else {
return 'unbind all events';
}
break;
case 'jQuery - live':
case 'jQuery - bind':
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - die':
case 'jQuery - unbind':
if (args[0]) {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt);
} else {
return 'unbind all events';
}
break;
case 'jQuery - delegate':
return 'bind <b>' + args[1] + '</b> on ' + getJQueryContextButtonHTML(ctxt) + '\'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) + '\'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 - error':
case 'jQuery - focus':
case 'jQuery - focusin':
case 'jQuery - focusout':
case 'jQuery - hover':
case 'jQuery - keydown':
case 'jQuery - keypress':
case 'jQuery - keyup':
case 'jQuery - load':
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':
case 'jQuery - unload':
return 'bind on ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - removeAttr':
case 'jQuery - removeProp':
return 'remove <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - val':
if (args[0]) {
return 'set value <b>' + args[0] + '</b> to ' + getJQueryContextButtonHTML(ctxt);
} else {
return 'get value from ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - hasClass':
case 'jQuery - addClass':
case 'jQuery - removeClass':
return '<b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - toggleClass':
if (args[0]) {
if (args[1]) {
return 'toggle <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt) + ' to <b>' + args[1] + '</b>';
} else {
return 'toggle <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt);
}
} else {
return 'magic no-argument toggleClass';
}
break;
case 'jQuery - children':
if (args[0]) {
return 'of ' + getJQueryContextButtonHTML(ctxt) + ' filtered by <b>' + args[0] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - closest':
if (args[1]) {
return 'closest <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt) + ' in context <b>' + args[1] + '</b>';
} else {
return 'closest <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - next':
case 'jQuery - nextAll':
if (args[0]) {
return 'after ' + getJQueryContextButtonHTML(ctxt) + ' matching <b>' + args[0] + '</b>';
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - nextUntil':
if (args[0]) {
if (args[1]) {
return 'after ' + getJQueryContextButtonHTML(ctxt) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - offsetParent':
return 'of ' + getJQueryContextButtonHTML(ctxt);
case 'jQuery - prev':
case 'jQuery - prevAll':
if (args[0]) {
return 'before ' + getJQueryContextButtonHTML(ctxt) + ' matching <b>' + args[0] + '</b>';
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - prevUntil':
if (args[0]) {
if (args[1]) {
return 'before ' + getJQueryContextButtonHTML(ctxt) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - parent':
case 'jQuery - parents':
if (args[0]) {
return 'of ' + getJQueryContextButtonHTML(ctxt) + ' matching <b>' + args[0] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - parentsUntil':
if (args[0]) {
if (args[1]) {
return 'of ' + getJQueryContextButtonHTML(ctxt) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - siblings':
if (args[0]) {
return 'near ' + getJQueryContextButtonHTML(ctxt) + ' matching <b>' + args[0] + '</b>';
} else {
return 'near ' + getJQueryContextButtonHTML(ctxt);
}
break;
case 'jQuery - onDOMReady':
return '(function)';
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 getHTML(node) {
if (isJQuery(node)) {
return getJQueryHTML(node);
} else {
return getNonJQueryHTML(node);
}
}
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';
}
return {
restrict: 'E',
scope: {
node: '='
},
template: '<div class="value offenders"></div>',
replace: true,
link: function(scope, element) {
var html = getHTML(scope.node);
if (html) {
element.append(html);
}
}
};
});
offendersDirectives.filter('shortenUrl', function() {
return function(url, maxLength) {
if (!maxLength) {
maxLength = 110;
}
// Why dividing by 2.1? Because it adds a 5% margin.
var leftLength = Math.floor((maxLength - 5) / 2.1);
var rightLength = Math.ceil((maxLength - 5) / 2.1);
return (url.length > maxLength) ? url.substr(0, leftLength) + ' ... ' + url.substr(-rightLength) : url;
};
});
offendersDirectives.directive('urlLink', function() {
return {
restrict: 'E',
scope: {
url: '=',
maxLength: '='
},
template: '<a href="{{url}}" target="_blank" title="{{url}}">{{url | shortenUrl:maxLength}}</a>',
replace: true
};
});
offendersDirectives.filter('encodeURIComponent', function() {
return window.encodeURIComponent;
});
offendersDirectives.directive('fileAndLine', function() {
return {
restrict: 'E',
scope: {
file: '=',
line: '=',
column: '='
},
template: '<span><span ng-if="file"><url-link url="file" max-length="60"></url-link></span><span ng-if="!file">&lt;inline CSS&gt;</span> @ {{line}}:{{column}}</span>',
replace: true
};
});
offendersDirectives.directive('fileAndLineButton', function() {
return {
restrict: 'E',
scope: {
file: '=',
line: '=',
column: '='
},
template: '<div class="offenderButton opens">css file<div class="cssFileAndLine"><file-and-line file="file" line="line" column="column" button="true"></file-and-line></div></div>',
replace: true
};
});
})();
-8
View File
@@ -1,8 +0,0 @@
<div class="offenderButton" ng-class="{opens: obj.tree}">
<span ng-if="obj.type == 'html' || obj.type == 'body' || obj.type == 'head' || obj.type == 'window' || obj.type == 'document' || obj.type == 'fragment'"><b>{{::obj.type}}</b></span>
<span ng-if="obj.type == 'domElement'">DOM element <b>{{::obj.element}}</b></span>
<span ng-if="obj.type == 'fragmentElement'">Fragment element <b>{{::obj.element}}</b></span>
<span ng-if="obj.type == 'createdElement'">Created element <b>{{::obj.element}}</b></span>
<span ng-if="obj.type == 'notAnElement'">Incorrect element</span>
<dom-tree ng-if="obj.tree" tree="obj.tree"></dom-tree>
</div>
-9
View File
@@ -1,9 +0,0 @@
<span>
<span ng-if="context.length == 0" class="offenderButton">Empty jQuery object</span>
<span ng-if="context.length == 1"><dom-element-button obj="context.elements[0]"></dom-element-button></span>
<span ng-if="context.length > 1">
{{::context.length}} elements
(<dom-element-button obj="context.elements[0]"></dom-element-button>, <dom-element-button obj="context.elements[1]"></dom-element-button>
<span ng-if="context.length > 2">and {{::context.length - 2}} more...</span>)
</span>
</span>
+3 -228
View File
@@ -75,7 +75,7 @@
<div>Timestamp</div>
</div>
<div ng-if="(!warningsFilterOn || node.warning || node.error)"
ng-repeat="node in profilerData" ng-class="{
ng-repeat="node in ::profilerData" ng-class="{
showingDetails: node.showDetails,
jsError: node.error,
windowPerformance: node.windowPerformance
@@ -87,233 +87,8 @@
<js-children node="node"></js-children>
</div>
<div class="value">
<span ng-if="node.data.type == 'getElementById' || node.data.type == 'createElement'">
<b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="node.data.type == 'getElementsByClassName' || node.data.type == 'getElementsByTagName' || node.data.type == 'querySelector' || node.data.type == 'querySelectorAll'" class="offenders">
<b>{{::node.data.callDetails.arguments[0]}}</b> on <dom-element-button obj="node.data.callDetails.context.elements[0]"></dom-element-button>
</span>
<span ng-if="node.data.type == 'appendChild'" class="offenders">
<dom-element-button obj="node.data.callDetails.arguments[0]"></dom-element-button> appended to <dom-element-button obj="node.data.callDetails.context.elements[0]"></dom-element-button>
</span>
<span ng-if="node.data.type == 'insertBefore'" class="offenders">
insert <dom-element-button obj="node.data.callDetails.arguments[0]"></dom-element-button> in <dom-element-button obj="node.data.callDetails.context.elements[0]"></dom-element-button> before <dom-element-button obj="node.data.callDetails.arguments[1]"></dom-element-button>
</span>
<span ng-if="node.data.type == 'addEventListener'" class="offenders">
<b>{{::node.data.callDetails.arguments[0]}}</b> bound to <dom-element-button obj="node.data.callDetails.context.elements[0]"></dom-element-button>
</span>
<timeline-params node="node"></timeline-params>
<span ng-if="node.data.type == 'jQuery loaded' || node.data.type == 'jQuery version change'">
{{::node.data.callDetails.arguments[0]}}
</span>
<span ng-if="node.data.type == 'jQuery - onDOMReady' || node.data.type == 'jQuery - windowOnLoad'">
(function)
</span>
<span ng-if="node.data.type == 'jQuery - Sizzle call'" class="offenders">
<b>{{::node.data.callDetails.arguments[0]}}</b>
<span ng-if="node.data.callDetails.context.elements[0].type != 'document'"> on <dom-element-button obj="node.data.callDetails.context.elements[0]"></dom-element-button></span>
</span>
<span ng-if="node.data.type == 'jQuery - find'" class="offenders">
<b>{{::node.data.callDetails.arguments[0]}}</b>
<span ng-if="node.data.callDetails.context"> on <dom-element-button obj="node.data.callDetails.context.elements[0]"></dom-element-button></span>
</span>
<span ng-if="node.data.type == 'jQuery - html'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0] != undefined">set content "<b>{{::node.data.callDetails.arguments[0]}}</b>" to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
<span ng-if="node.data.callDetails.arguments[0] == undefined">get content from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
</span>
<span ng-if="node.data.type == 'jQuery - append'" class="offenders">
append <b>{{::node.data.callDetails.arguments[0]}}</b><span ng-if="node.data.callDetails.arguments[1]">, <b>{{::node.data.callDetails.arguments[1]}}</b></span><span ng-if="node.data.callDetails.arguments[2]">, <b>{{::node.data.callDetails.arguments[2]}}</b></span><span ng-if="node.data.callDetails.arguments[3]">, <b>and more...</b></span> to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - appendTo'" class="offenders">
append <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> to {{::node.data.callDetails.arguments[0]}}
</span>
<span ng-if="node.data.type == 'jQuery - prepend'" class="offenders">
prepend <b>{{::node.data.callDetails.arguments[0]}}</b><span ng-if="node.data.callDetails.arguments[1]">, <b>{{::node.data.callDetails.arguments[1]}}</b></span><span ng-if="node.data.callDetails.arguments[2]">, <b>{{::node.data.callDetails.arguments[2]}}</b></span><span ng-if="node.data.callDetails.arguments[3]">, <b>and more...</b></span> to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - prependTo'" class="offenders">
prepend <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> to {{::node.data.callDetails.arguments[0]}}
</span>
<span ng-if="node.data.type == 'jQuery - before'" class="offenders">
insert <b>{{::node.data.callDetails.arguments[0]}}</b><span ng-if="node.data.callDetails.arguments[1]">, <b>{{::node.data.callDetails.arguments[1]}}</b></span><span ng-if="node.data.callDetails.arguments[2]">, <b>{{::node.data.callDetails.arguments[2]}}</b></span><span ng-if="node.data.callDetails.arguments[3]">, <b>and more...</b></span> before <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - insertBefore'" class="offenders">
insert <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> before {{::node.data.callDetails.arguments[0]}}
</span>
<span ng-if="node.data.type == 'jQuery - after'" class="offenders">
insert <b>{{::node.data.callDetails.arguments[0]}}</b><span ng-if="node.data.callDetails.arguments[1]">, <b>{{::node.data.callDetails.arguments[1]}}</b></span><span ng-if="node.data.callDetails.arguments[2]">, <b>{{::node.data.callDetails.arguments[2]}}</b></span><span ng-if="node.data.callDetails.arguments[3]">, <b>and more...</b></span> after <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - insertAfter'" class="offenders">
insert <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> after {{::node.data.callDetails.arguments[0]}}
</span>
<span ng-if="node.data.type == 'jQuery - remove'" class="offenders">
remove <jquery-context-button context="node.data.callDetails.context"></jquery-context-button><span ng-if="node.data.callDetails.arguments[0] != undefined"> filtered by <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - detach'" class="offenders">
detach <jquery-context-button context="node.data.callDetails.context"></jquery-context-button><span ng-if="node.data.callDetails.arguments[0] != undefined"> filtered by <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - empty'" class="offenders">
empty <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - clone'" class="offenders">
clone <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - replaceWith'" class="offenders">
replace <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> with <b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="node.data.type == 'jQuery - replaceAll'" class="offenders">
replace <b>{{::node.data.callDetails.arguments[0]}}</b> with <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - text'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0] != undefined">set text "<b>{{::node.data.callDetails.arguments[0]}}</b>" to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
<span ng-if="node.data.callDetails.arguments[0] == undefined">get text from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
</span>
<span ng-if="node.data.type == 'jQuery - wrap' || node.data.type == 'jQuery - wrapAll'" class="offenders">
wrap <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> with <b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="node.data.type == 'jQuery - wrapInner'" class="offenders">
wrap the content of <jquery-context-button context="node.data.callDetails.context"></jquery-context-button> with <b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="node.data.type == 'jQuery - unwrap'" class="offenders">
unwrap <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - css' || node.data.type == 'jQuery - attr' || node.data.type == 'jQuery - prop'" class="offenders">
<span ng-if="isStringOfObject(node.data.callDetails.arguments[0])">set <b>{{::node.data.callDetails.arguments[0]}}</b> to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
<span ng-if="!isStringOfObject(node.data.callDetails.arguments[0])">
<span ng-if="node.data.callDetails.arguments[1] == undefined">get <b>{{::node.data.callDetails.arguments[0]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
<span ng-if="node.data.callDetails.arguments[1] != undefined">set <b>{{::node.data.callDetails.arguments[0]}}</b> : <b>{{::node.data.callDetails.arguments[1]}}</b> to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
</span>
</span>
<span ng-if="node.data.type == 'jQuery - offset' || node.data.type == 'jQuery - height' || node.data.type == 'jQuery - innerHeight' || node.data.type == 'jQuery - outerHeight' || node.data.type == 'jQuery - width' || node.data.type == 'jQuery - innerWidth' || node.data.type == 'jQuery - outerWidth' || node.data.type == 'jQuery - scrollLeft' || node.data.type == 'jQuery - scrollTop'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0] == undefined">get from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
<span ng-if="node.data.callDetails.arguments[0] != undefined">set <b>{{::node.data.callDetails.arguments[0]}}</b> to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button></span>
</span>
<span ng-if="node.data.type == 'jQuery - position'" class="offenders">
get from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - show' || node.data.type == 'jQuery - hide' || node.data.type == 'jQuery - animate' || node.data.type == 'jQuery - fadeIn' || node.data.type == 'jQuery - fadeOut' || node.data.type == 'jQuery - fadeTo' || node.data.type == 'jQuery - fadeToggle' || node.data.type == 'jQuery - slideDown' || node.data.type == 'jQuery - slideUp' || node.data.type == 'jQuery - slideToggle'" class="offenders">
<jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - toggle'" class="offenders">
<jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0] == 'true'"> to visible</span>
<span ng-if="node.data.callDetails.arguments[0] == 'false'"> to hidden</span>
</span>
<span ng-if="node.data.type == 'jQuery - on' || node.data.type == 'jQuery - one'" class="offenders">
bind <b>{{::node.data.callDetails.arguments[0]}}</b> on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button><span ng-if="isPureString(node.data.callDetails.arguments[1])">'s children filtered by <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - off'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0]">
unbind <b>{{::node.data.callDetails.arguments[0]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button><span ng-if="isPureString(node.data.callDetails.arguments[1])">'s children filtered by <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="!node.data.callDetails.arguments[0]">
unbind all events
</span>
</span>
<span ng-if="node.data.type == 'jQuery - live'" class="offenders">
bind <b>{{::node.data.callDetails.arguments[0]}}</b> on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - die'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0]">
unbind <b>{{::node.data.callDetails.arguments[0]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="!node.data.callDetails.arguments[0]">
unbind all events
</span>
</span>
<span ng-if="node.data.type == 'jQuery - delegate'" class="offenders">
bind <b>{{::node.data.callDetails.arguments[1]}}</b> on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>'s children filtered by <b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="node.data.type == 'jQuery - undelegate'" class="offenders">
<span ng-if="node.data.callDetails.arguments[1]">
unbind <b>{{::node.data.callDetails.arguments[1]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>'s children filtered by <b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="node.data.callDetails.arguments[0] && !node.data.callDetails.arguments[1]">
unbind namespace <b>{{::node.data.callDetails.arguments[0]}}</b>
</span>
<span ng-if="!node.data.callDetails.arguments[0]">
unbind all events
</span>
</span>
<span ng-if="node.data.type == 'jQuery - bind'" class="offenders">
bind <b>{{::node.data.callDetails.arguments[0]}}</b> on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - unbind'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0]">
unbind <b>{{::node.data.callDetails.arguments[0]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="!node.data.callDetails.arguments[0]">
unbind all events
</span>
</span>
<span ng-if="node.data.type == 'jQuery - blur' || node.data.type == 'jQuery - change' || node.data.type == 'jQuery - click' || node.data.type == 'jQuery - dblclick' || node.data.type == 'jQuery - error' || node.data.type == 'jQuery - focus' || node.data.type == 'jQuery - focusin' || node.data.type == 'jQuery - focusout' || node.data.type == 'jQuery - hover' || node.data.type == 'jQuery - keydown' || node.data.type == 'jQuery - keypress' || node.data.type == 'jQuery - keyup' || node.data.type == 'jQuery - load' || node.data.type == 'jQuery - mousedown' || node.data.type == 'jQuery - mouseenter' || node.data.type == 'jQuery - mouseleave' || node.data.type == 'jQuery - mousemove' || node.data.type == 'jQuery - mouseout' || node.data.type == 'jQuery - mouseover' || node.data.type == 'jQuery - mouseup' || node.data.type == 'jQuery - resize' || node.data.type == 'jQuery - scroll' || node.data.type == 'jQuery - select' || node.data.type == 'jQuery - submit' || node.data.type == 'jQuery - unload'" class="offenders">
bind on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - removeAttr' || node.data.type == 'jQuery - removeProp'" class="offenders">
remove <b>{{::node.data.callDetails.arguments[0]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - val'" class="offenders">
<span ng-if="node.data.callDetails.arguments[0]">
set value <b>{{::node.data.callDetails.arguments[0]}}</b> to <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="!node.data.callDetails.arguments[0]">
get value from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
</span>
<span ng-if="node.data.type == 'jQuery - hasClass' || node.data.type == 'jQuery - addClass' || node.data.type == 'jQuery - removeClass'" class="offenders">
<b>{{::node.data.callDetails.arguments[0]}}</b> on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - toggleClass'" class="offenders">
<span ng-if="!node.data.callDetails.arguments[0]">magic no-argument toggleClass</span>
<span ng-if="node.data.callDetails.arguments[0]">toggle <b>{{::node.data.callDetails.arguments[0]}}</b></span>
on <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[1]">to <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - children'" class="offenders">
of <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">filtered by <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - closest'" class="offenders">
closest <b>{{::node.data.callDetails.arguments[0]}}</b> from <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[1]">in context <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - next' || node.data.type == 'jQuery - nextAll'" class="offenders">
after <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">matching <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - nextUntil'" class="offenders">
after <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">until <b>{{::node.data.callDetails.arguments[0]}}</b></span>
<span ng-if="node.data.callDetails.arguments[1]">and matching <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - offsetParent'" class="offenders">
of <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
</span>
<span ng-if="node.data.type == 'jQuery - prev' || node.data.type == 'jQuery - prevAll'" class="offenders">
before <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">matching <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - prevUntil'" class="offenders">
before <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">until <b>{{::node.data.callDetails.arguments[0]}}</b></span>
<span ng-if="node.data.callDetails.arguments[1]">and matching <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - parent' || node.data.type == 'jQuery - parents'" class="offenders">
of <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">matching <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - parentsUntil'" class="offenders">
of <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">until <b>{{::node.data.callDetails.arguments[0]}}</b></span>
<span ng-if="node.data.callDetails.arguments[1]">and matching <b>{{::node.data.callDetails.arguments[1]}}</b></span>
</span>
<span ng-if="node.data.type == 'jQuery - siblings'" class="offenders">
near <jquery-context-button context="node.data.callDetails.context"></jquery-context-button>
<span ng-if="node.data.callDetails.arguments[0]">matching <b>{{::node.data.callDetails.arguments[0]}}</b></span>
</span>
</div>
<div class="details">
<div ng-class="{'icon-question': !node.warning && !node.error, 'icon-warning': node.warning || node.error}"
ng-click="onNodeDetailsClick(node)"
@@ -352,7 +127,7 @@
</div>
</div>
</div>
<div class="startTime" ng-class="node.data.loadingStep">{{::node.data.timestamp | number: 0}} ms</div>
<div class="startTime" ng-class="::node.data.loadingStep">{{::node.data.timestamp | number: 0}} ms</div>
</div>
</div>