Merge pull request #58 from gmetais/profilerParams

Profiler params
This commit is contained in:
Gaël Métais
2015-03-16 13:04:23 +01:00
19 changed files with 1124 additions and 354 deletions
+3 -1
View File
@@ -66,7 +66,9 @@ module.exports = function(grunt) {
'app/nodeControllers/*.js',
'app/public/scripts/*.js',
'phantomas_custom/**/*.js',
'test/**/*.js',
'test/api/*.js',
'test/core/*.js',
'test/fixtures/*.js',
'front/src/js/**/*.js'
]
},
+3 -2
View File
@@ -109,6 +109,7 @@
border-bottom-left-radius: 0.4em;
border-bottom-right-radius: 0.4em;
border-top: 1px solid #999;
z-index: 2;
}
.offenders .offenderButton .domTree {
text-align: left;
@@ -125,13 +126,13 @@
white-space: nowrap;
padding: 0.5em;
}
.offenders .offenderButton.opens.mouseOver {
.offenders .offenderButton.opens:hover {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background: #ffe0cc;
z-index: 2;
}
.offenders .offenderButton.opens.mouseOver > div {
.offenders .offenderButton.opens:hover > div {
display: block;
}
.offendersHtml {
+8 -15
View File
@@ -143,20 +143,6 @@
border: 1px dotted #aaa;
text-align: left;
}
.slowRequestsLimit {
width: 3em;
font-size: 1em;
text-align: right;
border: 1px solid #aaa;
}
input.textFilter {
box-shadow: none;
font-size: 1em;
padding: 0 0.2em;
border: 1px solid #aaa;
border-radius: none;
width: 15em;
}
.table {
display: table;
width: 100%;
@@ -226,6 +212,9 @@ input.textFilter {
.table .child span:hover {
background: #EBD8E2;
}
.table .child span:hover div {
display: inline-block;
}
.table .child span:hover .childArgs {
display: block;
position: absolute;
@@ -259,6 +248,7 @@ input.textFilter {
width: 0.8em;
}
.detailsOverlay {
display: none;
position: absolute;
right: 3em;
top: -3em;
@@ -268,13 +258,16 @@ input.textFilter {
background: #fff;
border: 2px solid #f1c40f;
border-radius: 0.5em;
z-index: 1;
z-index: 2;
}
@media screen and (max-width: 1024px) {
.detailsOverlay {
width: 25em;
}
}
.showDetails .detailsOverlay {
display: block;
}
.detailsOverlay .closeBtn {
position: absolute;
top: 0.5em;
-1
View File
@@ -13,7 +13,6 @@ var yltApp = angular.module('YellowLabTools', [
'apiService',
'menuService',
'gradeDirective',
'jsChildrenDirective',
'offendersDirectives'
]);
+1 -52
View File
@@ -52,7 +52,7 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
var lastEvent = originalExecutions[originalExecutions.length - 1];
$scope.endTime = lastEvent.data.timestamp + (lastEvent.data.time || 0);
// Filter and calculate the search index
// Filter
$scope.executionTree = [];
originalExecutions.forEach(function(node) {
@@ -66,9 +66,6 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
}
}
// Prepare a faster angular search by creating a kind of search index
node.searchIndex = (node.data.callDetails) ? [node.data.type].concat(node.data.callDetails.arguments).join('°°') : node.data.type;
$scope.executionTree.push(node);
});
}
@@ -114,38 +111,6 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
$scope.profilerData = $scope.executionTree;
}
function parseBacktrace(str) {
if (!str) {
return null;
}
var out = [];
var splited = str.split(' / ');
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
});
});
return out;
}
$scope.changeScript = function() {
initExecutionTree();
initTimeline();
@@ -170,22 +135,6 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
return lineIndex;
};
$scope.onNodeDetailsClick = function(node) {
var isOpen = node.showDetails;
if (!isOpen) {
// Close all other nodes
$scope.executionTree.forEach(function(currentNode) {
currentNode.showDetails = false;
});
// Parse the backtrace
if (!node.parsedBacktrace) {
node.parsedBacktrace = parseBacktrace(node.data.backtrace);
}
}
node.showDetails = !isOpen;
};
$scope.backToDashboard = function() {
$location.path('/result/' + $scope.runId);
@@ -1,44 +0,0 @@
var jsChildrenDirective = angular.module('jsChildrenDirective', []);
jsChildrenDirective.directive('jsChildren', function() {
return {
restrict: 'E',
scope: {
node: '=node'
},
template: '<div class="children"></div>',
replace: true,
link: function(scope, element, attrs) {
function recursiveHtmlBuilder(node) {
var html = '';
if (node.children) {
node.children.forEach(function(child) {
var childArgs = '';
if (child.data.callDetails && child.data.callDetails.arguments && child.data.callDetails.arguments.length > 0) {
childArgs = child.data.callDetails.arguments.join(' : ');
if (childArgs.length > 100) {
childArgs = childArgs.substr(0, 98) + '...';
}
}
html += '<div class="child"><span>' + child.data.type + '<div class="childArgs">' + childArgs + '</div></span>' + recursiveHtmlBuilder(child) + '</div>';
});
}
return html;
}
element.append(recursiveHtmlBuilder(scope.node));
// Bind a very special behavior:
// We want to display something in the next table-cell, at the same hight.
element.find('span').on('mouseenter', function() {
});
}
};
});
+769 -95
View File
@@ -1,50 +1,735 @@
var offendersDirectives = angular.module('offendersDirectives', []);
(function() {
"use strict";
var offendersDirectives = angular.module('offendersDirectives', []);
offendersDirectives.directive('domTree', function() {
return {
restrict: 'E',
scope: {
tree: '='
},
template: '<div class="domTree"></div>',
replace: true,
link: function(scope, element, attrs) {
function recursiveHtmlBuilder(tree) {
var html = '';
var keys = Object.keys(tree);
keys.forEach(function(key) {
if (isNaN(tree[key])) {
html += '<div><span>' + key + '</span>' + recursiveHtmlBuilder(tree[key]) + '</div>';
} else if (tree[key] > 1) {
html += '<div><span>' + key + ' <span>(x' + tree[key] + ')</span></span></div>';
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, onASingleLine) {
if (obj.tree && !onASingleLine) {
return '<div class="offenderButton opens">' + getDomElementButtonInnerHTML(obj, onASingleLine) + '</div>';
} else {
return '<div class="offenderButton">' + getDomElementButtonInnerHTML(obj, onASingleLine) + '</div>';
}
}
function getDomElementButtonInnerHTML(obj, onASingleLine) {
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 && !onASingleLine) {
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, onASingleLine) {
if (context.length === 0) {
return '<span class="offenderButton">Empty jQuery object</span>';
}
if (context.length === 1) {
return getDomElementButtonHTML(context.elements[0], onASingleLine);
}
var html = context.length + ' elements (' + getDomElementButtonHTML(context.elements[0], onASingleLine) + ', ' + getDomElementButtonHTML(context.elements[1], onASingleLine);
if (context.length === 3) {
html += ', ' + getDomElementButtonHTML(context.elements[0], onASingleLine);
} else if (context.length > 3) {
html += ' and ' + (context.length - 2) + ' more...';
}
return html + '}';
}
function isJQuery(node) {
return node.data.type.indexOf('jQuery ') === 0;
}
function getNonJQueryHTML(node, onASingleLine) {
var type = node.data.type;
if (!node.data.callDetails) {
return '';
}
var args = node.data.callDetails.arguments;
var ctxt = node.data.callDetails.context;
switch (type) {
case 'getElementById':
case 'createElement':
return '<b>' + args[0] + '</b>';
case 'getElementsByClassName':
case 'getElementsByTagName':
case 'querySelector':
case 'querySelectorAll':
return '<b>' + args[0] + '</b> on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
case 'appendChild':
return 'append ' + getDomElementButtonHTML(args[0], onASingleLine) + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
case 'insertBefore':
return 'insert' + getDomElementButtonHTML(args[0], onASingleLine) + ' into ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine) + ' before ' + getDomElementButtonHTML(args[1], onASingleLine);
case 'addEventListener':
return 'bind <b>' + args[0] + '</b> to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
case 'error':
return args[0];
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]);
}
}
if (type === 'jQuery loaded' || type === 'jQuery version change') {
return args[0];
}
switch (type) {
case 'jQuery - onDOMReady':
case 'jQuery - windowOnLoad':
return '(function)';
case 'jQuery - Sizzle call':
return '<b>' + args[0] + '</b> on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine);
case 'jQuery - find':
if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') {
return '<b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return '<b>' + args[0] + '</b>';
}
break;
case 'jQuery - html':
if (args[0] !== undefined) {
return 'set content "<b>' + args[0] + '</b>" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'get content from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - append':
return 'append ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - appendTo':
return 'append' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to <b>' + args[0] + '</b>';
case 'jQuery - prepend':
return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - prependTo':
return 'prepend ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to <b>' + args[0] + '</b>';
case 'jQuery - before':
return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - insertBefore':
return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' before <b>' + args[0] + '</b>';
case 'jQuery - after':
return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - insertAfter':
return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' after <b>' + args[0] + '</b>';
case 'jQuery - remove':
case 'jQuery - detach':
if (args[0]) {
return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by <b>' + args[0] + '</b>';
} else {
return getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - empty':
case 'jQuery - clone':
case 'jQuery - unwrap':
case 'jQuery - show':
case 'jQuery - hide':
case 'jQuery - animate':
case 'jQuery - fadeIn':
case 'jQuery - fadeOut':
case 'jQuery - fadeTo':
case 'jQuery - fadeToggle':
case 'jQuery - slideDown':
case 'jQuery - slideUp':
case 'jQuery - slideToggle':
return getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - replaceWith':
return 'replace ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' with <b>' + args[0] + '</b>';
case 'jQuery - replaceAll':
return 'replace <b>' + args[0] + '</b> with ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - text':
if (args[0]) {
return 'set text "<b>' + args[0] + '</b>" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'get text from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - wrap':
case 'jQuery - wrapAll':
return 'wrap ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within <b>' + args[0] + '</b>';
case 'jQuery - wrapInner':
return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within <b>' + args[0] + '</b>';
case 'jQuery - css':
case 'jQuery - attr':
case 'jQuery - prop':
if (isStringOfObject(args[0])) {
return 'set <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else if (args[1]) {
return 'set <b>' + args[0] + '</b> : <b>' + args[1] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'get <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - offset':
case 'jQuery - height':
case 'jQuery - innerHeight':
case 'jQuery - 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, 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 <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[1] + '</b>';
} else {
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - off':
if (args[0]) {
if (args[1]) {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[1] + '</b>';
} else {
html += '<div><span>' + key + '</span></div>';
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
});
} else {
return 'unbind all events';
}
break;
return html;
case 'jQuery - live':
case 'jQuery - bind':
return 'bind <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - die':
case 'jQuery - unbind':
if (args[0]) {
return 'unbind <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'unbind all events';
}
break;
case 'jQuery - delegate':
return 'bind <b>' + args[1] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[0] + '</b>';
case 'jQuery - undelegate':
if (args[0]) {
if (args[1]) {
return 'unbind <b>' + args[1] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by <b>' + args[0] + '</b>';
} else {
return 'unbind namespace <b>' + args[0] + '</b>';
}
} else {
return 'unbind all events';
}
break;
case 'jQuery - blur':
case 'jQuery - change':
case 'jQuery - click':
case 'jQuery - dblclick':
case 'jQuery - 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 <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - val':
if (args[0]) {
return 'set value <b>' + args[0] + '</b> to ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
} else {
return 'get value from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - hasClass':
case 'jQuery - addClass':
case 'jQuery - removeClass':
return '<b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - toggleClass':
if (args[0]) {
if (args[1]) {
return 'toggle <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to <b>' + args[1] + '</b>';
} else {
return 'toggle <b>' + args[0] + '</b> on ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
} else {
return 'magic no-argument toggleClass';
}
break;
case 'jQuery - children':
if (args[0]) {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by <b>' + args[0] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - closest':
if (args[1]) {
return 'closest <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' in context <b>' + args[1] + '</b>';
} else {
return 'closest <b>' + args[0] + '</b> from ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - next':
case 'jQuery - nextAll':
if (args[0]) {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - nextUntil':
if (args[0]) {
if (args[1]) {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - offsetParent':
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
case 'jQuery - prev':
case 'jQuery - prevAll':
if (args[0]) {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - prevUntil':
if (args[0]) {
if (args[1]) {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - parent':
case 'jQuery - parents':
if (args[0]) {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - parentsUntil':
if (args[0]) {
if (args[1]) {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b> and matching <b>' + args[1] + '</b>';
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until <b>' + args[0] + '</b>';
}
} else {
return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - siblings':
if (args[0]) {
return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching <b>' + args[0] + '</b>';
} else {
return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine);
}
break;
case 'jQuery - onDOMReady':
return '(function)';
default:
return '';
}
}
function escapeHTML(html) {
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
return String(html).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
function joinArgs(args) {
var html = '<b>' + args[0] + '</b>';
if (args[1]) {
html += ', <b>' + args[1] + '</b>';
if (args[2]) {
html += ', <b>' + args[2] + '</b>';
if (args[3]) {
html += ', and more...';
}
}
}
return html;
}
function 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 += '<div><div>can\'t find any backtrace :/</div></div>';
} else {
for (var i = 0 ; i < parsedBacktrace.length ; i++) {
html += '<div>';
html += '<div>' + (parsedBacktrace[i].fnName || '(anonymous function)') + '</div>';
html += '<div class="trace">' + getUrlLink(parsedBacktrace[i].filePath, 40) + ':' + parsedBacktrace[i].line + '</div>';
html += '</div>';
}
}
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 += '<div class="icon-warning"></div>';
} else {
html += '<div class="icon-question"></div>';
}
element.append(recursiveHtmlBuilder(scope.tree));
html += '<div class="detailsOverlay">';
html += '<div class="closeBtn">✖</div>';
if (node.data.callDetails.context && node.data.callDetails.context.length === 0) {
html += '<h4>Called on 0 jQuery element</h4><p class="advice">Useless function call, as the jQuery object is empty.</p>';
} else if (node.data.type === 'jQuery - bind' && node.data.callDetails.context.length > 5) {
html += '<p class="advice">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).</p>';
}
if (node.data.resultsNumber === 0) {
html += '<p class="advice">The query returned 0 results. Could it be unused or dead code?</p>';
} else if (node.data.resultsNumber > 0) {
html += '<p>The query returned ' + node.data.resultsNumber + ' ' + (node.data.resultsNumber > 1 ? 'results' : 'result') + '.</p>';
}
if (node.data.backtrace) {
html += '<h4>Backtrace</h4>';
html += '<div class="table">';
html += getBacktraceHTML(node.data.backtrace);
html += '</div>';
}
html += '</div>';
}
};
});
return html;
}
offendersDirectives.directive('domElementButton', function() {
return {
restrict: 'E',
scope: {
obj: '='
},
templateUrl: 'views/domElementButton.html',
replace: true
};
});
offendersDirectives.directive('profilerLine', ['$filter', function($filter) {
var numberWithCommas = $filter('number');
offendersDirectives.filter('shortenUrl', function() {
return function(url, maxLength) {
function getProfilerLineHTML(index, node) {
return '<div class="index">' + (index + 1) + '</div>' +
'<div class="type">' + node.data.type + (node.children ? '<div class="children">' + recursiveChildrenHTML(node) + '</div>' : '') + '</div>' +
'<div class="value offenders">' + getTimelineParamsHTML(node, false) + '</div>' +
'<div class="details">' + getTimelineDetailsHTML(node) + '</div>' +
'<div class="startTime ' + node.data.loadingStep + '">' + numberWithCommas(node.data.timestamp, 0) + ' ms</div>';
}
function recursiveChildrenHTML(node) {
var html = '';
if (node.children) {
node.children.forEach(function(child) {
html += '<div class="child"><span>' + child.data.type + '<div class="childArgs">' + getTimelineParamsHTML(child, true) + '</div></span>' + recursiveChildrenHTML(child) + '</div>';
});
}
return html;
}
function onDetailsClick(row) {
// Close if it's 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 closeDetails(row) {
row.classList.remove('showDetails');
// Unbind the close button
row.querySelector('.closeBtn').removeEventListener('click', closeDetails);
}
return {
restrict: 'E',
scope: {
index: '=',
node: '='
},
template: '<div id="line_{{index}}"></div>',
replace: true,
link: function(scope, element) {
if (scope.node.error) {
element.addClass('jsError');
} else if (scope.node.windowPerformance) {
element.addClass('windowPerformance');
}
element.append(getProfilerLineHTML(scope.index, scope.node));
// Bind click on the details icon
var detailsIcon = element[0].querySelector('.details div');
if (detailsIcon) {
detailsIcon.addEventListener('click', function() {
onDetailsClick(this.parentNode.parentNode);
});
}
}
};
}]);
function shortenUrl(url, maxLength) {
if (!maxLength) {
maxLength = 110;
}
@@ -54,67 +739,56 @@ offendersDirectives.filter('shortenUrl', function() {
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('shortenUrl', function() {
return shortenUrl;
});
offendersDirectives.filter('encodeURIComponent', function() {
return window.encodeURIComponent;
});
function getUrlLink(url, maxLength) {
return '<a href="' + url + '" target="_blank" title="' + url + '">' + shortenUrl(url, maxLength) + '</a>';
}
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('urlLink', function() {
return {
restrict: 'E',
scope: {
url: '=',
maxLength: '='
},
template: '<a href="{{url}}" target="_blank" title="{{url}}">{{url | shortenUrl:maxLength}}</a>',
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.filter('encodeURIComponent', function() {
return window.encodeURIComponent;
});
offendersDirectives.directive('offenderButton', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
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
};
});
console.log('initializing touchstart');
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
};
});
element.bind('touchstart mouseenter', function(e) {
element.addClass('mouseOver');
e.preventDefault();
});
element.bind('touchend mouseleave click', function(e) {
element.removeClass('mouseOver');
e.preventDefault();
});
}
};
});
})();
+2 -1
View File
@@ -120,6 +120,7 @@
border-bottom-left-radius: 0.4em;
border-bottom-right-radius: 0.4em;
border-top: 1px solid #999;
z-index: 2;
}
.domTree {
@@ -140,7 +141,7 @@
padding: 0.5em;
}
&.opens.mouseOver {
&.opens:hover {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background: #ffe0cc;
+9 -17
View File
@@ -162,22 +162,6 @@
text-align: left;
}
.slowRequestsLimit {
width: 3em;
font-size: 1em;
text-align: right;
border: 1px solid #aaa;
}
input.textFilter {
box-shadow: none;
font-size: 1em;
padding: 0 0.2em;
border: 1px solid #aaa;
border-radius: none;
width: 15em;
}
.table {
display: table;
width: 100%;
@@ -260,6 +244,10 @@ input.textFilter {
span:hover {
background: #EBD8E2;
div {
display: inline-block;
}
.childArgs {
display: block;
position: absolute;
@@ -300,6 +288,7 @@ input.textFilter {
}
.detailsOverlay {
display: none;
position: absolute;
right: 3em;
top: -3em;
@@ -309,13 +298,16 @@ input.textFilter {
background: #fff;
border: 2px solid #f1c40f;
border-radius: 0.5em;
z-index: 1;
z-index: 2;
}
@media screen and (max-width: 1024px) {
.detailsOverlay {
width: 25em;
}
}
.showDetails .detailsOverlay {
display: block;
}
.detailsOverlay .closeBtn {
position: absolute;
top: 0.5em;
-1
View File
@@ -36,7 +36,6 @@
<script src="/js/services/apiService.js"></script>
<script src="/js/services/menuService.js"></script>
<script src="/js/directives/gradeDirective.js"></script>
<script src="/js/directives/jsChildrenDirective.js"></script>
<script src="/js/directives/offendersDirectives.js"></script>
<!-- endbuild -->
<head>
-7
View File
@@ -1,7 +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>
<dom-tree ng-if="obj.tree" tree="obj.tree"></dom-tree>
</div>
+1 -76
View File
@@ -63,15 +63,8 @@
<input type="checkbox" ng-model="warningsFilterOn" id="warningsFilterOn" />
<label for="warningsFilterOn">Show warnings only</label>
</div>
<div>
<input type="checkbox" ng-model="textFilterOn" />
Filter by
<input type="text" ng-model="textFilter" placeholder="search..." class="textFilter" ng-change="textFilterOn = true" />
</div>
</div>
<div class="table">
<toto data-info="mavariable"></toto>
<div class="headers">
<div><!-- index --></div>
<div>Type</div>
@@ -79,77 +72,9 @@
<div><!-- details --></div>
<div>Timestamp</div>
</div>
<div ng-if="(!warningsFilterOn || node.warning || node.error) && (!textFilterOn || !textFilter.length || node.searchIndex.indexOf(textFilter) >= 0)"
ng-repeat="node in profilerData" ng-class="{
showingDetails: node.showDetails,
jsError: node.error,
windowPerformance: node.windowPerformance
}" id="line_{{$index}}">
<div class="index">{{$index + 1}}</div>
<div class="type">
{{node.data.type}}
<js-children node="node"></js-children>
</div>
<profiler-line ng-repeat="node in profilerData" data-index="$index" node="node"></profiler-line>
<div class="value">
{{node.data.callDetails.arguments[0]}}
<span ng-if="node.data.callDetails.arguments.length > 1"> : {{node.data.callDetails.arguments[1]}}</span>
<span ng-if="node.data.callDetails.arguments.length > 2"> : {{node.data.callDetails.arguments[2]}}</span>
<span ng-if="node.data.callDetails.arguments.length > 3"> : {{node.data.callDetails.arguments[3]}}</span>
</div>
<div class="details">
<div ng-class="{'icon-question': !node.warning && !node.error, 'icon-warning': node.warning || node.error}"
ng-click="onNodeDetailsClick(node)"
ng-if="node.data.type != 'jQuery loaded' && node.data.type != 'jQuery version change' && !node.windowPerformance"></div>
<div class="detailsOverlay" ng-if="node.showDetails">
<div class="closeBtn" ng-click="onNodeDetailsClick(node)"></div>
<div ng-if="node.data.callDetails.context.domElement">
<h4>Called on DOM element</h4>
<div>{{node.data.callDetails.context.domElement}}</div>
</div>
<div ng-if="node.data.callDetails.context.length === 0">
<h4>Called on 0 jQuery element</h4>
<p class="advice">Useless function call, as the jQuery object is empty.</p>
</div>
<div ng-if="node.data.callDetails.context.length == 1 && node.data.callDetails.context.firstElementPath">
<h4>Called on 1 jQuery element</h4>
<div>{{node.data.callDetails.context.firstElementPath}}</div>
</div>
<div ng-if="node.data.callDetails.context.length > 1">
<h4>Called on {{node.data.callDetails.context.length}} jQuery elements</h4>
<p class="advice" ng-if="node.data.type == 'jQuery - bind' && node.data.callDetails.context.length > 5">
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).
</p>
<p ng-if="node.data.callDetails.context.firstElementPath"><b>First one is:</b> {{node.data.callDetails.context.firstElementPath}}</p>
</div>
<p class="advice" ng-if="node.data.resultsNumber === 0">
The query returned 0 results. Could it be unused or dead code?
</p>
<div ng-if="node.parsedBacktrace">
<h4>Backtrace</h4>
<div class="table">
<div ng-repeat="trace in node.parsedBacktrace track by $index">
<div>{{trace.fnName || '(anonymous)'}}</div>
<div class="trace"><url-link url="trace.filePath" max-length="40"></url-link>:{{trace.line}}</div>
</div>
<div ng-if="node.parsedBacktrace.length == 0 && node.data.type != 'script loaded'">
<div>can't find any backtrace :/</div>
</div>
</div>
</div>
</div>
</div>
<div class="startTime" ng-class="node.data.loadingStep">{{node.data.timestamp | number: 0}} ms</div>
</div>
</div>
<div class="backToDashboard"><a href="#" ng-click="backToDashboard()">Back to dashboard</a></div>
+9
View File
@@ -32,6 +32,15 @@ var OffendersHelpers = function() {
};
this.domPathToDomElementObj = function(domPath) {
if (typeof domPath === 'boolean') {
return {
// Not a normal element path
type: 'notAnElement',
element: domPath
};
}
var domArray = this.domPathToArray(domPath);
var domTree = this.listOfDomArraysToTree([this.domPathToArray(domPath)]);
+27
View File
@@ -1,5 +1,7 @@
var debug = require('debug')('ylt:jsExecutionTransformer');
var offendersHelpers = require('../offendersHelpers');
var jsExecutionTransformer = function() {
this.transform = function(data) {
@@ -46,6 +48,22 @@ var jsExecutionTransformer = function() {
data.toolsResults.phantomas.metrics.domComplete = node.data.timestamp;
break;
}
// Change the list of dom paths into a tree
treeRecursiveParser(node, function(node) {
if (node.data.callDetails && node.data.callDetails.context && node.data.callDetails.context.length > 0) {
node.data.callDetails.context.elements = node.data.callDetails.context.elements.map(offendersHelpers.domPathToDomElementObj, offendersHelpers);
}
if (node.data.type === 'appendChild' || node.data.type === 'insertBefore') {
node.data.callDetails.arguments[0] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[0]);
}
if (node.data.type === 'insertBefore') {
node.data.callDetails.arguments[1] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[1]);
}
});
});
}
@@ -57,6 +75,15 @@ var jsExecutionTransformer = function() {
return javascriptExecutionTree;
};
function treeRecursiveParser(node, fn) {
if (node.children) {
node.children.forEach(function(child) {
treeRecursiveParser(child, fn);
});
}
fn(node);
}
};
module.exports = new jsExecutionTransformer();
@@ -33,9 +33,6 @@ exports.module = function(phantomas) {
phantomas.enterContext({
type: 'getElementById',
callDetails: {
context: {
domElement: '#document'
},
arguments: ['#' + id]
},
backtrace: phantomas.getBacktrace()
@@ -65,7 +62,8 @@ exports.module = function(phantomas) {
type: 'getElementsByClassName',
callDetails: {
context: {
domElement: context
length: 1,
elements: [context]
},
arguments: ['.' + className]
},
@@ -103,7 +101,8 @@ exports.module = function(phantomas) {
type: 'getElementsByTagName',
callDetails: {
context: {
domElement: context
length: 1,
elements: [context]
},
arguments: [tagName]
},
@@ -145,7 +144,8 @@ exports.module = function(phantomas) {
type: 'querySelector',
callDetails: {
context: {
domElement: context
length: 1,
elements: [context]
},
arguments: [selector]
},
@@ -177,7 +177,8 @@ exports.module = function(phantomas) {
type: 'querySelectorAll',
callDetails: {
context: {
domElement: context
length: 1,
elements: [context]
},
arguments: [selector]
},
@@ -237,7 +238,8 @@ exports.module = function(phantomas) {
type: 'appendChild',
callDetails: {
context: {
domElement: context
length: 1,
elements: [context]
},
arguments: [appended]
},
@@ -245,20 +247,25 @@ exports.module = function(phantomas) {
});
}
function insertBeforeSpyBefore(child) {
function insertBeforeSpyBefore(child, refElement) {
/*jshint validthis: true */
var context = phantomas.getDOMPath(this);
var appended = phantomas.getDOMPath(child);
var referent = phantomas.getDOMPath(refElement);
appendChild(child, this, context, appended);
phantomas.enterContext({
type: 'insertBefore',
callDetails: {
context: {
domElement: context
length: 1,
elements: [context]
},
arguments: [appended]
arguments: [
appended,
referent
]
},
backtrace: phantomas.getBacktrace()
});
@@ -277,9 +284,6 @@ exports.module = function(phantomas) {
phantomas.enterContext({
type: 'createElement',
callDetails: {
context: {
domElement: '#document'
},
arguments: [tagName]
},
backtrace: phantomas.getBacktrace()
@@ -295,9 +299,6 @@ exports.module = function(phantomas) {
phantomas.enterContext({
type: 'createTextNode',
callDetails: {
context: {
domElement: '#document'
},
arguments: [text]
},
backtrace: phantomas.getBacktrace()
@@ -313,9 +314,6 @@ exports.module = function(phantomas) {
phantomas.enterContext({
type: 'createDocumentFragment',
callDetails: {
context: {
domElement: '#document'
},
arguments: []
},
backtrace: phantomas.getBacktrace()
@@ -27,7 +27,8 @@ exports.module = function(phantomas) {
type: 'addEventListener',
callDetails: {
context: {
domElement: path
length: 1,
elements: [path]
},
arguments: [eventType]
},
@@ -68,7 +68,7 @@ exports.module = function(phantomas) {
'slideUp',
'slideToggle',
// generic events
// Generic events
'on',
'off',
'live',
@@ -79,7 +79,7 @@ exports.module = function(phantomas) {
'bind',
'unbind',
// more events
// More events
'blur',
'change',
'click',
@@ -106,7 +106,7 @@ exports.module = function(phantomas) {
'submit',
'unload',
// attributes
// Attributes
'attr',
'prop',
'removeAttr',
@@ -115,12 +115,31 @@ exports.module = function(phantomas) {
'hasClass',
'addClass',
'removeClass',
'toggleClass'
'toggleClass',
];
var jQueryTraversalFunctions = [
'children',
'closest',
'find',
'next',
'nextAll',
'nextUntil',
'offsetParent',
'parent',
'parents',
'parentsUntil',
'prev',
'prevAll',
'prevUntil',
'siblings'
];
jQueryFunctions = jQueryFunctions.concat(jQueryTraversalFunctions);
// spy calls to jQuery functions
phantomas.once('init', function() {
phantomas.evaluate(function(jQueryFunctions) {
phantomas.evaluate(function(jQueryFunctions, jQueryTraversalFunctions) {
(function(phantomas) {
var jQuery;
var oldJQuery;
@@ -178,11 +197,11 @@ exports.module = function(phantomas) {
phantomas.addOffender('jQuerySizzleCalls', '%s (in %s)', selector, (phantomas.getDOMPath(context) || 'unknown'));
phantomas.enterContext({
type: 'jQuery - find',
type: 'jQuery - Sizzle call',
callDetails: {
context: {
length: this.length,
firstElementPath: phantomas.getDOMPath(context)
length: 1,
elements: [phantomas.getDOMPath(context)]
},
arguments: [selector]
},
@@ -196,6 +215,36 @@ exports.module = function(phantomas) {
phantomas.leaveContext(moreData);
}) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
phantomas.spy(jQuery.fn, 'init', function(selector, context) {
if (typeof selector === 'string' && /^#([\w\-]*)$/.exec(selector) !== null && !context) {
phantomas.enterContext({
type: 'jQuery - find',
callDetails: {
arguments: [selector]
},
backtrace: phantomas.getBacktrace()
});
}
}, function(result) {
var data = phantomas.getContextData();
if (data.type === 'jQuery - find' &&
!data.callDetails.context &&
data.callDetails.arguments.length === 1 &&
/^#([\w\-]*)$/.exec(data.callDetails.arguments[0]) !== null) {
var moreData = {
resultsNumber : (result && result.length) ? result.length : 0
};
phantomas.leaveContext(moreData);
}
});
/*if (!jQuery.event) {
phantomas.spy(jQuery.event, 'trigger', function(ev, data, elem) {
var path = phantomas.getDOMPath(elem),
@@ -213,20 +262,11 @@ exports.module = function(phantomas) {
if ((eventName === 'load') && (this[0] === window)) {
phantomas.incrMetric('jQueryWindowOnLoadFunctions');
phantomas.addOffender('jQueryWindowOnLoadFunctions', phantomas.getCaller(2));
phantomas.pushContext({
type: 'jQuery - windowOnLoad',
callDetails: {
arguments: [func]
},
backtrace: phantomas.getBacktrace()
});
}
});
// Add spys on many jQuery functions
jQueryFunctions.forEach(function(functionName) {
var capitalizedName = functionName.substring(0,1).toUpperCase() + functionName.substring(1);
phantomas.spy(jQuery.fn, functionName, function(args) {
@@ -234,6 +274,11 @@ exports.module = function(phantomas) {
args = [].slice.call(arguments);
args.forEach(function(arg, index) {
if (arg instanceof Array) {
arg = '[Array]';
}
if (arg instanceof Object) {
if (arg instanceof jQuery || (arg.jquery && arg.jquery.length > 0)) {
@@ -286,13 +331,17 @@ exports.module = function(phantomas) {
args[index] = arg;
});
var elements = [];
for (var i = 0 ; i < this.length ; i++) {
elements.push(phantomas.getDOMPath(this[i]));
}
phantomas.enterContext({
type: 'jQuery - ' + functionName,
callDetails: {
context: {
length: this.length,
firstElementPath: phantomas.getDOMPath(this[0])
elements: elements
},
arguments: args
},
@@ -300,12 +349,19 @@ exports.module = function(phantomas) {
});
}, function(result) {
phantomas.leaveContext();
}) || phantomas.log('jQuery: can not track jQuery - ' + capitalizedName + ' (this version of jQuery doesn\'t support it)');
if (jQueryTraversalFunctions.indexOf(functionName) >= 0) {
var moreData = {
resultsNumber : (result && result.length) ? result.length : 0
};
phantomas.leaveContext(moreData);
} else {
phantomas.leaveContext();
}
}) || phantomas.log('jQuery: can not track jQuery - ' + functionName + ' (this version of jQuery doesn\'t support it)');
});
});
})(window.__phantomas);
}, jQueryFunctions);
}, jQueryFunctions, jQueryTraversalFunctions);
});
+193
View File
@@ -0,0 +1,193 @@
<html>
<head>
<title>Simple page</title>
<script src="jquery1.8.3.js"></script>
</head>
<body>
<h1>Simple page</h1>
<ul>
<li>li 1</li>
<li>li 2</li>
<li>li 3</li>
</ul>
<ul id="foo">
<li>li4</li>
</ul>
<script>
// Let's start the spaghetti code!!!
$('ul').find('li');
$('li', $('#foo'));
var foo = document.getElementById('foo');
$('li', foo);
foo.getElementsByClassName('bar');
document.getElementsByClassName('bar');
foo.querySelector('ul li');
document.querySelector('ul li');
foo.querySelectorAll('ul li');
document.querySelectorAll('ul li');
var li = document.createElement('li');
foo.insertBefore(li, foo.children[0]);
li.addEventListener('click', function() {});
var $li = $(li);
$li.html();
$li.html('some content');
$('li').html();
$li.html('');
$('select').html();
$li.append('<div>woot</div>');
$li.append(document.createElement('span'));
$li.append($('<b>what</b>'));
$li.append(['<div>test1</div>', document.createElement('div')]);
$li.append('<div>test3</div>', '<div>test4</div>');
$('<div>test4</div>').appendTo($li);
$('<div>test5</div>').appendTo('<div class="bar"></div>');
$('li').prepend('<span class="prepended">prepended!</span>');
$('<div>test0</div>').prependTo($li);
$('<div>before bar</div>').before('.bar');
$('<div>before $li</div>').insertBefore($li);
$li.after('textnode after');
$('<div>after $li</div>').insertAfter($li);
$('.prepended').remove('.someClass');
$('.prepended').remove();
$('.prepended').detach();
$li.find('div').empty();
var $ulClones = $('ul').clone();
$('span').replaceWith('<span class="newspan">New span</span>');
$('<span class="newnewspan">New new span</span>').replaceAll('.newspan');
$('li').text();
$('li').eq(0).text('New li 1');
$('li').eq(0).wrap('<div></div>');
$('ul').wrapAll('<div class="ulWrapper"></div>');
$('.ulWrapper').wrapInner('<div class="doubleUlWrapper"></div>');
$('.doubleUlWrapper').unwrap();
$li.css('color', '#F00');
$li.css('color');
$li.css({'font-size': '1.2em', 'background': '#BFF'});
$li.offset();
$li.offset({left: 12, top: 23});
$li.offset(function() {return {left: 55, top: 44};});
$li.height();
$li.hide();
$li.hide(120);
$li.hide({duration: 133, done: function() {console.log(12);}});
$li.hide(222, 'swing', function() {console.log(9);});
$li.toggle();
$li.toggle(true);
$li.toggle(false);
$li.toggle(200);
var handler = function() {};
var $tbody = $('#dataTable tbody');
var $tbodytr = $('#dataTable tbody tr');
$tbodytr.on('click', handler);
$tbody.on('click', 'tr', handler);
$tbody.on('click', {someData: 'Joe'}, handler);
$tbodytr.off('click', handler);
$tbodytr.off('click', 'tr');
$tbodytr.off('click');
$tbodytr.off();
$tbody.delegate('tr', 'click', handler);
$tbody.undelegate('tr', 'click');
$tbody.undelegate('someNamespage');
$tbody.undelegate();
$tbody.one('zoop', handler);
$tbody.bind('zoop', handler);
$tbody.unbind('zoop', handler);
$tbody.bind();
$tbody.blur(handler);
$tbody.change(handler);
$tbody.click(handler);
$tbody.dblclick(handler);
$tbody.error(handler);
$tbody.focus(handler);
$tbody.focusin(handler);
$tbody.focusout(handler);
$tbody.hover(handler);
$tbody.keydown(handler);
$tbody.keypress(handler);
$tbody.keyup(handler);
$tbody.load(handler);
$tbody.mousedown(handler);
$tbody.mouseenter(handler);
$tbody.mouseleave(handler);
$tbody.mousemove(handler);
$tbody.mouseout(handler);
$tbody.mouseover(handler);
$tbody.mouseup(handler);
$tbody.resize(handler);
$tbody.scroll(handler);
$tbody.select(handler);
$tbody.submit(handler);
$tbody.unload(handler);
$tbody.attr({alt: "Beijing Brush Seller", title: "photo by Kelly Clark"});
$tbody.attr('alt');
$tbody.attr('alt', 'An alternative alt');
$tbody.attr('alt', '');
$tbody.removeAttr('alt');
$tbody.prop({checked: true});
$tbody.prop('checked');
$tbody.prop('checked', true);
$tbody.prop('checked', '');
$tbody.removeProp('checked');
var $input = $('<input type="text"></input>');
$input.val('some value');
$input.val();
$tbody.addClass(function() {return 'foo';});
$tbody.hasClass('foo');
$tbody.removeClass('foo');
$tbody.toggleClass('foo');
$tbody.toggleClass('foo', true);
$tbody.toggleClass();
$li.children();
$li.children('span');
$li.closest('ul');
$li.closest('ul', document.getElementById('foo'));
$li.find('span');
$li.find(document.getElementById('foo'));
$li.find($li);
$li.next();
$li.next('li');
$li.nextAll();
$li.nextAll('li');
$li.nextUntil('.theEnd', 'li');
$li.offsetParent();
$li.prevAll();
$li.prevAll('li');
$li.prevUntil('.theBeginning', 'li');
$li.parent();
$li.parent('ul');
$li.parents();
$li.parents('div');
$li.parentsUntil('ul');
$li.parentsUntil('body', 'div');
$li.siblings();
$li.siblings('li');
</script>
</body>
</html>
File diff suppressed because one or more lines are too long