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 @@
- -
Type
@@ -74,61 +72,9 @@
Timestamp
-
-
{{$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
-
Back to dashboard