Procházet zdrojové kódy

Speed up timeline page, by removing some angular

Gaël Métais před 10 roky
rodič
revize
50571995a4

+ 0 - 8
front/src/js/controllers/timelineCtrl.js

@@ -184,14 +184,6 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
         node.showDetails = !isOpen;
         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() {
     $scope.backToDashboard = function() {
         $location.path('/result/' + $scope.runId);
         $location.path('/result/' + $scope.runId);

+ 619 - 120
front/src/js/directives/offendersDirectives.js

@@ -1,129 +1,628 @@
-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() {
+    "use strict";
+    var offendersDirectives = angular.module('offendersDirectives', []);
+
+    function getdomTreeHTML(tree) {
+        return '<div class="domTree">' + getdomTreeInnerHTML(tree) + '</div>';
+    }
+
+    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 '';
+            }
+
+            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]);
+
+                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 '';
+            }
+        }
+
+        function getJQueryHTML(node) {
+            var type = node.data.type;
+            var args = node.data.callDetails.arguments;
+            var ctxt = node.data.callDetails.context;
             
             
-            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>';
+            // escape HTML in args
+            for (var i = 0 ; i < 4 ; i ++) {
+                if (args[i]) {
+                    args[i] = escapeHTML(args[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]);
+
+                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;
+
+                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;
+
+                case 'jQuery - append':
+                    return 'append ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt);
+
+                case 'jQuery - appendTo':
+                    return 'append' + getJQueryContextButtonHTML(ctxt) + ' to <b>' + args[0] + '</b>';
+
+                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 {
                     } else {
-                        html += '<div><span>' + key + '</span></div>';
+                        return 'get from ' + getJQueryContextButtonHTML(ctxt);
                     }
                     }
-                });
+                    break;
 
 
-                return html;
+                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 '';
             }
             }
+        }
 
 
-            element.append(recursiveHtmlBuilder(scope.tree));
-        }
-    };
-});
-
-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
-    };
-});
-
-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
-    };
-});
-
-offendersDirectives.directive('offenderButton', function() {
-    return {
-        restrict: 'C',
-        link: function(scope, element, attrs) {
-
-            element.bind('touchstart mouseenter', function(e) {
-                element.addClass('mouseOver');
-                e.preventDefault();
-            });
+        function escapeHTML(html) {
+            var entityMap = {
+                "&": "&amp;",
+                "<": "&lt;",
+                ">": "&gt;",
+                '"': '&quot;',
+                "'": '&#39;',
+                "/": '&#x2F;'
+            };
 
 
-            element.bind('touchend mouseleave click', function(e) {
-                element.removeClass('mouseOver');
-                e.preventDefault();
+            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
+        };
+    });
+
+})();

+ 0 - 8
front/src/views/domElementButton.html

@@ -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>

+ 0 - 9
front/src/views/jqueryContextButton.html

@@ -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
front/src/views/timeline.html

@@ -75,7 +75,7 @@
             <div>Timestamp</div>
             <div>Timestamp</div>
         </div>
         </div>
         <div ng-if="(!warningsFilterOn || node.warning || node.error)"
         <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,
                 showingDetails: node.showDetails,
                 jsError: node.error,
                 jsError: node.error,
                 windowPerformance: node.windowPerformance
                 windowPerformance: node.windowPerformance
@@ -87,233 +87,8 @@
                 <js-children node="node"></js-children>
                 <js-children node="node"></js-children>
             </div>
             </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 class="details">
                 <div ng-class="{'icon-question': !node.warning && !node.error, 'icon-warning': node.warning || node.error}"
                 <div ng-class="{'icon-question': !node.warning && !node.error, 'icon-warning': node.warning || node.error}"
                      ng-click="onNodeDetailsClick(node)"
                      ng-click="onNodeDetailsClick(node)"
@@ -352,7 +127,7 @@
                     </div>
                     </div>
                 </div>
                 </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>
     </div>
     </div>