reorganize plugin
@ -1 +1,2 @@
|
|||||||
batarang-release-*.zip
|
batarang-release-*.zip
|
||||||
|
*.build.js
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<script src="js/background.js"></script>
|
<script src="background.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -0,0 +1,20 @@
|
|||||||
|
# content-scripts/inject.js
|
||||||
|
|
||||||
|
What does this do?
|
||||||
|
|
||||||
|
This adds a script to the page that watches DOM mutation events until it sees that `window.angular` is available.
|
||||||
|
Immediately after, before any applications have the opportunity to bootstrap, this decorates core Angular
|
||||||
|
components to expose debugging information.
|
||||||
|
|
||||||
|
## Building debug.js
|
||||||
|
|
||||||
|
Why does this need a build step?
|
||||||
|
|
||||||
|
Because this script does `fn.toString()` to construct the script tags, it's impossible to use any sort of
|
||||||
|
code loading. The code needs to be inlined before being run.
|
||||||
|
|
||||||
|
From the root of this repository, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
node ./scripts/inline.js
|
||||||
|
```
|
@ -0,0 +1,356 @@
|
|||||||
|
// content-scripts/inject.js
|
||||||
|
// this file is run from the content script context (separate JS VM from the app, but same DOM)
|
||||||
|
// but injects an 'instrumentation' script tag into the app context
|
||||||
|
// confusing, right?
|
||||||
|
|
||||||
|
var instument = function instument (window) {
|
||||||
|
// 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.
|
||||||
|
var ngLoaded = function () {
|
||||||
|
if (!window.angular) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
window.angular.module('ng');
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ngLoaded()) {
|
||||||
|
// TODO: var name
|
||||||
|
var areWeThereYet = function (ev) {
|
||||||
|
if (ev.srcElement.tagName === 'SCRIPT') {
|
||||||
|
var oldOnload = ev.srcElement.onload;
|
||||||
|
ev.srcElement.onload = function () {
|
||||||
|
if (ngLoaded()) {
|
||||||
|
document.removeEventListener('DOMNodeInserted', areWeThereYet);
|
||||||
|
instument(window);
|
||||||
|
}
|
||||||
|
if (oldOnload) {
|
||||||
|
oldOnload.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('DOMNodeInserted', areWeThereYet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not patch twice
|
||||||
|
if (window.__ngDebug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
// =======
|
||||||
|
|
||||||
|
var throttle = require('./lib/throttle.js');
|
||||||
|
var summarizeObject = require('./lib/summarizeObject.js');
|
||||||
|
|
||||||
|
// helper to extract dependencies from function arguments
|
||||||
|
// not all versions of AngularJS expose annotate
|
||||||
|
var annotate = angular.injector().annotate || require('./lib/annotate.js');
|
||||||
|
|
||||||
|
// polyfill for performance.now on older webkit
|
||||||
|
if (!performance.now) {
|
||||||
|
performance.now = performance.webkitNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notifications from app context to devtools context
|
||||||
|
// in order to do this, we need to create a DOM element across which
|
||||||
|
// the app and content script contexts can communicate
|
||||||
|
var eventProxyElement = document.createElement('div');
|
||||||
|
eventProxyElement.id = '__ngDebugElement';
|
||||||
|
eventProxyElement.style.display = 'none';
|
||||||
|
document.body.appendChild(eventProxyElement);
|
||||||
|
|
||||||
|
var customEvent = document.createEvent('Event');
|
||||||
|
customEvent.initEvent('myCustomEvent', true, true);
|
||||||
|
|
||||||
|
var fireCustomEvent = function (data) {
|
||||||
|
data.appId = instrumentedAppId;
|
||||||
|
eventProxyElement.innerText = JSON.stringify(data);
|
||||||
|
eventProxyElement.dispatchEvent(customEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// given a scope object, return an object with deep clones
|
||||||
|
// of the models exposed on that scope
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Private state
|
||||||
|
// =============
|
||||||
|
|
||||||
|
//var bootstrap = window.angular.bootstrap;
|
||||||
|
var debug = {
|
||||||
|
// map of scopes --> watcher function name strings
|
||||||
|
watchers: {},
|
||||||
|
|
||||||
|
// maps of watch/apply exp/fns to perf data
|
||||||
|
watchPerf: {},
|
||||||
|
applyPerf: {},
|
||||||
|
|
||||||
|
// map of scope.$ids --> $scope objects
|
||||||
|
scopes: {},
|
||||||
|
|
||||||
|
// whether or not to emit profile data
|
||||||
|
profiling: false,
|
||||||
|
|
||||||
|
// map of $ids --> [] array of things being watched
|
||||||
|
modelWatchers: {},
|
||||||
|
// map of $id + watcher --> value
|
||||||
|
modelWatchersState: {},
|
||||||
|
|
||||||
|
// map of $ids --> refs to $rootScope objects
|
||||||
|
rootScopes: {},
|
||||||
|
|
||||||
|
deps: []
|
||||||
|
};
|
||||||
|
|
||||||
|
var popover = null;
|
||||||
|
var instrumentedAppId = window.location.host + '~' + Math.random();
|
||||||
|
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
// =====
|
||||||
|
|
||||||
|
var getScopeTree = function (id) {
|
||||||
|
|
||||||
|
var names = api.niceNames();
|
||||||
|
|
||||||
|
var traverse = function (sc) {
|
||||||
|
var tree = {
|
||||||
|
id: sc.$id,
|
||||||
|
name: names[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;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getWatchPerf = function () {
|
||||||
|
var changes = [];
|
||||||
|
angular.forEach(debug.watchPerf, function (info, name) {
|
||||||
|
if (info.time > 0) {
|
||||||
|
changes.push({
|
||||||
|
name: name,
|
||||||
|
time: info.time
|
||||||
|
});
|
||||||
|
info.time = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return changes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit stuff
|
||||||
|
// ==========
|
||||||
|
|
||||||
|
var emit = {
|
||||||
|
modelChange: throttle(function (id, watchers) {
|
||||||
|
var scope = debug.scopes[id];
|
||||||
|
var changes = {};
|
||||||
|
|
||||||
|
watchers = watchers || debug.modelWatchers[id];
|
||||||
|
|
||||||
|
if (scope && debug.modelWatchers[id]) {
|
||||||
|
|
||||||
|
Object.keys(debug.modelWatchers[id]).
|
||||||
|
forEach(function (watcher) {
|
||||||
|
var newValue = api.getModel(id, watcher),
|
||||||
|
newString = JSON.stringify(newValue),
|
||||||
|
prop = id + '~' + watcher;
|
||||||
|
|
||||||
|
if (debug.modelWatchersState[prop] !== newString) {
|
||||||
|
changes[watcher] = newValue;
|
||||||
|
debug.modelWatchersState[prop] = newString;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(changes).length > 0) {
|
||||||
|
fireCustomEvent({
|
||||||
|
action: 'modelChange',
|
||||||
|
id: id,
|
||||||
|
changes: changes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50),
|
||||||
|
|
||||||
|
scopeChange: throttle(function (id) {
|
||||||
|
fireCustomEvent({
|
||||||
|
action: 'scopeChange',
|
||||||
|
id: id,
|
||||||
|
scope: getScopeTree(id)
|
||||||
|
});
|
||||||
|
}, 50),
|
||||||
|
|
||||||
|
scopeDeleted: function (id) {
|
||||||
|
fireCustomEvent({
|
||||||
|
action: 'scopeDeleted',
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
watcherChange: throttle(function (id) {
|
||||||
|
if (debug.modelWatchers[id]) {
|
||||||
|
fireCustomEvent({
|
||||||
|
action: 'watcherChange',
|
||||||
|
id: id,
|
||||||
|
watchers: debug.watchers[id]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50),
|
||||||
|
|
||||||
|
watchPerfChange: throttle(function (str) {
|
||||||
|
if (debug.profiling) {
|
||||||
|
fireCustomEvent({
|
||||||
|
action: 'watchPerfChange',
|
||||||
|
watcher: str,
|
||||||
|
value: debug.watchPerf[str]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50),
|
||||||
|
|
||||||
|
applyPerfChange: throttle(function (str) {
|
||||||
|
if (debug.profiling) {
|
||||||
|
fireCustomEvent({
|
||||||
|
action: 'applyPerfChange',
|
||||||
|
watcher: str,
|
||||||
|
value: debug.applyPerf[str]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50),
|
||||||
|
|
||||||
|
// might be worth limiting
|
||||||
|
watchPerf: function () {
|
||||||
|
throw new Error('Implement me :c');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
// ==========
|
||||||
|
|
||||||
|
var api = window.__ngDebug = {
|
||||||
|
|
||||||
|
profiling: function (setting) {
|
||||||
|
debug.profiling = setting;
|
||||||
|
},
|
||||||
|
|
||||||
|
getDeps: function () {
|
||||||
|
return debug.deps;
|
||||||
|
},
|
||||||
|
|
||||||
|
getRootScopeIds: function () {
|
||||||
|
return Object.keys(debug.rootScopes);
|
||||||
|
},
|
||||||
|
|
||||||
|
getAppId: function () {
|
||||||
|
return instrumentedAppId;
|
||||||
|
},
|
||||||
|
|
||||||
|
fireCustomEvent: fireCustomEvent,
|
||||||
|
|
||||||
|
niceNames: require('./lib/niceNames.js'),
|
||||||
|
getModel: require('./lib/summarizeModel.js'),
|
||||||
|
|
||||||
|
setSomeModel: function (id, path, value) {
|
||||||
|
debug.scope[id].$apply(path + '=' + JSON.stringify(value));
|
||||||
|
},
|
||||||
|
|
||||||
|
watchModel: function (id, path) {
|
||||||
|
debug.modelWatchers[id] = debug.modelWatchers[id] || {};
|
||||||
|
debug.modelWatchers[id][path || ''] = true;
|
||||||
|
if (!path || path === '') {
|
||||||
|
debug.modelWatchersState = {};
|
||||||
|
}
|
||||||
|
emit.modelChange(id);
|
||||||
|
emit.watcherChange(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
// unwatches all children of the given path
|
||||||
|
// Ex:
|
||||||
|
// if watching 'foo.bar.baz', 'foo.bar', and 'foo'
|
||||||
|
// unwatchModel('001', 'foo.bar')
|
||||||
|
// unwatches 'foo.bar.baz' and 'foo.bar'
|
||||||
|
unwatchModel: function (id, path) {
|
||||||
|
if (!debug.modelWatchers[id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (path === undefined) {
|
||||||
|
path = '';
|
||||||
|
}
|
||||||
|
Object.keys(modelWatchers[id]).forEach(function (key) {
|
||||||
|
if (key.substr(0, path.length) === path) {
|
||||||
|
delete debug.modelWatchers[id][key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: require('./lib/popover.js')
|
||||||
|
};
|
||||||
|
|
||||||
|
var recordDependencies = function (providerName, dependencies) {
|
||||||
|
debug.deps.push({
|
||||||
|
name: providerName,
|
||||||
|
imports: dependencies
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
require('./lib/decorate.js');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// inject into the application context from the content script context
|
||||||
|
|
||||||
|
var inject = function () {
|
||||||
|
var script = window.document.createElement('script');
|
||||||
|
script.innerHTML = '(' + instument.toString() + '(window))';
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
// handle forwarding the events sent from the app context to the
|
||||||
|
// background page context
|
||||||
|
var eventProxyElement = document.getElementById('__ngDebugElement');
|
||||||
|
|
||||||
|
if (eventProxyElement) {
|
||||||
|
eventProxyElement.addEventListener('myCustomEvent', function () {
|
||||||
|
var eventData = JSON.parse(eventProxyElement.innerText);
|
||||||
|
chrome.extension.sendMessage(eventData);
|
||||||
|
});
|
||||||
|
document.removeEventListener('DOMContentLoaded', inject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// only inject if cookie is set
|
||||||
|
if (document.cookie.indexOf('__ngDebug=true') != -1) {
|
||||||
|
document.addEventListener('DOMContentLoaded', inject);
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
|
||||||
|
var FN_ARG_SPLIT = /,/;
|
||||||
|
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
|
||||||
|
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||||
|
|
||||||
|
// TODO: should I keep these assertions?
|
||||||
|
function assertArg(arg, name, reason) {
|
||||||
|
if (!arg) {
|
||||||
|
throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertArgFn(arg, name, acceptArrayAnnotation) {
|
||||||
|
if (acceptArrayAnnotation && angular.isArray(arg)) {
|
||||||
|
arg = arg[arg.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArg(angular.isFunction(arg), name, 'not a function, got ' +
|
||||||
|
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function (fn) {
|
||||||
|
var $inject,
|
||||||
|
fnText,
|
||||||
|
argDecl,
|
||||||
|
last;
|
||||||
|
|
||||||
|
if (typeof fn == 'function') {
|
||||||
|
if (!($inject = fn.$inject)) {
|
||||||
|
$inject = [];
|
||||||
|
fnText = fn.toString().replace(STRIP_COMMENTS, '');
|
||||||
|
argDecl = fnText.match(FN_ARGS);
|
||||||
|
argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
|
||||||
|
arg.replace(FN_ARG, function(all, underscore, name) {
|
||||||
|
$inject.push(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fn.$inject = $inject;
|
||||||
|
}
|
||||||
|
} else if (angular.isArray(fn)) {
|
||||||
|
last = fn.length - 1;
|
||||||
|
assertArgFn(fn[last], 'fn');
|
||||||
|
$inject = fn.slice(0, last);
|
||||||
|
} else {
|
||||||
|
assertArgFn(fn, 'fn', true);
|
||||||
|
}
|
||||||
|
return $inject;
|
||||||
|
};
|
@ -0,0 +1,251 @@
|
|||||||
|
|
||||||
|
var ng = angular.module('ng');
|
||||||
|
ng.config(function ($provide) {
|
||||||
|
// methods to patch
|
||||||
|
|
||||||
|
// $provide.provider
|
||||||
|
var temp = $provide.provider;
|
||||||
|
$provide.provider = function (name, definition) {
|
||||||
|
if (!definition) {
|
||||||
|
angular.forEach(name, function (definition, name) {
|
||||||
|
var tempGet = definition.$get;
|
||||||
|
definition.$get = function () {
|
||||||
|
recordDependencies(name, annotate(tempGet));
|
||||||
|
return tempGet.apply(this, arguments);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else if (definition instanceof Array) {
|
||||||
|
// it is a constructor with array syntax
|
||||||
|
var tempConstructor = definition[definition.length - 1];
|
||||||
|
|
||||||
|
definition[definition.length - 1] = function () {
|
||||||
|
recordDependencies(name, annotate(tempConstructor));
|
||||||
|
return tempConstructor.apply(this, arguments);
|
||||||
|
};
|
||||||
|
} else if (definition.$get instanceof Array) {
|
||||||
|
// it should have a $get
|
||||||
|
var tempGet = definition.$get[definition.$get.length - 1];
|
||||||
|
|
||||||
|
definition.$get[definition.$get.length - 1] = function () {
|
||||||
|
recordDependencies(name, annotate(tempGet));
|
||||||
|
return tempGet.apply(this, arguments);
|
||||||
|
};
|
||||||
|
} else if (typeof definition === 'object') {
|
||||||
|
// it should have a $get
|
||||||
|
var tempGet = definition.$get;
|
||||||
|
|
||||||
|
// preserve original annotations
|
||||||
|
definition.$get = annotate(definition.$get);
|
||||||
|
definition.$get.push(function () {
|
||||||
|
recordDependencies(name, annotate(tempGet));
|
||||||
|
return tempGet.apply(this, arguments);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
recordDependencies(name, annotate(definition));
|
||||||
|
}
|
||||||
|
return temp.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// $provide.(factory|service)
|
||||||
|
[
|
||||||
|
'factory',
|
||||||
|
'service'
|
||||||
|
].forEach(function (met) {
|
||||||
|
var temp = $provide[met];
|
||||||
|
$provide[met] = function (name, definition) {
|
||||||
|
if (typeof name === 'object') {
|
||||||
|
angular.forEach(name, function (value, key) {
|
||||||
|
var isArray = value instanceof Array;
|
||||||
|
var originalValue = isArray ? value[value.length - 1] : value;
|
||||||
|
|
||||||
|
var newValue = function () {
|
||||||
|
recordDependencies(key, annotate(originalValue));
|
||||||
|
return originalValue.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isArray) {
|
||||||
|
value[value.length - 1] = newValue;
|
||||||
|
} else {
|
||||||
|
name[value] = newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
recordDependencies(name, annotate(definition));
|
||||||
|
}
|
||||||
|
return temp.apply(this, arguments);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$provide.decorator('$rootScope', function ($delegate) {
|
||||||
|
|
||||||
|
var watchFnToHumanReadableString = function (fn) {
|
||||||
|
if (fn.exp) {
|
||||||
|
return fn.exp.trim();
|
||||||
|
}
|
||||||
|
if (fn.name) {
|
||||||
|
return fn.name.trim();
|
||||||
|
}
|
||||||
|
return fn.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// patch registering watchers
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
var _watch = $delegate.__proto__.$watch;
|
||||||
|
$delegate.__proto__.$watch = function (watchExpression, applyFunction) {
|
||||||
|
var thatScope = this;
|
||||||
|
var watchStr = watchFnToHumanReadableString(watchExpression);
|
||||||
|
|
||||||
|
if (!debug.watchPerf[watchStr]) {
|
||||||
|
debug.watchPerf[watchStr] = {
|
||||||
|
time: 0,
|
||||||
|
calls: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!debug.watchers[thatScope.$id]) {
|
||||||
|
debug.watchers[thatScope.$id] = [];
|
||||||
|
}
|
||||||
|
debug.watchers[thatScope.$id].push(watchStr);
|
||||||
|
emit.watcherChange(thatScope.$id);
|
||||||
|
|
||||||
|
// patch watchExpression
|
||||||
|
// ---------------------
|
||||||
|
var w = watchExpression;
|
||||||
|
if (typeof w === 'function') {
|
||||||
|
watchExpression = function () {
|
||||||
|
var start = performance.now();
|
||||||
|
var ret = w.apply(this, arguments);
|
||||||
|
var end = performance.now();
|
||||||
|
debug.watchPerf[watchStr].time += (end - start);
|
||||||
|
debug.watchPerf[watchStr].calls += 1;
|
||||||
|
emit.watchPerfChange(watchStr);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
watchExpression = function () {
|
||||||
|
var start = performance.now();
|
||||||
|
var ret = thatScope.$eval(w);
|
||||||
|
var end = performance.now();
|
||||||
|
debug.watchPerf[watchStr].time += (end - start);
|
||||||
|
debug.watchPerf[watchStr].calls += 1;
|
||||||
|
emit.watchPerfChange(watchStr);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch applyFunction
|
||||||
|
// -------------------
|
||||||
|
if (typeof applyFunction === 'function') {
|
||||||
|
var applyStr = applyFunction.toString();
|
||||||
|
var unpatchedApplyFunction = applyFunction;
|
||||||
|
applyFunction = function () {
|
||||||
|
var start = performance.now();
|
||||||
|
var ret = unpatchedApplyFunction.apply(this, arguments);
|
||||||
|
var end = performance.now();
|
||||||
|
|
||||||
|
//TODO: move these checks out of here and into registering the watcher
|
||||||
|
if (!debug.applyPerf[applyStr]) {
|
||||||
|
debug.applyPerf[applyStr] = {
|
||||||
|
time: 0,
|
||||||
|
calls: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
debug.applyPerf[applyStr].time += (end - start);
|
||||||
|
debug.applyPerf[applyStr].calls += 1;
|
||||||
|
emit.applyPerfChange(applyStr);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return _watch.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// patch $destroy
|
||||||
|
// --------------
|
||||||
|
var _destroy = $delegate.__proto__.$destroy;
|
||||||
|
$delegate.__proto__.$destroy = function () {
|
||||||
|
[
|
||||||
|
'watchers',
|
||||||
|
'scopes'
|
||||||
|
].forEach(function (prop) {
|
||||||
|
if (debug[prop][this.$id]) {
|
||||||
|
delete debug[prop][this.$id];
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
emit.scopeDeleted(this.$id);
|
||||||
|
return _destroy.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// patch $new
|
||||||
|
// ----------
|
||||||
|
var _new = $delegate.__proto__.$new;
|
||||||
|
$delegate.__proto__.$new = function () {
|
||||||
|
|
||||||
|
var ret = _new.apply(this, arguments);
|
||||||
|
if (ret.$root) {
|
||||||
|
debug.rootScopes[ret.$root.$id] = ret.$root;
|
||||||
|
emit.scopeChange(ret.$root.$id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create empty watchers array for this scope
|
||||||
|
if (!debug.watchers[ret.$id]) {
|
||||||
|
debug.watchers[ret.$id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.scopes[ret.$id] = ret;
|
||||||
|
debug.scopes[this.$id] = this;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// patch $digest
|
||||||
|
// -------------
|
||||||
|
var _digest = $delegate.__proto__.$digest;
|
||||||
|
$delegate.__proto__.$digest = function (fn) {
|
||||||
|
var ret = _digest.apply(this, arguments);
|
||||||
|
emit.modelChange(this.$id);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// patch $apply
|
||||||
|
// ------------
|
||||||
|
var _apply = $delegate.__proto__.$apply;
|
||||||
|
$delegate.__proto__.$apply = function (fn) {
|
||||||
|
var start = performance.now();
|
||||||
|
var ret = _apply.apply(this, arguments);
|
||||||
|
var end = performance.now();
|
||||||
|
|
||||||
|
// If the debugging option is enabled, log to console
|
||||||
|
// --------------------------------------------------
|
||||||
|
if (debug.log) {
|
||||||
|
console.log(applyFnToLogString(fn) + '\t\t' + (end - start).toPrecision(4) + 'ms');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return $delegate;
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
module.exports = function niceNames () {
|
||||||
|
var ngScopeElts = document.getElementsByClassName('ng-scope');
|
||||||
|
ngScopeElts = Array.prototype.slice.call(ngScopeElts);
|
||||||
|
return ngScopeElts.
|
||||||
|
reduce(function (acc, elt) {
|
||||||
|
var $elt = angular.element(elt);
|
||||||
|
var scope = $elt.scope();
|
||||||
|
|
||||||
|
var name = {};
|
||||||
|
|
||||||
|
[
|
||||||
|
'ng-app',
|
||||||
|
'ng-controller',
|
||||||
|
'ng-repeat'
|
||||||
|
].
|
||||||
|
forEach(function (attr) {
|
||||||
|
var val = $elt.attr(attr),
|
||||||
|
className = $elt[0].className,
|
||||||
|
match,
|
||||||
|
lhs,
|
||||||
|
valueIdentifier,
|
||||||
|
keyIdentifier;
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
name[attr] = val;
|
||||||
|
if (attr === 'ng-repeat') {
|
||||||
|
match = /(.+) in/.exec(val);
|
||||||
|
lhs = match[1];
|
||||||
|
|
||||||
|
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
|
||||||
|
valueIdentifier = match[3] || match[1];
|
||||||
|
keyIdentifier = match[2];
|
||||||
|
|
||||||
|
if (keyIdentifier) {
|
||||||
|
name.lhs = valueIdentifier + '["' + scope[keyIdentifier] + '"]' + summarizeObject(scope[valueIdentifier]);
|
||||||
|
} else {
|
||||||
|
name.lhs = valueIdentifier + summarizeObject(scope[valueIdentifier]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (className.indexOf(attr) !== -1) {
|
||||||
|
match = (new RegExp(attr + ': ([a-zA-Z0-9]+);')).exec(className);
|
||||||
|
name[attr] = match[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(name).length === 0) {
|
||||||
|
name.tag = $elt[0].tagName.toLowerCase();
|
||||||
|
name.classes = $elt[0].className.
|
||||||
|
replace(/(\W*ng-scope\W*)/, ' ').
|
||||||
|
split(' ').
|
||||||
|
filter(function (i) { return i; });
|
||||||
|
}
|
||||||
|
acc[scope.$id] = name;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
@ -0,0 +1,234 @@
|
|||||||
|
// TODO: this depends on global state and stuff
|
||||||
|
|
||||||
|
module.exports = function () {
|
||||||
|
if (popover) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var angular = window.angular;
|
||||||
|
popover = angular.element(
|
||||||
|
'<div style="position: fixed; left: 50px; top: 50px; z-index: 9999; background-color: #f1f1f1; box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;">' +
|
||||||
|
'<div style="position: relative" style="min-width: 300px; min-height: 100px;">' +
|
||||||
|
'<div style="min-width: 100px; min-height: 50px; padding: 5px;"><pre>{ Please select a scope }</pre></div>' +
|
||||||
|
'<button style="position: absolute; top: -15px; left: -15px; cursor: move;">⇱</button>' +
|
||||||
|
'<button style="position: absolute; top: -15px; left: 10px;">+</button>' +
|
||||||
|
'<button style="position: absolute; top: -15px; right: -15px;">x</button>' +
|
||||||
|
'<style>' +
|
||||||
|
'.ng-scope.bat-selected { border: 1px solid red; } ' +
|
||||||
|
'.bat-indent { margin-left: 20px; }' +
|
||||||
|
'</style>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>');
|
||||||
|
angular.element(window.document.body).append(popover);
|
||||||
|
var popoverContent = angular.element(angular.element(popover.children('div')[0]).children()[0]);
|
||||||
|
var dragElt = angular.element(angular.element(popover.children('div')[0]).children()[1]);
|
||||||
|
var selectElt = angular.element(angular.element(popover.children('div')[0]).children()[2]);
|
||||||
|
var closeElt = angular.element(angular.element(popover.children('div')[0]).children()[3]);
|
||||||
|
|
||||||
|
var currentScope = null,
|
||||||
|
currentElt = null;
|
||||||
|
|
||||||
|
function onMove (ev) {
|
||||||
|
var x = ev.clientX,
|
||||||
|
y = ev.clientY;
|
||||||
|
|
||||||
|
if (x > window.outerWidth - 100) {
|
||||||
|
x = window.outerWidth - 100;
|
||||||
|
} else if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
if (y > window.outerHeight - 100) {
|
||||||
|
y = window.outerHeight - 100;
|
||||||
|
} else if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x += 5;
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
popover.css('left', x + 'px');
|
||||||
|
popover.css('top', y + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
closeElt.bind('click', function () {
|
||||||
|
popover.remove();
|
||||||
|
popover = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
selectElt.bind('click', bindSelectScope);
|
||||||
|
|
||||||
|
var selecting = false;
|
||||||
|
function bindSelectScope () {
|
||||||
|
if (selecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(function () {
|
||||||
|
selecting = true;
|
||||||
|
selectElt.attr('disabled', true);
|
||||||
|
angular.element(document.body).css('cursor', 'crosshair');
|
||||||
|
angular.element(document.getElementsByClassName('ng-scope'))
|
||||||
|
.bind('click', onSelectScope)
|
||||||
|
.bind('mouseover', onHoverScope);
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hoverScopeElt = null;
|
||||||
|
|
||||||
|
function markHoverElt () {
|
||||||
|
if (hoverScopeElt) {
|
||||||
|
hoverScopeElt.addClass('bat-selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function unmarkHoverElt () {
|
||||||
|
if (hoverScopeElt) {
|
||||||
|
hoverScopeElt.removeClass('bat-selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelectScope (ev) {
|
||||||
|
render(this);
|
||||||
|
angular.element(document.getElementsByClassName('ng-scope'))
|
||||||
|
.unbind('click', onSelectScope)
|
||||||
|
.unbind('mouseover', onHoverScope);
|
||||||
|
unmarkHoverElt();
|
||||||
|
selecting = false;
|
||||||
|
selectElt.attr('disabled', false);
|
||||||
|
angular.element(document.body).css('cursor', '');
|
||||||
|
hovering = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hovering = false;
|
||||||
|
function onHoverScope (ev) {
|
||||||
|
if (hovering) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hovering = true;
|
||||||
|
var that = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
unmarkHoverElt();
|
||||||
|
hoverScopeElt = angular.element(that);
|
||||||
|
markHoverElt();
|
||||||
|
hovering = false;
|
||||||
|
render(that);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUnhoverScope (ev) {
|
||||||
|
angular.element(this).css('border', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
dragElt.bind('mousedown', function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
rendering = true;
|
||||||
|
angular.element(document).bind('mousemove', onMove);
|
||||||
|
});
|
||||||
|
angular.element(document).bind('mouseup', function () {
|
||||||
|
angular.element(document).unbind('mousemove', onMove);
|
||||||
|
setTimeout(function () {
|
||||||
|
rendering = false;
|
||||||
|
}, 120);
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderTree (data) {
|
||||||
|
var tree = angular.element('<div class="bat-indent"></div>');
|
||||||
|
angular.forEach(data, function (val, key) {
|
||||||
|
var toAppend;
|
||||||
|
if (val === undefined) {
|
||||||
|
toAppend = '<i>undefined</i>';
|
||||||
|
} else if (val === null) {
|
||||||
|
toAppend = '<i>null</i>';
|
||||||
|
} else if (val instanceof Array) {
|
||||||
|
toAppend = '[ ... ]';
|
||||||
|
} else if (val instanceof Object) {
|
||||||
|
toAppend = '{ ... }';
|
||||||
|
} else {
|
||||||
|
toAppend = val.toString();
|
||||||
|
}
|
||||||
|
if (data instanceof Array) {
|
||||||
|
toAppend = '<div>' +
|
||||||
|
toAppend +
|
||||||
|
((key === (data.length - 1))?'':',') +
|
||||||
|
'</div>';
|
||||||
|
} else {
|
||||||
|
toAppend = '<div>' +
|
||||||
|
key +
|
||||||
|
': ' +
|
||||||
|
toAppend +
|
||||||
|
(key!==0?'':',') +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
toAppend = angular.element(toAppend);
|
||||||
|
if (val instanceof Array || val instanceof Object) {
|
||||||
|
function recur () {
|
||||||
|
toAppend.unbind('click', recur);
|
||||||
|
toAppend.html('');
|
||||||
|
toAppend
|
||||||
|
.append(angular.element('<span>' +
|
||||||
|
key + ': ' +
|
||||||
|
((val instanceof Array)?'[':'{') +
|
||||||
|
'<span>').bind('click', collapse))
|
||||||
|
.append(renderTree(val))
|
||||||
|
.append('<span>' + ((val instanceof Array)?']':'}') + '<span>');
|
||||||
|
}
|
||||||
|
function collapse () {
|
||||||
|
toAppend.html('');
|
||||||
|
toAppend.append(angular.element('<div>' +
|
||||||
|
key +
|
||||||
|
': ' +
|
||||||
|
((val instanceof Array)?'[ ... ]':'{ ... }') +
|
||||||
|
'</div>').bind('click', recur));
|
||||||
|
}
|
||||||
|
toAppend.bind('click', recur);
|
||||||
|
}
|
||||||
|
tree.append(toAppend);
|
||||||
|
});
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEmpty = function (object) {
|
||||||
|
var prop;
|
||||||
|
for (prop in object) {
|
||||||
|
if (object.hasOwnProperty(prop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var objLength = function (object) {
|
||||||
|
var prop, len = 0;
|
||||||
|
for (prop in object) {
|
||||||
|
if (object.hasOwnProperty(prop)) {
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
};
|
||||||
|
|
||||||
|
var rendering = false;
|
||||||
|
var render = function (elt) {
|
||||||
|
if (rendering) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rendering = true;
|
||||||
|
setTimeout(function () {
|
||||||
|
var scope = angular.element(elt).scope();
|
||||||
|
rendering = false;
|
||||||
|
if (scope === currentScope) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentScope = scope;
|
||||||
|
currentElt = elt;
|
||||||
|
|
||||||
|
var models = getScopeLocals(scope);
|
||||||
|
popoverContent.children().remove();
|
||||||
|
if (isEmpty(models)) {
|
||||||
|
popoverContent.append(angular.element('<i>This scope has no models</i>'));
|
||||||
|
} else {
|
||||||
|
popoverContent.append(renderTree(models));
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
// TODO: handle DOM nodes, fns, etc better.
|
||||||
|
var subModel = function (obj) {
|
||||||
|
return obj instanceof Array ?
|
||||||
|
{ '~array-length': obj.length } :
|
||||||
|
obj === null ?
|
||||||
|
null :
|
||||||
|
typeof obj === 'object' ?
|
||||||
|
{ '~object': true } :
|
||||||
|
obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function (id, path) {
|
||||||
|
|
||||||
|
if (path === undefined || path === '') {
|
||||||
|
path = [];
|
||||||
|
} else if (typeof path === 'string') {
|
||||||
|
path = path.split('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var dest = debug.scopes[id],
|
||||||
|
segment;
|
||||||
|
|
||||||
|
if (!dest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (path.length > 0) {
|
||||||
|
segment = path.shift();
|
||||||
|
dest = dest[segment];
|
||||||
|
if (!dest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dest instanceof Array) {
|
||||||
|
return dest.map(subModel);
|
||||||
|
} else if (typeof dest === 'object') {
|
||||||
|
return Object.
|
||||||
|
keys(dest).
|
||||||
|
filter(function (key) {
|
||||||
|
return key[0] !== '$' || key[1] !== '$';
|
||||||
|
}).
|
||||||
|
reduce(function (obj, prop) {
|
||||||
|
obj[prop] = subModel(dest[prop]);
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
} else {
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
module.exports = function summarizeObject (obj) {
|
||||||
|
var summary = {}, keys;
|
||||||
|
if (obj instanceof Array) {
|
||||||
|
keys = obj.map(function (e, i) { return i; });
|
||||||
|
} else if (typeof obj === 'object') {
|
||||||
|
keys = Object.keys(obj);
|
||||||
|
} else {
|
||||||
|
return '=' + obj.toString().substr(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
var id;
|
||||||
|
|
||||||
|
if (keys.some(function (key) {
|
||||||
|
var lowKey = key.toLowerCase();
|
||||||
|
if (lowKey.indexOf('name') !== -1 ||
|
||||||
|
lowKey.indexOf('id') !== -1) {
|
||||||
|
return id = key;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
return '.' + id + '="' + obj[id].toString() + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.length > 5) {
|
||||||
|
keys = keys.slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.forEach(function (key) {
|
||||||
|
var val = obj[key];
|
||||||
|
if (val instanceof Array) {
|
||||||
|
summary[key] = '[ … ]';
|
||||||
|
} else if (typeof val === 'object') {
|
||||||
|
summary[key] = '{ … }';
|
||||||
|
} else if (typeof val === 'function') {
|
||||||
|
summary[key] = 'fn';
|
||||||
|
} else {
|
||||||
|
summary[key] = obj[key].toString();
|
||||||
|
if (summary[key].length > 10) {
|
||||||
|
summary[key] = summary[key].substr(0, 10) + '…';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return '=' + JSON.stringify(summary);
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
// throttle based on _.throttle from Lo-Dash
|
||||||
|
// https://github.com/bestiejs/lodash/blob/master/lodash.js#L4625
|
||||||
|
|
||||||
|
// modified so that it
|
||||||
|
// throttles based on arguments
|
||||||
|
// returns nothing
|
||||||
|
|
||||||
|
// Ex:
|
||||||
|
// var th = throttle(fn, 50);
|
||||||
|
// fn('foo'); // not throttled
|
||||||
|
// fn('foo'); // throttled
|
||||||
|
// fn('bar'); // not throttled
|
||||||
|
module.exports = function (func, wait) {
|
||||||
|
var args,
|
||||||
|
thisArg,
|
||||||
|
timeoutId = {},
|
||||||
|
lastCalled = {};
|
||||||
|
|
||||||
|
if (wait === 0) {
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
args = arguments;
|
||||||
|
thisArg = this;
|
||||||
|
|
||||||
|
var argsString = Array.prototype.slice.call(args).join(';');
|
||||||
|
|
||||||
|
var now = new Date();
|
||||||
|
var remaining = wait - (now - lastCalled[argsString]);
|
||||||
|
|
||||||
|
if (remaining <= 0) {
|
||||||
|
clearTimeout(timeoutId[argsString]);
|
||||||
|
timeoutId[argsString] = null;
|
||||||
|
lastCalled[argsString] = now;
|
||||||
|
func.apply(thisArg, args);
|
||||||
|
}
|
||||||
|
else if (!timeoutId[argsString]) {
|
||||||
|
timeoutId[argsString] = setTimeout(function () {
|
||||||
|
lastCalled[argsString] = new Date();
|
||||||
|
timeoutId[argsString] = null;
|
||||||
|
func.apply(thisArg, args);
|
||||||
|
}, remaining);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 235 KiB |
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
@ -1,5 +1,5 @@
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<script src="js/devtoolsBackground.js"></script>
|
<script src="devtoolsBackground.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -0,0 +1,61 @@
|
|||||||
|
// Karma configuration for testing injected
|
||||||
|
// AngularJS instrumentation
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
|
||||||
|
// base path, that will be used to resolve files and exclude
|
||||||
|
basePath: '',
|
||||||
|
|
||||||
|
// frameworks to use
|
||||||
|
frameworks: ['jasmine'],
|
||||||
|
|
||||||
|
// list of files / patterns to load in the browser
|
||||||
|
files: [
|
||||||
|
'test/inject/mock/*.js',
|
||||||
|
'js/inject/debug.final.js',
|
||||||
|
'bower_components/angular/angular.js',
|
||||||
|
'bower_components/angular-mocks/angular-mocks.js',
|
||||||
|
'test/inject/*.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
// list of files to exclude
|
||||||
|
exclude: [
|
||||||
|
'*.min.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
// test results reporter to use
|
||||||
|
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
|
||||||
|
reporters: ['progress'],
|
||||||
|
|
||||||
|
// web server port
|
||||||
|
port: 9876,
|
||||||
|
|
||||||
|
// enable / disable colors in the output (reporters and logs)
|
||||||
|
colors: true,
|
||||||
|
|
||||||
|
// level of logging
|
||||||
|
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
|
||||||
|
// enable / disable watching file and executing tests whenever any file changes
|
||||||
|
autoWatch: true,
|
||||||
|
|
||||||
|
// Start these browsers, currently available:
|
||||||
|
// - Chrome
|
||||||
|
// - ChromeCanary
|
||||||
|
// - Firefox
|
||||||
|
// - Opera
|
||||||
|
// - Safari (only Mac)
|
||||||
|
// - PhantomJS
|
||||||
|
// - IE (only Windows)
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
|
||||||
|
// If browser does not capture in given timeout [ms], kill it
|
||||||
|
captureTimeout: 60000,
|
||||||
|
|
||||||
|
// Continuous Integration mode
|
||||||
|
// if true, it capture browsers, run tests and exit
|
||||||
|
singleRun: false
|
||||||
|
});
|
||||||
|
};
|
@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
files = [
|
|
||||||
JASMINE,
|
|
||||||
JASMINE_ADAPTER,
|
|
||||||
'js/lib/angular.js',
|
|
||||||
'js/lib/angular-mocks.js',
|
|
||||||
|
|
||||||
'js/panelApp.js',
|
|
||||||
'js/controllers/*.js',
|
|
||||||
'js/directives/*.js',
|
|
||||||
'js/filters/*.js',
|
|
||||||
'js/services/*.js',
|
|
||||||
|
|
||||||
'test/mock/*.js',
|
|
||||||
'test/*.js'
|
|
||||||
];
|
|
||||||
|
|
||||||
exclude = [];
|
|
||||||
|
|
||||||
autoWatch = true;
|
|
||||||
autoWatchInterval = 1;
|
|
||||||
logLevel = LOG_INFO;
|
|
||||||
logColors = true;
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
files = [
|
|
||||||
JASMINE,
|
|
||||||
JASMINE_ADAPTER,
|
|
||||||
'js/lib/angular.js',
|
|
||||||
'js/lib/angular-mocks.js',
|
|
||||||
|
|
||||||
'js/panelApp.js',
|
|
||||||
'js/controllers/*.js',
|
|
||||||
'js/directives/*.js',
|
|
||||||
'js/filters/*.js',
|
|
||||||
'js/services/*.js',
|
|
||||||
|
|
||||||
'test/mock/*.js',
|
|
||||||
'test/*.js'
|
|
||||||
];
|
|
||||||
|
|
||||||
exclude = [];
|
|
||||||
|
|
||||||
autoWatch = true;
|
|
||||||
autoWatchInterval = 1;
|
|
||||||
logLevel = LOG_INFO;
|
|
||||||
logColors = true;
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
// Similar to browserify, but inlines the scripts instead.
|
||||||
|
// This is a really dumb naive approach that only supports `module.exports =`
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var debug = fs.readFileSync(__dirname + '/../content-scripts/inject.js', 'utf8');
|
||||||
|
|
||||||
|
var r = new RegExp("require\\('(.+?)'\\)", 'g');
|
||||||
|
|
||||||
|
var out = debug.replace(r, function (match, file) {
|
||||||
|
return ex(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(__dirname + '/../content-scripts/inject.build.js', out);
|
||||||
|
|
||||||
|
// takes the contents of a file, wraps it in a closure
|
||||||
|
// and returns the result
|
||||||
|
function ex (file) {
|
||||||
|
contents = fs.readFileSync(__dirname + '/../content-scripts/' + file, 'utf8');
|
||||||
|
contents = contents.replace('module.exports = ', 'return ');
|
||||||
|
contents = '(function () {\n' +
|
||||||
|
'// exported from ' + file + '\n' +
|
||||||
|
contents + '\n' +
|
||||||
|
'}())';
|
||||||
|
|
||||||
|
return contents;
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
describe('inject', function () {
|
||||||
|
|
||||||
|
// inject/debug bootstraps asynchronously
|
||||||
|
beforeEach(function () {});
|
||||||
|
|
||||||
|
it('should expose a __ngDebug object to window', function () {
|
||||||
|
expect(window.__ngDebug).not.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRootScopeIds', function () {
|
||||||
|
|
||||||
|
it('should start empty', function () {
|
||||||
|
expect(__ngDebug.getRootScopeIds()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bootstraped', function () {
|
||||||
|
it('should work', function () {
|
||||||
|
var elt, scope;
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
angular.module('foo', []).controller('A', function ($scope) {
|
||||||
|
$scope.model = 1;
|
||||||
|
$scope.complexModel = { foo: { bar: 'baz' } };
|
||||||
|
});
|
||||||
|
elt = angular.element('<div ng-app="foo" ng-controller="A"></div>');
|
||||||
|
angular.bootstrap(elt, ['ng', 'foo']);
|
||||||
|
scope = elt.data().$scope;
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(__ngDebug.getRootScopeIds().length).toBe(1);
|
||||||
|
expect(__ngDebug.getModel(scope.$id).model).toBe(scope.model);
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
scope.model = 2;
|
||||||
|
scope.$digest();
|
||||||
|
expect(__ngDebug.getModel(scope.$id).model).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
__ngDebug.watchModel(scope.$id, 'complexModel');
|
||||||
|
scope.$digest();
|
||||||
|
});
|
||||||
|
|
||||||
|
waits(60);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
scope.complexModel.b = 1;
|
||||||
|
scope.$digest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
// mocks window.chrome.extension
|
||||||
|
|
||||||
|
chrome.extension = {
|
||||||
|
sendMessage: dump
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
// sets the __ngDebug cookie
|
||||||
|
window.document.cookie = '__ngDebug=true;';
|