');
- $compile(element.contents())(scope.$new());
+ var childScope = scope.$new();
+ childScope.modelState = modelState;
+ childScope.scopeState = scopeState;
+
+ $compile(element.contents())(childScope);
}
};
});
diff --git a/js/directives/watcherTree.js b/js/directives/watcherTree.js
index 74bf5ec..4bedb99 100644
--- a/js/directives/watcherTree.js
+++ b/js/directives/watcherTree.js
@@ -1,5 +1,9 @@
// watchers tree
panelApp.directive('batWatcherTree', function($compile) {
+
+ // make toggle settings persist across $compile
+ var scopeState = {};
+
return {
restrict: 'E',
terminal: true,
@@ -13,8 +17,8 @@ panelApp.directive('batWatcherTree', function($compile) {
element.append(
'
' +
'
Scope ({{val.id}}) | ' +
- '
toggle' +
- '
' +
+ '
toggle' +
+ '
' +
'
' +
'- ' +
'toggle ' +
@@ -30,7 +34,10 @@ panelApp.directive('batWatcherTree', function($compile) {
'
' +
'
');
- $compile(element.contents())(scope.$new());
+ var childScope = scope.$new();
+ childScope.scopeState = scopeState;
+
+ $compile(element.contents())(childScope);
}
};
});
diff --git a/js/inject/debug.js b/js/inject/debug.js
index 54806de..a697e1e 100644
--- a/js/inject/debug.js
+++ b/js/inject/debug.js
@@ -1,64 +1,6 @@
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;
@@ -107,9 +49,82 @@ var inject = function () {
return;
}
+ // polyfill for performance.now on older webkit
+ if (!performance.now) {
+ performance.now = performance.webkitNow;
+ }
+
+ // 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
+ // ===
+
// Instrumentation
// ---------------
+ var getScopeLocals = function (scope) {
+ var scopeLocals = {}, prop;
+ for (prop in scope) {
+ if (scope.hasOwnProperty(prop) && prop !== 'this' && prop[0] !== '$') {
+ scopeLocals[prop] = decycle(scope[prop]);
+ }
+ }
+ return scopeLocals;
+ };
+
//var bootstrap = window.angular.bootstrap;
var debug = window.__ngDebug = {
watchers: {}, // map of scopes --> watchers
@@ -117,21 +132,73 @@ var inject = function () {
watchPerf: {}, // maps of watch/apply exp/fns to perf data
applyPerf: {},
- scopes: {}, // map of scope.$ids --> scope objects
+ scopes: {}, // map of scope.$ids --> model objects
rootScopes: {}, // map of $ids --> refs to root scopes
+ rootScopeDirty: {},
+
+ getRootScopeIds: function () {
+ var ids = [];
+ angular.forEach(debug.rootScopes, function (elt, id) {
+ ids.push(id);
+ });
+ return ids;
+ },
+ getScopeTree: function (id) {
+ if (debug.rootScopeDirty[id] === false) {
+ return;
+ }
+ var traverse = function (sc) {
+ var tree = {
+ id: sc.$id,
+ locals: debug.scopes[sc.$id],
+ children: []
+ };
+
+ var child = sc.$$childHead;
+ if (child) {
+ do {
+ tree.children.push(traverse(child));
+ } while (child !== sc.$$childTail && (child = child.$$nextSibling));
+ }
- deps: []
- };
+ return tree;
+ };
+ var root = debug.rootScopes[id];
+ var tree = traverse(root);
- var getScopeLocals = function (scope) {
- var scopeLocals = {}, prop;
- for (prop in scope) {
- if (scope.hasOwnProperty(prop) && prop !== 'this' && prop[0] !== '$') {
- scopeLocals[prop] = scope[prop];
+ if (tree) {
+ debug.rootScopeDirty[id] = false;
}
- }
- return scopeLocals;
+
+ return tree;
+ },
+
+ getWatchTree: function (id) {
+ var traverse = function (sc) {
+ var tree = {
+ id: sc.$id,
+ watchers: debug.watchers[sc.$id],
+ children: []
+ };
+
+ var child = sc.$$childHead;
+ if (child) {
+ do {
+ tree.children.push(traverse(child));
+ } while (child !== sc.$$childTail && (child = child.$$nextSibling));
+ }
+
+ return tree;
+ };
+
+ var root = debug.rootScopes[id];
+ var tree = traverse(root);
+
+ return tree;
+ },
+
+ deps: []
};
var annotate = angular.injector().annotate;
@@ -154,7 +221,7 @@ var inject = function () {
}
function assertArgFn(arg, name, acceptArrayAnnotation) {
if (acceptArrayAnnotation && angular.isArray(arg)) {
- arg = arg[arg.length - 1];
+ arg = arg[arg.length - 1];
}
assertArg(angular.isFunction(arg), name, 'not a function, got ' +
@@ -182,7 +249,7 @@ var inject = function () {
}
} else if (angular.isArray(fn)) {
last = fn.length - 1;
- assertArgFn(fn[last], 'fn')
+ assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
@@ -201,9 +268,9 @@ var inject = function () {
'service'
].forEach(function (met) {
var temp = $provide[met];
- $provide[met] = function (thingName, definition) {
+ $provide[met] = function (name, definition) {
debug.deps.push({
- name: thingName,
+ name: name,
imports: annotate(definition)
});
return temp.apply(this, arguments);
@@ -243,7 +310,7 @@ var inject = function () {
// ==========================
var _watch = $delegate.__proto__.$watch;
- $delegate.__proto__.$watch = function(watchExpression, applyFunction) {
+ $delegate.__proto__.$watch = function (watchExpression, applyFunction) {
var thatScope = this;
var watchStr = watchFnToHumanReadableString(watchExpression);
@@ -253,6 +320,10 @@ var inject = function () {
calls: 0
};
}
+ if (!debug.watchers[thatScope.$id]) {
+ debug.watchers[thatScope.$id] = [];
+ }
+ debug.watchers[thatScope.$id].push(watchStr);
// patch watchExpression
// ---------------------
@@ -260,18 +331,18 @@ var inject = function () {
var w = watchExpression;
if (typeof w === 'function') {
watchExpression = function () {
- var start = window.performance.webkitNow();
+ var start = performance.now();
var ret = w.apply(this, arguments);
- var end = window.performance.webkitNow();
+ var end = performance.now();
debug.watchPerf[watchStr].time += (end - start);
debug.watchPerf[watchStr].calls += 1;
return ret;
};
} else {
watchExpression = function () {
- var start = window.performance.webkitNow();
+ var start = performance.now();
var ret = thatScope.$eval(w);
- var end = window.performance.webkitNow();
+ var end = performance.now();
debug.watchPerf[watchStr].time += (end - start);
debug.watchPerf[watchStr].calls += 1;
return ret;
@@ -284,11 +355,11 @@ var inject = function () {
var applyStr = applyFunction.toString();
var unpatchedApplyFunction = applyFunction;
applyFunction = function () {
- var start = window.performance.webkitNow();
+ var start = performance.now();
var ret = unpatchedApplyFunction.apply(this, arguments);
- var end = window.performance.webkitNow();
+ var end = performance.now();
- debug.scopes[thatScope.$id] = getScopeLocals(thatScope)
+ 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] = {
@@ -297,7 +368,8 @@ var inject = function () {
};
}
debug.applyPerf[applyStr].time += (end - start);
- debug.applyPerf[applyStr].calls += (end - start);
+ debug.applyPerf[applyStr].calls += 1;
+ debug.rootScopeDirty[thatScope.$root.$id] = true;
return ret;
};
}
@@ -313,6 +385,8 @@ var inject = function () {
$delegate.__proto__.$destroy = function () {
if (debug.watchers[this.$id]) {
delete debug.watchers[this.$id];
+ }
+ if (debug.scopes[this.$id]) {
delete debug.scopes[this.$id];
}
return _destroy.apply(this, arguments);
@@ -320,8 +394,8 @@ var inject = function () {
var _new = $delegate.__proto__.$new;
$delegate.__proto__.$new = function () {
- var ret = _new.apply(this, arguments);
+ var ret = _new.apply(this, arguments);
if (ret.$root) {
debug.rootScopes[ret.$root.$id] = ret.$root;
}
@@ -331,6 +405,8 @@ var inject = function () {
debug.watchers[ret.$id] = [];
}
+ debug.rootScopeDirty[ret.$root.$id] = true;
+
return ret;
};
@@ -338,9 +414,9 @@ var inject = function () {
// -----------
var _apply = $delegate.__proto__.$apply;
$delegate.__proto__.$apply = function (fn) {
- var start = window.performance.webkitNow();
+ var start = performance.now();
var ret = _apply.apply(this, arguments);
- var end = window.performance.webkitNow();
+ var end = performance.now();
// If the debugging option is enabled, log to console
// --------------------------------------------------
diff --git a/js/services/appContext.js b/js/services/appContext.js
index cabb2ee..6bb3623 100644
--- a/js/services/appContext.js
+++ b/js/services/appContext.js
@@ -5,18 +5,23 @@ panelApp.factory('appContext', function (chromeExtension) {
// ============
var _debugCache = {},
+ _scopeCache = {},
+ _watchCache = {},
_pollListeners = [],
_pollInterval = 500;
// TODO: make this private and have it automatically poll?
var getDebugData = function (callback) {
chromeExtension.eval(function (window) {
- // Detect whether or not this is an AngularJS app
- if (!window.angular || !window.__ngDebug) {
+ if (!window.__ngDebug) {
return {};
- } else {
- return window.__ngDebug;
}
+ return {
+ deps: window.__ngDebug.deps,
+ applyPerf: window.__ngDebug.applyPerf,
+ watchPerf: window.__ngDebug.watchPerf,
+ roots: window.__ngDebug.getRootScopeIds()
+ };
},
function (data) {
if (data) {
@@ -64,15 +69,33 @@ panelApp.factory('appContext', function (chromeExtension) {
// -------
getHistogram: function () {
- return _debugCache.histogram;
+ return _debugCache.watchPerf;
},
getListOfRoots: function () {
return _debugCache.roots;
},
- getModelTrees: function () {
- return _debugCache.trees;
+ getModelTree: function (id) {
+ chromeExtension.eval("function (window, args) {" +
+ "return window.__ngDebug.getScopeTree(args.id);" +
+ "}", {id: id}, function (tree) {
+ if (tree) {
+ _scopeCache[id] = tree;
+ }
+ });
+ return _scopeCache[id];
+ },
+
+ getWatchTree: function (id) {
+ chromeExtension.eval("function (window, args) {" +
+ "return window.__ngDebug.getWatchTree(args.id);" +
+ "}", {id: id}, function (tree) {
+ if (tree) {
+ _watchCache[id] = tree;
+ }
+ });
+ return _watchCache[id];
},
getDeps: function () {
@@ -104,7 +127,7 @@ panelApp.factory('appContext', function (chromeExtension) {
"return 'good';" +
"} else {" +
"return 'info';" +
- "}" +
+ "}" +
"}" +
"}" +
"return 'info';" +
diff --git a/js/services/appInspect.js b/js/services/appInspect.js
new file mode 100644
index 0000000..b4a92ac
--- /dev/null
+++ b/js/services/appInspect.js
@@ -0,0 +1,27 @@
+// Service for highlighting parts of the application
+panelApp.factory('appInspect', function (chromeExtension) {
+ return {
+ enable: function () {
+ chromeExtension.eval(function (window) {
+ var angular = window.angular;
+ var popover = angular.element('
');
+ angular.element(window.document.body).append(popover);
+ angular.element('.ng-scope').
+ on('mouseover', function () {
+ var thisElt = this;
+ var thisScope = angular.element(this).scope();
+ var models = {};
+ for (prop in thisScope) {
+ if (thisScope.hasOwnProperty(prop) && prop !== 'this' && prop[0] !== '$') {
+ models[prop] = thisScope[prop];
+ }
+ }
+ var str = JSON.stringify(models);
+ console.log(str);
+ //console.log(thisScope);
+ popover.html(str);
+ });
+ });
+ }
+ };
+});
diff --git a/panes/model.html b/panes/model.html
index 7de2a2f..17aac29 100644
--- a/panes/model.html
+++ b/panes/model.html
@@ -1,9 +1,9 @@
-
Model Tree
+
Models
-
+
-
\ No newline at end of file
+
diff --git a/panes/perf.html b/panes/perf.html
index ef81b3a..15e1b1e 100644
--- a/panes/perf.html
+++ b/panes/perf.html
@@ -14,7 +14,7 @@