2012-07-02 21:09:51 +00:00
|
|
|
// Service for doing stuff in the context of the application being debugged
|
2012-08-16 00:13:34 +00:00
|
|
|
panelApp.factory('appContext', function (chromeExtension) {
|
2012-07-02 21:09:51 +00:00
|
|
|
|
|
|
|
// Private vars
|
|
|
|
// ============
|
|
|
|
|
|
|
|
var _debugCache = {},
|
|
|
|
_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) {
|
|
|
|
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 = {};
|
|
|
|
|
2012-08-09 00:26:59 +00:00
|
|
|
var scopeLocals = {};
|
|
|
|
for (prop in scope) {
|
|
|
|
if (scope.hasOwnProperty(prop) && prop !== 'this' && prop[0] !== '$') {
|
|
|
|
scopeLocals[prop] = scope[prop];
|
2012-07-02 21:09:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-09 00:26:59 +00:00
|
|
|
node.locals = decycle(scopeLocals);
|
|
|
|
|
2012-07-02 21:09:51 +00:00
|
|
|
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 = [],
|
2012-07-23 17:20:06 +00:00
|
|
|
deps;
|
2012-07-02 21:09:51 +00:00
|
|
|
|
|
|
|
// 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));
|
|
|
|
|
2012-07-23 17:20:06 +00:00
|
|
|
deps = __ngDebug.deps;
|
2012-07-02 21:09:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
roots: rootIds,
|
|
|
|
trees: trees,
|
|
|
|
histogram: histogram,
|
2012-07-23 17:20:06 +00:00
|
|
|
deps: deps
|
2012-07-02 21:09:51 +00:00
|
|
|
};
|
|
|
|
},
|
|
|
|
function (data) {
|
|
|
|
_debugCache = data;
|
|
|
|
_pollListeners.forEach(function (fn) {
|
|
|
|
fn();
|
|
|
|
});
|
|
|
|
|
|
|
|
// poll every 500 ms
|
|
|
|
setTimeout(getDebugData, _pollInterval);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
getDebugData();
|
|
|
|
|
2012-07-27 00:13:08 +00:00
|
|
|
// Helpers
|
|
|
|
// =======
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-07-02 21:09:51 +00:00
|
|
|
|
|
|
|
// Public API
|
|
|
|
// ==========
|
|
|
|
return {
|
2012-07-25 21:26:36 +00:00
|
|
|
// Fix selection of scope
|
|
|
|
// https://github.com/angular/angularjs-batarang/issues/6
|
2012-07-02 21:09:51 +00:00
|
|
|
executeOnScope: function(scopeId, fn, args, cb) {
|
|
|
|
if (typeof args === 'function') {
|
|
|
|
cb = args;
|
|
|
|
args = {};
|
|
|
|
} else if (!args) {
|
|
|
|
args = {};
|
|
|
|
}
|
|
|
|
args.scopeId = scopeId;
|
|
|
|
args.fn = fn.toString();
|
|
|
|
|
|
|
|
chromeExtension.eval("function (window, args) {" +
|
|
|
|
"var elts = window.document.getElementsByClassName('ng-scope'), i;" +
|
|
|
|
"for (i = 0; i < elts.length; i++) {" +
|
|
|
|
"(function (elt) {" +
|
|
|
|
"var $scope = window.angular.element(elt).scope();" +
|
|
|
|
"if ($scope.$id === args.scopeId) {" +
|
|
|
|
"(" + args.fn + "($scope, elt, args));" +
|
|
|
|
"}" +
|
|
|
|
"}(elts[i]));" +
|
|
|
|
"}" +
|
|
|
|
"}", args, cb);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Getters
|
|
|
|
// -------
|
|
|
|
|
|
|
|
getHistogram: function () {
|
|
|
|
return _debugCache.histogram;
|
|
|
|
},
|
|
|
|
|
|
|
|
getListOfRoots: function () {
|
|
|
|
return _debugCache.roots;
|
|
|
|
},
|
|
|
|
|
|
|
|
getModelTrees: function () {
|
|
|
|
return _debugCache.trees;
|
|
|
|
},
|
|
|
|
|
2012-07-19 18:37:39 +00:00
|
|
|
getDeps: function () {
|
|
|
|
return _debugCache.deps;
|
|
|
|
},
|
|
|
|
|
2012-07-27 00:13:08 +00:00
|
|
|
|
|
|
|
getAngularVersion: function (cb) {
|
|
|
|
chromeExtension.eval(function () {
|
|
|
|
return window.angular.version.full +
|
|
|
|
' ' +
|
|
|
|
window.angular.version.codeName;
|
|
|
|
}, cb);
|
|
|
|
},
|
|
|
|
|
2012-07-25 21:37:54 +00:00
|
|
|
getAngularSrc: function (cb) {
|
|
|
|
chromeExtension.eval("function (window, args) {" +
|
|
|
|
"if (!window.angular) {" +
|
|
|
|
"return 'info';" +
|
|
|
|
"}" +
|
|
|
|
"var elts = window.angular.element('script[src]');" +
|
|
|
|
"var re = /\/angular(-\d+(\.(\d+))+(rc)?)?(\.min)?\.js$/;" +
|
|
|
|
"var elt;" +
|
|
|
|
"for (i = 0; i < elts.length; i++) {" +
|
|
|
|
"elt = elts[i];" +
|
|
|
|
"if (re.exec(elt.src)) {" +
|
|
|
|
"if (elt.src.indexOf('code.angularjs.org') !== -1) {" +
|
|
|
|
"return 'error';" +
|
|
|
|
"} else if (elt.src.indexOf('ajax.googleapis.com') !== -1) {" +
|
|
|
|
"return 'good';" +
|
|
|
|
"} else {" +
|
|
|
|
"return 'info';" +
|
|
|
|
"}" +
|
|
|
|
"}" +
|
|
|
|
"}" +
|
|
|
|
"return 'info';" +
|
|
|
|
"}", cb);
|
|
|
|
},
|
|
|
|
|
2012-07-02 21:09:51 +00:00
|
|
|
// Actions
|
|
|
|
// -------
|
2012-07-27 00:13:08 +00:00
|
|
|
|
2012-07-02 21:09:51 +00:00
|
|
|
clearHistogram: function (cb) {
|
|
|
|
chromeExtension.eval(function (window) {
|
|
|
|
window.__ngDebug.watchExp = {};
|
|
|
|
}, cb);
|
|
|
|
},
|
|
|
|
|
|
|
|
refresh: function (cb) {
|
|
|
|
chromeExtension.eval(function (window) {
|
|
|
|
window.document.location.reload();
|
|
|
|
}, cb);
|
|
|
|
},
|
|
|
|
|
|
|
|
inspect: function (scopeId) {
|
|
|
|
this.executeOnScope(scopeId, function (scope, elt) {
|
|
|
|
inspect(elt);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
// --------
|
|
|
|
|
|
|
|
// takes a bool
|
|
|
|
setDebug: function (setting) {
|
|
|
|
if (setting) {
|
|
|
|
chromeExtension.eval(function (window) {
|
2012-08-16 00:13:34 +00:00
|
|
|
window.document.cookie = '__ngDebug=true;';
|
2012-07-02 21:09:51 +00:00
|
|
|
window.document.location.reload();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
chromeExtension.eval(function (window) {
|
2012-08-16 00:13:34 +00:00
|
|
|
window.document.cookie = '__ngDebug=false;';
|
2012-07-02 21:09:51 +00:00
|
|
|
window.document.location.reload();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-07-10 21:08:32 +00:00
|
|
|
getDebug: function (cb) {
|
|
|
|
chromeExtension.eval(function (window) {
|
|
|
|
return document.cookie.indexOf('__ngDebug=true') != -1;
|
|
|
|
}, cb);
|
|
|
|
},
|
|
|
|
|
2012-07-02 21:09:51 +00:00
|
|
|
// takes a bool
|
|
|
|
setLog: function (setting) {
|
2012-07-19 18:37:39 +00:00
|
|
|
setting = !!setting;
|
2012-07-02 21:09:51 +00:00
|
|
|
chromeExtension.eval('function (window) {' +
|
|
|
|
'window.__ngDebug.log = ' + setting.toString() + ';' +
|
|
|
|
'}');
|
|
|
|
},
|
|
|
|
|
|
|
|
// takes # of miliseconds
|
|
|
|
setPollInterval: function (setting) {
|
|
|
|
_pollInterval = setting;
|
|
|
|
},
|
|
|
|
|
|
|
|
// Registering events
|
|
|
|
// ------------------
|
|
|
|
|
|
|
|
// TODO: depreciate this; only poll from now on?
|
2012-07-25 21:26:36 +00:00
|
|
|
// There are some cases where you need to gather data on a once-per-bootstrap basis, for
|
|
|
|
// instance getting the version of AngularJS
|
|
|
|
|
2012-07-02 21:09:51 +00:00
|
|
|
// TODO: move to chromeExtension?
|
|
|
|
watchRefresh: function (cb) {
|
|
|
|
var port = chrome.extension.connect();
|
|
|
|
port.postMessage({
|
|
|
|
action: 'register',
|
|
|
|
inspectedTabId: chrome.devtools.inspectedWindow.tabId
|
|
|
|
});
|
|
|
|
port.onMessage.addListener(function(msg) {
|
|
|
|
if (msg === 'refresh') {
|
|
|
|
cb();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
port.onDisconnect.addListener(function (a) {
|
|
|
|
console.log(a);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
watchPoll: function (fn) {
|
|
|
|
_pollListeners.push(fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
});
|