diff --git a/js/inject/debug.js b/js/inject/debug.js index bc98dc4..e09bbde 100644 --- a/js/inject/debug.js +++ b/js/inject/debug.js @@ -1,7 +1,68 @@ var inject = function () { document.head.appendChild((function () { + + // Helpers + // ======= + + // Based on cycle.js + // 2011-08-24 + // https://github.com/douglascrockford/JSON-js/blob/master/cycle.js + + // Make a deep copy of an object or array, assuring that there is at most + // one instance of each object or array in the resulting structure. The + // duplicate references (which might be forming cycles) are replaced with + // an object of the form + // {$ref: PATH} + // where the PATH is a JSONPath string that locates the first occurance. + var decycle = function (object) { + var objects = [], // Keep a reference to each unique object or array + paths = []; // Keep the path to each unique object or array + + return (function derez(value, path) { + var i, // The loop counter + name, // Property name + nu; // The new object or array + switch (typeof value) { + case 'object': + if (!value) { + return null; + } + for (i = 0; i < objects.length; i += 1) { + if (objects[i] === value) { + return {$ref: paths[i]}; + } + } + objects.push(value); + paths.push(path); + if (Object.prototype.toString.apply(value) === '[object Array]') { + nu = []; + for (i = 0; i < value.length; i += 1) { + nu[i] = derez(value[i], path + '[' + i + ']'); + } + } else { + nu = {}; + for (name in value) { + if (Object.prototype.hasOwnProperty.call(value, name)) { + nu[name] = derez(value[name], + path + '[' + JSON.stringify(name) + ']'); + } + } + } + return nu; + case 'number': + case 'string': + case 'boolean': + return value; + } + }(object, '$')); + }; + // End + // === + var fn = function bootstrap (window) { + var angular = window.angular; + // Helper to determine if the root 'ng' module has been loaded // window.angular may be available if the app is bootstrapped asynchronously, but 'ng' might // finish loading later. @@ -51,12 +112,28 @@ var inject = function () { //var bootstrap = window.angular.bootstrap; var debug = window.__ngDebug = { - watchers: {}, - watchExp: {}, - watchList: {}, + watchers: {}, // map of scopes --> watchers + + watchPerf: {}, // maps of watch/apply exp/fns to perf data + applyPerf: {}, + + scopes: {}, // map of scope.$ids --> scope objects + rootScopes: [], // array of refs to root scopes + deps: [] }; + + var getScopeLocals = function (scope) { + var scopeLocals = {}, prop; + for (prop in scope) { + if (scope.hasOwnProperty(prop) && prop !== 'this' && prop[0] !== '$') { + scopeLocals[prop] = scope[prop]; + } + } + return scopeLocals; + }; + var annotate = angular.injector().annotate; // not all versions of AngularJS expose annotate @@ -117,6 +194,7 @@ var inject = function () { var ng = angular.module('ng'); ng.config(function ($provide) { + // methods to patch [ 'provider', 'factory', @@ -144,113 +222,133 @@ var inject = function () { } }; - // patch registering watchers - // -------------------------- - var watch = $delegate.__proto__.$watch; - $delegate.__proto__.$watch = function() { - if (!debug.watchers[this.$id]) { - debug.watchers[this.$id] = []; + var applyFnToLogString = function (fn) { + var str; + if (fn) { + if (fn.name) { + str = fn.name; + } else if (fn.toString().split('\n').length > 1) { + str = 'fn () { ' + fn.toString().split('\n')[1].trim() + ' /* ... */ }'; + } else { + str = fn.toString().trim().substr(0, 30) + '...'; + } + } else { + str = '$apply'; } - var str = watchFnToHumanReadableString(arguments[0]); + return str; + }; - debug.watchers[this.$id].push(str); - + + // patch registering watchers + // ========================== + + var _watch = $delegate.__proto__.$watch; + $delegate.__proto__.$watch = function(watchExpression, applyFunction) { + var thatScope = this; + var watchStr = watchFnToHumanReadableString(watchExpression); - var w = arguments[0]; + if (!debug.watchPerf[watchStr]) { + debug.watchPerf[watchStr] = { + time: 0, + calls: 0 + }; + } + + // patch watchExpression + // --------------------- + + var w = watchExpression; if (typeof w === 'function') { - arguments[0] = function () { + watchExpression = function () { var start = window.performance.webkitNow(); var ret = w.apply(this, arguments); var end = window.performance.webkitNow(); - if (!debug.watchExp[str]) { - debug.watchExp[str] = { - time: 0, - calls: 0 - }; - } - debug.watchExp[str].time += (end - start); - debug.watchExp[str].calls += 1; + debug.watchPerf[watchStr].time += (end - start); + debug.watchPerf[watchStr].calls += 1; return ret; }; } else { var thatScope = this; - arguments[0] = function () { + watchExpression = function () { var start = window.performance.webkitNow(); var ret = thatScope.$eval(w); var end = window.performance.webkitNow(); - if (!debug.watchExp[str]) { - debug.watchExp[str] = { + debug.watchPerf[watchStr].time += (end - start); + debug.watchPerf[watchStr].calls += 1; + return ret; + }; + } + + // patch applyFunction + // ------------------- + if (applyFunction) { + var applyStr = applyFunction.toString(); + var unpatchedApplyFunction = applyFunction; + applyFunction = function () { + var start = window.performance.webkitNow(); + var ret = unpatchedApplyFunction.apply(this, arguments); + var end = window.performance.webkitNow(); + + debug.scopes[thatScope.$id] = getScopeLocals(thatScope) + //TODO: move these checks out of here and into registering the watcher + if (!debug.applyPerf[applyStr]) { + debug.applyPerf[applyStr] = { time: 0, calls: 0 }; } - debug.watchExp[str].time += (end - start); - debug.watchExp[str].calls += 1; + debug.applyPerf[applyStr].time += (end - start); + debug.applyPerf[applyStr].calls += (end - start); return ret; }; } - var fn = arguments[1]; - arguments[1] = function () { - var start = window.performance.webkitNow(); - var ret = fn.apply(this, arguments); - var end = window.performance.webkitNow(); - var str = fn.toString(); - if (typeof debug.watchList[str] !== 'number') { - debug.watchList[str] = 0; - //debug.watchList[str].total = 0; - } - debug.watchList[str] += (end - start); - //debug.watchList[str].total += (end - start); - //debug.dirty = true; - return ret; - }; - - return watch.apply(this, arguments); + return _watch.apply(this, arguments); }; + // patch destroy // ------------- - /* - var destroy = $delegate.__proto__.$destroy; + var _destroy = $delegate.__proto__.$destroy; $delegate.__proto__.$destroy = function () { if (debug.watchers[this.$id]) { delete debug.watchers[this.$id]; + delete debug.scopes[this.$id]; } - debug.dirty = true; - return destroy.apply(this, arguments); + return _destroy.apply(this, arguments); }; - */ - + + var _new = $delegate.__proto__.$new; + $delegate.__proto__.$new = function () { + var ret = _new.apply(this, arguments); + + // create empty watchers array for this scope + if (!debug.watchers[ret.$id]) { + debug.watchers[ret.$id] = []; + } + + return ret; + } + // patch apply // ----------- - var apply = $delegate.__proto__.$apply; + var _apply = $delegate.__proto__.$apply; $delegate.__proto__.$apply = function (fn) { var start = window.performance.webkitNow(); - var ret = apply.apply(this, arguments); + var ret = _apply.apply(this, arguments); var end = window.performance.webkitNow(); // If the debugging option is enabled, log to console // -------------------------------------------------- if (debug.log) { - if (fn) { - if (fn.name) { - fn = fn.name; - } else if (fn.toString().split('\n').length > 1) { - fn = 'fn () { ' + fn.toString().split('\n')[1].trim() + ' /* ... */ }'; - } else { - fn = fn.toString().trim().substr(0, 30) + '...'; - } - } else { - fn = '$apply'; - } - console.log(fn + '\t\t' + (end - start).toPrecision(4) + 'ms'); + console.log(applyFnToLogString(fn) + '\t\t' + (end - start).toPrecision(4) + 'ms'); } return ret; }; + return $delegate; }); }); diff --git a/js/services/appContext.js b/js/services/appContext.js index a5e1e7a..b686121 100644 --- a/js/services/appContext.js +++ b/js/services/appContext.js @@ -12,152 +12,11 @@ panelApp.factory('appContext', function (chromeExtension) { var getDebugData = function (callback) { chromeExtension.eval(function (window) { // Detect whether or not this is an AngularJS app - if (!window.angular) { - return false; - } - - // cycle.js - // 2011-08-24 - // https://github.com/douglascrockford/JSON-js/blob/master/cycle.js - - // Make a deep copy of an object or array, assuring that there is at most - // one instance of each object or array in the resulting structure. The - // duplicate references (which might be forming cycles) are replaced with - // an object of the form - // {$ref: PATH} - // where the PATH is a JSONPath string that locates the first occurance. - var decycle = function decycle(object) { - var objects = [], // Keep a reference to each unique object or array - paths = []; // Keep the path to each unique object or array - - return (function derez(value, path) { - var i, // The loop counter - name, // Property name - nu; // The new object or array - switch (typeof value) { - case 'object': - if (!value) { - return null; - } - for (i = 0; i < objects.length; i += 1) { - if (objects[i] === value) { - return {$ref: paths[i]}; - } - } - objects.push(value); - paths.push(path); - if (Object.prototype.toString.apply(value) === '[object Array]') { - nu = []; - for (i = 0; i < value.length; i += 1) { - nu[i] = derez(value[i], path + '[' + i + ']'); - } - } else { - nu = {}; - for (name in value) { - if (Object.prototype.hasOwnProperty.call(value, name)) { - nu[name] = derez(value[name], - path + '[' + JSON.stringify(name) + ']'); - } - } - } - return nu; - case 'number': - case 'string': - case 'boolean': - return value; - } - }(object, '$')); - }; - - var rootIds = []; - var rootScopes = []; - - var elts = window.document.getElementsByClassName('ng-scope'); - var i; - for (i = 0; i < elts.length; i++) { - (function (elt) { - var $scope = window.angular.element(elt).scope(); - - while ($scope.$parent) { - $scope = $scope.$parent; - } - if ($scope === $scope.$root && rootScopes.indexOf($scope) === -1) { - rootScopes.push($scope); - rootIds.push($scope.$id); - } - }(elts[i])); - } - - var getScopeTree = function (scope) { - var tree = {}; - var getScopeNode = function (scope, node) { - - // copy scope's locals - node.locals = {}; - - var scopeLocals = {}; - for (prop in scope) { - if (scope.hasOwnProperty(prop) && prop !== 'this' && prop[0] !== '$') { - scopeLocals[prop] = scope[prop]; - } - } - - node.locals = decycle(scopeLocals); - - node.id = scope.$id; - - if (window.__ngDebug) { - node.watchers = __ngDebug.watchers[scope.$id]; - } - - // recursively get children scopes - node.children = []; - var child; - if (scope.$$childHead) { - child = scope.$$childHead; - - do { - getScopeNode(child, node.children[node.children.length] = {}); - } while (child = child.$$nextSibling); - } - }; - - getScopeNode(scope, tree); - return tree; - }; - - var trees = {}; - rootScopes.forEach(function (root) { - trees[root.$id] = getScopeTree(root); - }); - - // get histogram data - var histogram = [], - deps; - - // performance - if (window.__ngDebug) { - (function (info) { - for (exp in info) { - if (info.hasOwnProperty(exp)) { - histogram.push({ - name: exp, - time: info[exp].time, - calls: info[exp].calls - }); - } - } - }(window.__ngDebug.watchExp)); - - deps = __ngDebug.deps; + if (!window.angular || !window.__ngDebug) { + return {}; + } else { + return window.__ngDebug; } - - return { - roots: rootIds, - trees: trees, - histogram: histogram, - deps: deps - }; }, function (data) { _debugCache = data;