diff --git a/front/src/css/timeline.css b/front/src/css/timeline.css
index 8ac97c1..ab00a13 100644
--- a/front/src/css/timeline.css
+++ b/front/src/css/timeline.css
@@ -212,6 +212,9 @@
.table .child span:hover {
background: #EBD8E2;
}
+.table .child span:hover div {
+ display: inline-block;
+}
.table .child span:hover .childArgs {
display: block;
position: absolute;
@@ -245,6 +248,7 @@
width: 0.8em;
}
.detailsOverlay {
+ display: none;
position: absolute;
right: 3em;
top: -3em;
@@ -261,6 +265,9 @@
width: 25em;
}
}
+.showDetails .detailsOverlay {
+ display: block;
+}
.detailsOverlay .closeBtn {
position: absolute;
top: 0.5em;
diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js
index dd56eb5..18aecd6 100644
--- a/front/src/js/directives/offendersDirectives.js
+++ b/front/src/js/directives/offendersDirectives.js
@@ -41,15 +41,15 @@
};
});
- function getDomElementButtonHTML(obj) {
- if (obj.tree) {
- return '
' + getDomElementButtonInnerHTML(obj) + '
';
+ function getDomElementButtonHTML(obj, onASingleLine) {
+ if (obj.tree && !onASingleLine) {
+ return '' + getDomElementButtonInnerHTML(obj, onASingleLine) + '
';
+ } else {
+ return '' + getDomElementButtonInnerHTML(obj, onASingleLine) + '
';
}
-
- return '' + getDomElementButtonInnerHTML(obj) + '
';
}
- function getDomElementButtonInnerHTML(obj) {
+ function getDomElementButtonInnerHTML(obj, onASingleLine) {
if (obj.type === 'html' ||
obj.type === 'body' ||
obj.type === 'head' ||
@@ -72,7 +72,7 @@
html = 'Created element ' + obj.element + '';
}
- if (obj.tree) {
+ if (obj.tree && !onASingleLine) {
html += getdomTreeHTML(obj.tree);
}
@@ -94,480 +94,641 @@
});
- function getJQueryContextButtonHTML(context) {
+ function getJQueryContextButtonHTML(context, onASingleLine) {
if (context.length === 0) {
return 'Empty jQuery object';
}
if (context.length === 1) {
- return getDomElementButtonHTML(context.elements[0]);
+ return getDomElementButtonHTML(context.elements[0], onASingleLine);
}
- var html = context.length + ' elements (' + getDomElementButtonHTML(context.elements[0]) + ', ' + getDomElementButtonHTML(context.elements[1]);
+ var html = context.length + ' elements (' + getDomElementButtonHTML(context.elements[0], onASingleLine) + ', ' + getDomElementButtonHTML(context.elements[1], onASingleLine);
if (context.length === 3) {
- html += ', ' + getDomElementButtonHTML(context.elements[0]);
+ html += ', ' + getDomElementButtonHTML(context.elements[0], onASingleLine);
} 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 isJQuery(node) {
+ return node.data.type.indexOf('jQuery ') === 0;
+ }
+
+ function getNonJQueryHTML(node, onASingleLine) {
+ var type = node.data.type;
+
+ if (!node.data.callDetails) {
+ return '';
}
- 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;
+ var args = node.data.callDetails.arguments;
+ var ctxt = node.data.callDetails.context;
- switch (type) {
- case 'getElementById':
- case 'createElement':
- return '' + args[0] + '';
+ switch (type) {
+ case 'getElementById':
+ case 'createElement':
+ return '' + args[0] + '';
- case 'getElementsByClassName':
- case 'getElementsByTagName':
- case 'querySelector':
- case 'querySelectorAll':
- return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0]);
+ case 'getElementsByClassName':
+ case 'getElementsByTagName':
+ case 'querySelector':
+ case 'querySelectorAll':
+ return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
- case 'appendChild':
- return 'append ' + getDomElementButtonHTML(args[0]) + ' to ' + getDomElementButtonHTML(ctxt.elements[0]);
+ case 'appendChild':
+ return 'append ' + getDomElementButtonHTML(args[0], onASingleLine) + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
- case 'insertBefore':
- return 'insert' + getDomElementButtonHTML(args[0]) + ' into ' + getDomElementButtonHTML(ctxt.elements[0]) + ' before ' + getDomElementButtonHTML(args[1]);
+ case 'insertBefore':
+ return 'insert' + getDomElementButtonHTML(args[0], onASingleLine) + ' into ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine) + ' before ' + getDomElementButtonHTML(args[1], onASingleLine);
- case 'addEventListener':
- return 'bind ' + args[0] + ' to ' + getDomElementButtonHTML(ctxt.elements[0]);
+ case 'addEventListener':
+ return 'bind ' + args[0] + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
- 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;
-
- // 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') {
+ case 'error':
return args[0];
- }
- switch (type) {
- case 'jQuery - onDOMReady':
- case 'jQuery - windowOnLoad':
- return '(function)';
+ default:
+ return '';
+ }
+ }
- case 'jQuery - Sizzle call':
- return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0]);
-
- case 'jQuery - find':
- if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') {
- return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt);
- } else {
- return '' + args[0] + '';
- }
- break;
-
- case 'jQuery - html':
- if (args[0] !== undefined) {
- return 'set content "' + args[0] + '" 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 ' + args[0] + '';
-
- case 'jQuery - prepend':
- return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - prependTo':
- return 'prepend ' + getJQueryContextButtonHTML(ctxt) + ' to ' + args[0] + '';
-
- case 'jQuery - before':
- return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - insertBefore':
- return 'insert ' + getJQueryContextButtonHTML(ctxt) + ' before ' + args[0] + '';
-
- case 'jQuery - after':
- return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - insertAfter':
- return 'insert ' + getJQueryContextButtonHTML(ctxt) + ' after ' + args[0] + '';
-
- case 'jQuery - remove':
- case 'jQuery - detach':
- if (args[0]) {
- return getJQueryContextButtonHTML(ctxt) + ' filtered by ' + args[0] + '';
- } 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 ' + args[0] + '';
-
- case 'jQuery - replaceAll':
- return 'replace ' + args[0] + ' with ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - text':
- if (args[0]) {
- return 'set text "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt);
- } else {
- return 'get text from ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - wrap':
- case 'jQuery - wrapAll':
- return 'wrap ' + getJQueryContextButtonHTML(ctxt) + ' within ' + args[0] + '';
-
- case 'jQuery - wrapInner':
- return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt) + ' within ' + args[0] + '';
-
- case 'jQuery - css':
- case 'jQuery - attr':
- case 'jQuery - prop':
- if (isStringOfObject(args[0])) {
- return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt);
- } else if (args[1]) {
- return 'set ' + args[0] + ' : ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt);
- } else {
- return 'get ' + args[0] + ' 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 ' + args[0] + ' 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 ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt) + '\'s children filtered by ' + args[1] + '';
- } else {
- return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - off':
- if (args[0]) {
- if (args[1]) {
- return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt) + '\'s children filtered by ' + args[1] + '';
- } else {
- return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt);
- }
- } else {
- return 'unbind all events';
- }
- break;
-
- case 'jQuery - live':
- case 'jQuery - bind':
- return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - die':
- case 'jQuery - unbind':
- if (args[0]) {
- return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt);
- } else {
- return 'unbind all events';
- }
- break;
-
- case 'jQuery - delegate':
- return 'bind ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt) + '\'s children filtered by ' + args[0] + '';
-
- case 'jQuery - undelegate':
- if (args[0]) {
- if (args[1]) {
- return 'unbind ' + args[1] + ' from ' + getJQueryContextButtonHTML(ctxt) + '\'s children filtered by ' + args[0] + '';
- } else {
- return 'unbind namespace ' + args[0] + '';
- }
- } else {
- return 'unbind all events';
- }
- break;
-
- case 'jQuery - blur':
- case 'jQuery - change':
- case 'jQuery - click':
- case 'jQuery - dblclick':
- case 'jQuery - 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 ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - val':
- if (args[0]) {
- return 'set value ' + args[0] + ' to ' + getJQueryContextButtonHTML(ctxt);
- } else {
- return 'get value from ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - hasClass':
- case 'jQuery - addClass':
- case 'jQuery - removeClass':
- return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt);
-
- case 'jQuery - toggleClass':
- if (args[0]) {
- if (args[1]) {
- return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt) + ' to ' + args[1] + '';
- } else {
- return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt);
- }
- } else {
- return 'magic no-argument toggleClass';
- }
- break;
-
- case 'jQuery - children':
- if (args[0]) {
- return 'of ' + getJQueryContextButtonHTML(ctxt) + ' filtered by ' + args[0] + '';
- } else {
- return 'of ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - closest':
- if (args[1]) {
- return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt) + ' in context ' + args[1] + '';
- } else {
- return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - next':
- case 'jQuery - nextAll':
- if (args[0]) {
- return 'after ' + getJQueryContextButtonHTML(ctxt) + ' matching ' + args[0] + '';
- } else {
- return 'after ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - nextUntil':
- if (args[0]) {
- if (args[1]) {
- return 'after ' + getJQueryContextButtonHTML(ctxt) + ' until ' + args[0] + ' and matching ' + args[1] + '';
- } else {
- return 'after ' + getJQueryContextButtonHTML(ctxt) + ' until ' + args[0] + '';
- }
- } 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 ' + args[0] + '';
- } else {
- return 'before ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - prevUntil':
- if (args[0]) {
- if (args[1]) {
- return 'before ' + getJQueryContextButtonHTML(ctxt) + ' until ' + args[0] + ' and matching ' + args[1] + '';
- } else {
- return 'before ' + getJQueryContextButtonHTML(ctxt) + ' until ' + args[0] + '';
- }
- } else {
- return 'before ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - parent':
- case 'jQuery - parents':
- if (args[0]) {
- return 'of ' + getJQueryContextButtonHTML(ctxt) + ' matching ' + args[0] + '';
- } else {
- return 'of ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - parentsUntil':
- if (args[0]) {
- if (args[1]) {
- return 'of ' + getJQueryContextButtonHTML(ctxt) + ' until ' + args[0] + ' and matching ' + args[1] + '';
- } else {
- return 'of ' + getJQueryContextButtonHTML(ctxt) + ' until ' + args[0] + '';
- }
- } else {
- return 'of ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - siblings':
- if (args[0]) {
- return 'near ' + getJQueryContextButtonHTML(ctxt) + ' matching ' + args[0] + '';
- } else {
- return 'near ' + getJQueryContextButtonHTML(ctxt);
- }
- break;
-
- case 'jQuery - onDOMReady':
- return '(function)';
-
- default:
- return '';
+ function getJQueryHTML(node, onASingleLine) {
+ 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]);
}
}
- function escapeHTML(html) {
- var entityMap = {
- "&": "&",
- "<": "<",
- ">": ">",
- '"': '"',
- "'": ''',
- "/": '/'
- };
-
- return String(html).replace(/[&<>"'\/]/g, function (s) {
- return entityMap[s];
- });
+ if (type === 'jQuery loaded' || type === 'jQuery version change') {
+ return args[0];
}
- function joinArgs(args) {
- var html = '' + args[0] + '';
- if (args[1]) {
- html += ', ' + args[1] + '';
- if (args[2]) {
- html += ', ' + args[2] + '';
- if (args[3]) {
- html += ', and more...';
+ switch (type) {
+ case 'jQuery - onDOMReady':
+ case 'jQuery - windowOnLoad':
+ return '(function)';
+
+ case 'jQuery - Sizzle call':
+ return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
+
+ case 'jQuery - find':
+ if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') {
+ return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return '' + args[0] + '';
+ }
+ break;
+
+ case 'jQuery - html':
+ if (args[0] !== undefined) {
+ return 'set content "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return 'get content from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - append':
+ return 'append ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - appendTo':
+ return 'append' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[0] + '';
+
+ case 'jQuery - prepend':
+ return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - prependTo':
+ return 'prepend ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[0] + '';
+
+ case 'jQuery - before':
+ return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - insertBefore':
+ return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' before ' + args[0] + '';
+
+ case 'jQuery - after':
+ return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - insertAfter':
+ return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' after ' + args[0] + '';
+
+ case 'jQuery - remove':
+ case 'jQuery - detach':
+ if (args[0]) {
+ return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by ' + args[0] + '';
+ } else {
+ return getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - empty':
+ case 'jQuery - clone':
+ case 'jQuery - unwrap':
+ case 'jQuery - show':
+ case 'jQuery - hide':
+ case 'jQuery - animate':
+ case 'jQuery - fadeIn':
+ case 'jQuery - fadeOut':
+ case 'jQuery - fadeTo':
+ case 'jQuery - fadeToggle':
+ case 'jQuery - slideDown':
+ case 'jQuery - slideUp':
+ case 'jQuery - slideToggle':
+ return getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - replaceWith':
+ return 'replace ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' with ' + args[0] + '';
+
+ case 'jQuery - replaceAll':
+ return 'replace ' + args[0] + ' with ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - text':
+ if (args[0]) {
+ return 'set text "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return 'get text from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - wrap':
+ case 'jQuery - wrapAll':
+ return 'wrap ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within ' + args[0] + '';
+
+ case 'jQuery - wrapInner':
+ return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within ' + args[0] + '';
+
+ case 'jQuery - css':
+ case 'jQuery - attr':
+ case 'jQuery - prop':
+ if (isStringOfObject(args[0])) {
+ return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else if (args[1]) {
+ return 'set ' + args[0] + ' : ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return 'get ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - offset':
+ case 'jQuery - height':
+ case 'jQuery - innerHeight':
+ case 'jQuery - outerHeight':
+ case 'jQuery - width':
+ case 'jQuery - innerWidth':
+ case 'jQuery - outerWidth':
+ case 'jQuery - scrollLeft':
+ case 'jQuery - scrollTop':
+ case 'jQuery - position':
+ if (args[0]) {
+ return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - toggle':
+ if (args[0] === 'true') {
+ return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to visible';
+ } else if (args[0] === 'false') {
+ return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to hidden';
+ } else {
+ return getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - on':
+ case 'jQuery - one':
+ if (args[1]) {
+ return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + '';
+ } else {
+ return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - off':
+ if (args[0]) {
+ if (args[1]) {
+ return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + '';
+ } else {
+ return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
+ } else {
+ return 'unbind all events';
+ }
+ break;
+
+ case 'jQuery - live':
+ case 'jQuery - bind':
+ return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - die':
+ case 'jQuery - unbind':
+ if (args[0]) {
+ return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return 'unbind all events';
+ }
+ break;
+
+ case 'jQuery - delegate':
+ return 'bind ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[0] + '';
+
+ case 'jQuery - undelegate':
+ if (args[0]) {
+ if (args[1]) {
+ return 'unbind ' + args[1] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[0] + '';
+ } else {
+ return 'unbind namespace ' + args[0] + '';
+ }
+ } else {
+ return 'unbind all events';
+ }
+ break;
+
+ case 'jQuery - blur':
+ case 'jQuery - change':
+ case 'jQuery - click':
+ case 'jQuery - dblclick':
+ case 'jQuery - 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, onASingleLine);
+
+ case 'jQuery - removeAttr':
+ case 'jQuery - removeProp':
+ return 'remove ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - val':
+ if (args[0]) {
+ return 'set value ' + args[0] + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ } else {
+ return 'get value from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - hasClass':
+ case 'jQuery - addClass':
+ case 'jQuery - removeClass':
+ return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - toggleClass':
+ if (args[0]) {
+ if (args[1]) {
+ return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[1] + '';
+ } else {
+ return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ } else {
+ return 'magic no-argument toggleClass';
+ }
+ break;
+
+ case 'jQuery - children':
+ if (args[0]) {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by ' + args[0] + '';
+ } else {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - closest':
+ if (args[1]) {
+ return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' in context ' + args[1] + '';
+ } else {
+ return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - next':
+ case 'jQuery - nextAll':
+ if (args[0]) {
+ return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + '';
+ } else {
+ return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - nextUntil':
+ if (args[0]) {
+ if (args[1]) {
+ return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + '';
+ } else {
+ return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + '';
+ }
+ } else {
+ return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - offsetParent':
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+
+ case 'jQuery - prev':
+ case 'jQuery - prevAll':
+ if (args[0]) {
+ return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + '';
+ } else {
+ return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - prevUntil':
+ if (args[0]) {
+ if (args[1]) {
+ return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + '';
+ } else {
+ return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + '';
+ }
+ } else {
+ return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - parent':
+ case 'jQuery - parents':
+ if (args[0]) {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + '';
+ } else {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - parentsUntil':
+ if (args[0]) {
+ if (args[1]) {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + '';
+ } else {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + '';
+ }
+ } else {
+ return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - siblings':
+ if (args[0]) {
+ return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + '';
+ } else {
+ return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
+ }
+ break;
+
+ case 'jQuery - onDOMReady':
+ return '(function)';
+
+ default:
+ return '';
+ }
+ }
+
+ function escapeHTML(html) {
+ var entityMap = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': '"',
+ "'": ''',
+ "/": '/'
+ };
+
+ return String(html).replace(/[&<>"'\/]/g, function (s) {
+ return entityMap[s];
+ });
+ }
+
+ function joinArgs(args) {
+ var html = '' + args[0] + '';
+ if (args[1]) {
+ html += ', ' + args[1] + '';
+ if (args[2]) {
+ html += ', ' + args[2] + '';
+ if (args[3]) {
+ html += ', and more...';
}
}
+ }
+ return html;
+ }
+
+ function isStringOfObject(str) {
+ return typeof str === 'string' && str[0] === '{' && str[str.length - 1] === '}';
+ }
+
+ function isPureString(str) {
+ return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown';
+ }
+
+ function getTimelineParamsHTML(node, onASingleLine) {
+ if (isJQuery(node)) {
+ return getJQueryHTML(node, onASingleLine);
+ } else {
+ return getNonJQueryHTML(node, onASingleLine);
+ }
+ }
+
+ function getBacktraceHTML(backtrace) {
+ var html = '';
+ var parsedBacktrace = parseBacktrace(backtrace);
+
+ if (!parsedBacktrace || parsedBacktrace.length === 0) {
+ html += 'can\'t find any backtrace :/
';
+ } else {
+ for (var i = 0 ; i < parsedBacktrace.length ; i++) {
+ html += '';
+ html += '
' + (parsedBacktrace[i].fnName || '(anonymous)') + '
';
+ html += '
:' + parsedBacktrace[i].line + '
';
+ html += '
';
+ }
+ }
+
+ return html;
+ }
+
+ function parseBacktrace(str) {
+ if (!str) {
+ return null;
+ }
+
+ var out = [];
+ var splited = str.split(' / ');
+
+ try {
+
+ splited.forEach(function(trace) {
+ var fnName = null, fileAndLine;
+
+ var withFnResult = /^([^\s\(]+) \((.+:\d+)\)$/.exec(trace);
+ if (withFnResult === null) {
+ fileAndLine = trace;
+ } else {
+ fnName = withFnResult[1];
+ fileAndLine = withFnResult[2];
+ }
+
+ var fileAndLineSplit = /^(.*):(\d+)$/.exec(fileAndLine);
+ var filePath = fileAndLineSplit[1];
+ var line = fileAndLineSplit[2];
+
+ out.push({
+ fnName: fnName,
+ filePath: filePath,
+ line: line
+ });
+ });
+
+ } catch(e) {
+ return null;
+ }
+
+ return out;
+ }
+
+ function getTimelineDetailsHTML(node) {
+ var html = '';
+
+ if (node.data.type != 'jQuery loaded' && node.data.type != 'jQuery version change' && !node.windowPerformance) {
+ if (node.warning || node.error) {
+ html += '';
+ } else {
+ html += '';
+ }
+
+ html += '';
+ html += '
✖
';
+
+ if (node.data.callDetails.context && node.data.callDetails.context.length === 0) {
+ html += '
Called on 0 jQuery element
Useless function call, as the jQuery object is empty.
';
+ } else if (node.data.type === 'jQuery - bind' && node.data.callDetails.context.length > 5) {
+ html += '
The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method is preferable if available (from v1.7).
';
+ }
+
+ if (node.data.resultsNumber === 0) {
+ html += '
The query returned 0 results. Could it be unused or dead code?
';
+ } else if (node.data.resultsNumber > 0) {
+ html += '
The query returned ' + node.data.resultsNumber + ' ' + (node.data.resultsNumber > 1 ? 'results' : 'result') + '.
';
+ }
+
+ if (node.data.backtrace) {
+ html += '
Backtrace
';
+ html += '
';
+ html += getBacktraceHTML(node.data.backtrace);
+ html += '
';
+ }
+
+ html += '
';
+ }
+
+ return html;
+ }
+
+
+ offendersDirectives.directive('profilerLine', ['$filter', function($filter) {
+
+ var numberWithCommas = $filter('number');
+
+ function getProfilerLineHTML(index, node) {
+ return '' + (index + 1) + '
' +
+ '' + node.data.type + (node.children ? '
' + recursiveChildrenHTML(node) + '
' : '') + '
' +
+ '' + getTimelineParamsHTML(node, false) + '
' +
+ '' + getTimelineDetailsHTML(node) + '
' +
+ '' + numberWithCommas(node.data.timestamp, 0) + ' ms
';
+ }
+
+ function recursiveChildrenHTML(node) {
+ var html = '';
+
+ if (node.children) {
+ node.children.forEach(function(child) {
+ html += '' + child.data.type + '' + getTimelineParamsHTML(child, true) + '
' + recursiveChildrenHTML(child) + '
';
+ });
+ }
+
return html;
}
- function getHTML(node) {
- if (isJQuery(node)) {
- return getJQueryHTML(node);
- } else {
- return getNonJQueryHTML(node);
+ function onDetailsClick(row) {
+ // Close if it's alreay open
+ if (row.classList.contains('showDetails')) {
+ closeDetails(row);
+ return;
}
+
+ // Close any other open details overlay
+ var openOnes = document.getElementsByClassName('showDetails');
+ if (openOnes.length > 0) {
+ openOnes[0].classList.remove('showDetails');
+ }
+
+ // Make it appear
+ row.classList.add('showDetails');
+
+ // Bind the close button
+ row.querySelector('.closeBtn').addEventListener('click', function() {
+ closeDetails(row);
+ });
}
- function isStringOfObject(str) {
- return typeof str === 'string' && str[0] === '{' && str[str.length - 1] === '}';
- }
+ function closeDetails(row) {
+ row.classList.remove('showDetails');
- function isPureString(str) {
- return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown';
+ // Unbind the close button
+ row.querySelector('.closeBtn').removeEventListener('click', closeDetails);
}
return {
restrict: 'E',
scope: {
+ index: '=',
node: '='
},
- template: '',
+ template: '',
replace: true,
link: function(scope, element) {
- var html = getHTML(scope.node);
- if (html) {
- element.append(html);
+
+ if (scope.node.error) {
+ element.addClass('jsError');
+ } else if (scope.node.windowPerformance) {
+ element.addClass('windowPerformance');
+ }
+
+ element.append(getProfilerLineHTML(scope.index, scope.node));
+
+ // Bind click on the details icon
+ var detailsIcon = element[0].querySelector('.details div');
+ if (detailsIcon) {
+ detailsIcon.addEventListener('click', function() {
+ onDetailsClick(this.parentNode.parentNode);
+ });
}
}
};
- });
+ }]);
+
offendersDirectives.filter('shortenUrl', function() {
return function(url, maxLength) {
diff --git a/front/src/less/timeline.less b/front/src/less/timeline.less
index 9f5d790..7e353f8 100644
--- a/front/src/less/timeline.less
+++ b/front/src/less/timeline.less
@@ -244,6 +244,10 @@
span:hover {
background: #EBD8E2;
+ div {
+ display: inline-block;
+ }
+
.childArgs {
display: block;
position: absolute;
@@ -284,6 +288,7 @@
}
.detailsOverlay {
+ display: none;
position: absolute;
right: 3em;
top: -3em;
@@ -300,6 +305,9 @@
width: 25em;
}
}
+.showDetails .detailsOverlay {
+ display: block;
+}
.detailsOverlay .closeBtn {
position: absolute;
top: 0.5em;
diff --git a/front/src/views/timeline.html b/front/src/views/timeline.html
index 030faff..176653a 100644
--- a/front/src/views/timeline.html
+++ b/front/src/views/timeline.html
@@ -65,8 +65,6 @@
-
-
-
-
{{$index + 1}}
-
- {{node.data.type}}
-
-
+
-
-
-
-
-
-
-
✖
-
-
-
Called on 0 jQuery element
-
Useless function call, as the jQuery object is empty.
-
-
-
- The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method is preferable if available (from v1.7).
-
-
-
- The query returned 0 results. Could it be unused or dead code?
-
-
- The query returned .
-
-
-
-
Backtrace
-
-
-
{{trace.fnName || '(anonymous)'}}
-
:{{trace.line}}
-
-
-
can't find any backtrace :/
-
-
-
-
-
-
{{node.data.timestamp | number: 0}} ms
-