Compare commits

...

122 Commits

Author SHA1 Message Date
Chakib Benziane a2f6528268 inject $injector to window 9 years ago
Richard Littauer db89db9431 Added the icon for chrome://extensions 9 years ago
Brian Ford 154772b7f4 style(json-tree.spec): add missing semicolon 10 years ago
Brian Ford f83c7f5ca6 style(inspectedApp): remove todo comment 10 years ago
Brian Ford a219e1cdd3 feat(pageAction): show page action when instrumentation is active
Closes #199
10 years ago
Brian Ford 68b72bfa6f fix(background): remove data for closed tabs 10 years ago
Brian Ford 0cf05a7588 fix(background): hydrate inspected app data on devtools connect 10 years ago
Brian Ford f2a9044e3b fix(panel): handle refresh better 10 years ago
Brian Ford ca0dc695e5 style(scopeTree): fix deep indentation 10 years ago
Brian Ford fce0052322 fix(inspectedApp): handle refresh better 10 years ago
Brian Ford 287074b73e fix(background): handle refresh better 10 years ago
Brian Ford bd32b00172 style(scopeTree): fix wrapping 10 years ago
Brian Ford e902ecae96 docs(readme): add travis badge 10 years ago
Brian Ford 8c67a3e77e test(travis): run unit tests on travis + saucelabs 10 years ago
Brian Ford d7008a1368 test(inspectedApp): add tests for messaging 10 years ago
Brian Ford d3e7bd6ec4 v0.7.4 10 years ago
Brian Ford 2d0e8abff0 docs(readme): link to instructions on installing from src 10 years ago
Brian Ford 60bdec9949 docs(contributing): update build and release docs 10 years ago
Brian Ford bd2ebbfd39 fix(sidebarPane): shorten title
Closes #75
10 years ago
Brian Ford bec405c8c7 docs(readme): explain how to install previous versions
Closes #191
10 years ago
Brian Ford 010d8b573b v0.7.3 10 years ago
Brian Ford 738ba58e77 fix(enableInstrumentation): sync instrumentation status 10 years ago
Brian Ford f745b92053 fix(panel/hints): fix category name 10 years ago
Brian Ford 5cee2720b7 v0.7.2 10 years ago
Brian Ford 5fd469035a fix(background): flush buffer on devtools connect 10 years ago
Brian Ford a521bb9ea3 fix(inspectedApp): ignore events with missing scope info 10 years ago
Brian Ford aa1c792d1a fix(batScopeTree): unfocus model editing on enter/exit keydown 10 years ago
Brian Ford 1e5783f848 v0.7.1 10 years ago
Brian Ford f8e205beb6 v0.7.0 10 years ago
Brian Ford 1cfef9bd07 feat(instrumentation): do not enable by default 10 years ago
Brian Ford f1c54f3f24 v0.6.1 10 years ago
Brian Ford 1e3aa47522 chore(gitignore): ignore package dir 10 years ago
Brian Ford 5dc1e14aeb v0.6.0 10 years ago
Brian Ford 48a4c65ddc chore(package.json): update dependencies and build script 10 years ago
Brian Ford e0ac7065df chore(deps): use angular 1.3.6 from npm
Closes #164
10 years ago
Brian Ford 2caeb0d288 feat(propertiesPane): add back properties pane
Closes #160
10 years ago
Brian Ford d28787930e v0.5.0 10 years ago
Brian Ford 2d92189c48 chore(gulp): add tasks to help automate releases 10 years ago
Brian Ford aee24175f4 fix(json-tree): handle inspecting array elements 10 years ago
Brian Ford 6a193f0d14 style(json-tree): fix formatting bools 10 years ago
Brian Ford 2768929c9c feat(panel/scopes): expand models pane when first scope is selected 10 years ago
Brian Ford 727ab2da9c chore: remove watchers panel for now 10 years ago
Brian Ford a192204441 fix(panel/hints): resolve layout issue with multiple categories 10 years ago
Brian Ford 3a999ad92a fix(panel/scopes): disallow editing special scope properties 10 years ago
Brian Ford 239c560c74 chore: remove console log 10 years ago
Brian Ford eb2e7555d4 feat(panel/scopes): set window.$scope on scope select 10 years ago
Brian Ford 147cd0b796 feat(panel/scopes): allow changing model values 10 years ago
Brian Ford f9c028bc26 docs(hint): add header
Closes #144
10 years ago
Michał Gołębiowski 86d7c40af5 docs(contributing): Add `npm install` to build instructions 10 years ago
Brian Ford 626c2b23f6 chore(package.json): add angular-hint dep 10 years ago
Brian Ford 7363f3debb chore(img): add src for logos 10 years ago
Brian Ford 89b577c88d docs(contributing): add notes on directory layout 10 years ago
Brian Ford c35956c61d style(panel): reorganize to improve style 10 years ago
Brian Ford d1f0888929 docs(example): make example more interesting 10 years ago
Brian Ford 918ecd5c7d f 10 years ago
Brian Ford 3de10b6f41 chore(jsonTree): remove unused dependencies 10 years ago
Brian Ford 444398e9d4 refactor(jsonTree): remove dependancy on inspectedApp 10 years ago
Brian Ford d4edd956f6 fix(inspectedApp): dont parse object messages 10 years ago
Brian Ford b40377531e refactor(*): basically change everything
🍕
10 years ago
Brian Ford 706a63cc51 chore(bower): update dependencies 10 years ago
Brian Ford 499c82d5a6 chore(bower): remove check in bower components 10 years ago
Brian Ford 1162787cb0 docs(example): add example 10 years ago
Brian Ford d6463b1e2f style(hintApp): move decls to top of file 10 years ago
Brian Ford 5c676f9959 style(css): formatting, remove over-specified selectors 10 years ago
Brian Ford 556b6d0e0b refactor(*): simplify message API 10 years ago
Brian Ford decec2685d fix(inject): fix path of hint bundle 10 years ago
Brian Ford 6911e08b73 refactor(hintApp): simplify app 10 years ago
Brian Ford f1b460e45a docs(readme/contributing): reorganize docs 10 years ago
Brian Ford 156bf99c1f style(gulp): add missing newline 10 years ago
Carlo s A. Guillen 97380e8fcc test(hintCtrl): implement test for suppressing messages 10 years ago
Erin Altenhof-Long 03bef161b5 test(hintCtrl): add test coverage for hintCtrl functions
Add tests that demonstrate the functionality of hintCtrl formatting functions.
These tests mock the hintService to test the operation of hintCtrl
functions for splitting and using received messages.
10 years ago
Erin Altenhof-Long c432fb6930 refactor(updateAll): remove unnecessary updateAll method
Now that message data is provided incrementally by the hintService,
all the arrays of data may be updated with each message.
10 years ago
Erin Altenhof-Long b86d7a6955 test(karma.conf.js): add karma and dependencies to allow testing 10 years ago
Erin Altenhof-Long 952a4a4c73 refactor(hintService): move communication through developer tools to backend service
To improve testability, the backend communication with the developer tools is moved to
a service that can then be mocked for testing purposes. Moving functionality to
a service means that an app file is needed to create the module before the controller
and the service and that the app, service, and controller should be contained in the
application html page.
10 years ago
Carlo s A. Guillen 0130c9dc8e feat(hintCtrl): implement suppress message feature 10 years ago
Erin Altenhof-Long 6a0ae94f4d fix(loader): create custom loader that is compatible with angular 1.2 and 1.3
A breaking change with the loading of config blocks between angular 1.2 and 1.3 causes
a an error when registering providers if the appropriate version of angular loader is not
used. This custom loader alters the invokeQueue to use config blocks correctly for both
versions of angular. See the issue with the loader and angular docs:
https://github.com/angular/angular-hint/issues/39 And the breaking change for the
angular loader: c0b4e2db9c
10 years ago
Erin Altenhof-Long df5bf33762 fix(defaultPane): change default console pane to "All"
Using "Directives" as the default message pane caused
errors when using AngularHint without any errors from "Directives".
This changes the default to "All".
10 years ago
Erin Altenhof-Long 9bcaec14ee chore(.gitignore): ignore built files and dependencies 10 years ago
Erin Altenhof-Long d826dadf39 chore(): remove static data 10 years ago
Carlos Guillen f06a989a78 chore(hintCtrl): remove use of isEmpty variable 10 years ago
Carlos Guillen 3b55f063e5 chore(hint.html): change ng-bind to ng-show 10 years ago
Erin Altenhof-Long 85606bb78c fix(reload): refresh messages on reload of inspectedWindow
Messages should not be continually added when the inspectedWindow is refreshed.
When the inspectedWindow is refreshed, start a new list of messages.
10 years ago
Erin Altenhof-Long d0da55123f feat(buffer): buffer messages before devTools open
Before the devTools are opened, messages should be buffered. This way they can
be delivered when the devTools are opened.
10 years ago
Erin Altenhof-Long e1cd70d98e fix(ngShow): load csp to allow use of ng-show and update ng-show logic
Due to the loading order of angular in a chrome extension, include the angular-csp.css
to allow the operation of ng-show and ng-class. Also, update the ng-show and ng-class
logic.
10 years ago
Carlo s A. Guillen b94c14e5aa feat(HintCtrl): debounce message logging in UI 10 years ago
Carlo s A. Guillen fe231271b7 feat(*): interact with HintCtrl 10 years ago
Carlo s A. Guillen e81023bac7 feat(*): implement connection between content script and devTools pane 10 years ago
Carlo s A. Guillen 2ebab66b13 feat(*): set up UI 10 years ago
Carlo s A. Guillen f3703f423f feat(*): set up UI 10 years ago
Brian Ford 036a6cf310 docs: add new build script 10 years ago
Brian Ford 99ea8b5225 wip 10 years ago
Brian Ford 4a85f99679 wip 10 years ago
Brian Ford 0c1edd6e58 wip: huge refactor again
sorry not sorry
10 years ago
Brian Ford cd2a9e495d fix: niceNames 10 years ago
Frost a68c9b7724 Moved all the files around and added livereloading to the extension. 10 years ago
Brian Ford 4741ed559f docs: typo like a real engineer 10 years ago
Brian Ford 1cb6beacb7 fix: issue with refactored niceName helper 10 years ago
Brian Ford febbc8c478 docs: add inline doc for background 10 years ago
Brian Ford 253337e55f docs: improve arch doc 10 years ago
Brian Ford 514eddcda9 chore: clean up instrumentation test config 10 years ago
Brian Ford b581e188d1 chore: use local karma install 10 years ago
Brian Ford 8e7ae1e968 Merge pull request #1 from aaronfrost/incremental-instrumentation-stale
docs: add inline documentation
10 years ago
Frost 5089764c36 Add some comments to explain better what is going on here. 10 years ago
Frost da9dc6d740 ignore the bloody node_modules and the webstorm .idea files. 10 years ago
Frost 01560fe7b4 Update the Gruntfile to build the files from their new locations. Brian moved them and the Gruntfile was pointing to their old location. 10 years ago
Brian Ford 989749f4d3 fix: broken code after refactor 10 years ago
Brian Ford 7421f9196f chore: fix spacing output of inline script 10 years ago
Brian Ford f81ebb4701 docs: add architecture document 10 years ago
Brian Ford 1ce75f5922 chore: add inline script to grunt 10 years ago
Brian Ford e4a68061ae chore: fix whitespace in inline.js 10 years ago
Brian Ford 13d23765b2 fix: instrumentation tests 10 years ago
Brian Ford 032b3a6ac6 WIP - I have no idea 10 years ago
Brian Ford 9be0ba8955 fix(lib/throttle): fix bugs, add tests 11 years ago
Brian Ford 704ac4dfbb reorganize plugin 11 years ago
Brian Ford ee422a1957 wip 11 years ago
Brian Ford 6cfcaa8545 cache scope tree, fix issue with cache invalidation 11 years ago
Brian Ford b2c7ac52a4 wip 11 years ago
Brian Ford 2512c63e97 wip 11 years ago
Brian Ford d4f851dae3 wip 11 years ago
Brian Ford 04d7a1bbd1 message passing nonsense 11 years ago
Brian Ford 49a130c53b chore(style): fix spacing style 11 years ago
Brian Ford a5ecad55fd fix(instrumentation): fix patchin provider object with array properties 11 years ago

7
.gitignore vendored

@ -1 +1,8 @@
batarang-release-*.zip
*.build.js
hint.bundle.js
.idea
bower_components
node_modules
coverage/
package/

@ -0,0 +1,21 @@
language: node_js
node_js:
- "0.10"
env:
global:
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
- LOGS_DIR=/tmp/angular-hint-build/logs
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
install:
- mkdir -p $LOGS_DIR
- ./scripts/sauce_connect_setup.sh
- npm install
- npm install -g gulp
- npm install -g karma-cli
- ./scripts/wait_for_browser_provider.sh
script:
- ./scripts/test_on_sauce.sh

@ -1,37 +0,0 @@
# 0.4.3 (2013-06-26)
## Bug fixes
### instrumentation
* fix injecting a provider with array syntax (354fa541)
# Before...
## Features
### build
* use Grunt for building Batarang (4b584ec3)
## Bug fixes
### instrumentation
* improve perf of serializing models by ignoring $ properties, optimizing derez (d0fa3141)
* fix issue with checking models of root scopes (bae0b604)
* fix instrumenting $get (ce962885)
### model
* fix issue in model pane where the first element of array models is undefined (2da618fd)
### style
* prefix highlight class name (9bb1ebb3)

@ -0,0 +1,36 @@
# Contributing
This document describes how to contribute to Batarang
## Installing from Source
1. Clone the repository: `git clone git://github.com/angular/angularjs-batarang`
2. Install the npm dependencies `npm install`
3. Build the inject script: `npm run build`
4. Navigate to `chrome://chrome/extensions/` and enable Developer Mode.
5. Choose "Load unpacked extension"
6. In the dialog, open the directory you just cloned.
## Running the tests
## Packaging a release
I (@btford) will do this periodically, but I'm adding these instructions here
for posterity.
1. Edit the version number in `manifest.json` and `package.json` with the new version.
2. Run `gulp zip`
3. `git add package.json manifest.json dist/`
4. `git commit -m "v1.2.3"`
5. `git tag v1.2.3`
6. `git push upstream master && git push upstream --tags`
7. Upload `batarang-v1.2.3.zip` to Web Store via the [Web Store Dashboard](https://chrome.google.com/webstore/developer/dashboard)
## Layout
The `panel` directory contains...
`panel/components` contains self-contained directives and services.
## Testing Batarang manually

@ -1,15 +0,0 @@
# Batarang FAQ
### How do I measure a directive's performance?
If your directive uses `$watch`, you should be able to see the watch expression wherever your directive is used.
### My $watch functions show up as just "function ()" in the performance tab
Use named functions for $watch:
```javascript
scope.$watch(function checkIfSomethingChanged() {
// ...
}, function whenThatChanges(newValue, oldValue) {
// ...
});
```

@ -1,110 +0,0 @@
var markdown = require('marked'),
semver = require('semver');
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('manifest.json'),
changelog: {
options: {
dest: 'CHANGELOG.md',
versionFile: 'manifest.json'
}
},
bump: {
options: {
file: 'manifest.json'
}
},
markdown: {
all: {
file: 'README.md',
dest: 'panes/help.html'
}
},
release: {
options: {
commitMessage: 'v<%= version %>',
tagName: 'v<%= version %>',
bump: false, // we have out own bump
npm: false,
file: 'manifest.json'
}
},
stage: {
files: ['CHANGELOG.md', 'pane/help.html']
},
zip: {
release: {
src: [
'css/*.css',
'img/**',
'js/**',
'panes/*.html',
'panel.html',
'LICENSE',
'manifest.json',
'background.html',
'devtoolsBackground.html'
],
dest: 'batarang-release-' + Date.now() + '.zip'
}
}
});
grunt.registerTask('bump', 'bump manifest version', function (type) {
var options = this.options({
file: grunt.config('pkgFile') || 'package.json'
});
function setup(file, type){
var pkg = grunt.file.readJSON(file);
var newVersion = pkg.version = semver.inc(pkg.version, type || 'patch');
return {file: file, pkg: pkg, newVersion: newVersion};
}
var config = setup(options.file, type);
grunt.file.write(config.file, JSON.stringify(config.pkg, null, ' ') + '\n');
grunt.log.ok('Version bumped to ' + config.newVersion);
});
grunt.registerMultiTask('markdown', 'compiles markdown README into html for the help pane', function() {
var md = grunt.file.read(this.data.file);
// pull out the install instructions, etc
var marker = '<!-- HELP TAB -->';
md = md.substr(md.indexOf(marker) + marker.length);
// fix image paths
md = md.replace(/https:\/\/github.com\/angular\/angularjs-batarang\/raw\/master\/img\//g, '/img/');
var html = markdown(md);
grunt.file.write(this.data.dest, html);
});
grunt.registerTask('stage', 'git add files before running the release task', function() {
grunt.util.spawn({
cmd: process.platform === 'win32' ?
'git.cmd' : 'git',
args: ['add'].append(this.data.files)
}, grunt.task.current.async());
});
grunt.registerTask('url', 'open the url for the chrome app dashboard', function() {
var url = 'https://chrome.google.com/webstore/developer/dashboard';
console.log('Publish to: ' + url);
grunt.util.spawn({
cmd: process.platform === 'win32' ?
'explorer' : 'open',
args: [ url ]
}, grunt.task.current.async());
});
grunt.loadNpmTasks('grunt-release');
grunt.loadNpmTasks('grunt-zip');
grunt.loadNpmTasks('grunt-conventional-changelog');
grunt.registerTask('default', ['bump', 'markdown', 'changelog', 'release', 'zip']);
};

@ -1,61 +1,23 @@
# AngularJS Batarang
[![Build Status](https://travis-ci.org/angular/angularjs-batarang.svg)](https://travis-ci.org/angular/angularjs-batarang)
## Installing from the Web Store
https://chrome.google.com/webstore/detail/ighdmehidhipcmcojjgiloacoafjmpfk
## Installing from Source
1. Clone the repository: `git clone git://github.com/angular/angularjs-batarang`
2. Navigate to `chrome://chrome/extensions/` and enable Developer Mode.
3. Choose "Load unpacked extension"
4. Open the directory you just cloned (should open with Chrome, otherwise try dragging/dropping the file into Chrome) and follow the prompts to install.
## Screencast
Check out [this screencast](http://www.youtube.com/embed/q-7mhcHXSfM) that walks you through the Batarang's features.
## Using the Batarang
First, navigate Chrome Canary to the AngularJS application that you want to debug. [Open the Developer Tools](https://developers.google.com/chrome-developer-tools/docs/overview#access). There should be an AngularJS icon. Click on it to open the AngularJS Batarang.
<!-- HELP TAB -->
In order to begin using the Batarang you need to click the "enable" checkbox. This will cause the application's tab to refresh, and the Batarang to begin collecting perfomance and debug information about the inspected app.
The Batarang has five tabs: Model, Performance, Dependencies, Options, and Help.
### Models
![Batarang screenshot](https://github.com/angular/angularjs-batarang/raw/master/img/models.png)
Starting at the top of this tab, there is the root selection. If the application has only one `ng-app` declaration (as most applications do) then you will not see the option to change roots.
## Installing Previous Versions
Below that is a tree showing how scopes are nested, and which models are attached to them. Clicking on a scope name will take you to the Elements tab, and show you the DOM element associated with that scope. Models and methods attached to each scope are listed with bullet points on the tree. Just the name of methods attached to a scope are shown. Models with a simple value and complex objects are shown as JSON. You can edit either, and the changes will be reflected in the application being debugged.
1. Download and extract one of the files from the [Batarang releases page on GitHub](https://github.com/angular/angularjs-batarang/releases)
1. Navigate to `chrome://chrome/extensions/` in Chrome
1. If you've installed Batarang from the web store, disable or remove that version
1. On the top right, check the checkbox for "Developer mode"
1. Click "Load unpacked extension..."
1. Select the directory where you extracted the extension
1. Close and re-open any inspected tabs
### Performance
![Batarang performance tab screenshot](https://github.com/angular/angularjs-batarang/raw/master/img/perf.png)
The performance tab must be enabled separately because it causes code to be injected into AngularJS to track and report performance metrics. There is also an option to output performance metrics to the console.
Below that is a tree of watched expressions, showing which expressions are attached to which scopes. Much like the model tree, you can collapse sections by clicking on "toggle" and you can inspect the element that a scope is attached to by clicking on the scope name.
Underneath that is a graph showing the relative performance of all of the application's expressions. This graph will update as you interact with the application.
### Dependencies
![Batarang dependencies tab screenshot](https://github.com/angular/angularjs-batarang/raw/master/img/deps.png)
The dependencies tab shows a visualization of the application's dependencies. When you hover over a service name, services that depend on the hovered service turn green, and those the hovered service depend on turn red.
### Options
![Batarang options tab screenshot](https://github.com/angular/angularjs-batarang/raw/master/img/options.png)
Last, there is the options tab. The options tab has three checkboxes: one for "show applications," "show scopes," and "show bindings." Each of these options, when enabled, highlights the respective feature of the application being debugged; scopes will have a red outline, and bindings will have a blue outline, and applications a green outline.
### Elements
![Batarang console screenshot](https://github.com/angular/angularjs-batarang/raw/master/img/inspect.png)
The Batarang also hooks into some of the existing features of the Chrome developer tools. For AngularJS applications, there is now a properties pane on in the Elements tab. Much like the model tree in the AngularJS tab, you can use this to inspect the models attached to a given element's scope.
## Installing from Source
### Console
![Batarang console screenshot](https://github.com/angular/angularjs-batarang/raw/master/img/console.png)
See the [instructions in the contributing guide](https://github.com/angular/angularjs-batarang/blob/master/CONTRIBUTING.md#installing-from-source)
The Batarang exposes some convenient features to the Chrome developer tools console. To access the scope of an element selected in the Elements tab of the developer tools, in console, you can type `$scope`. If you change value of some model on `$scope` and want to have this change reflected in the running application, you need to call `$scope.$apply()` after making the change.
## License
MIT

@ -1,5 +0,0 @@
<html>
<body>
<script src="js/background.js"></script>
</body>
</html>

@ -0,0 +1,111 @@
// tabId -> devtool port
var inspectedTabs = {};
// tabId -> buffered data
var data = {};
function bufferOrForward(message, sender) {
var tabId = sender.tab.id,
devToolsPort = inspectedTabs[tabId];
if (!data[tabId] || message === 'refresh') {
resetState(tabId);
// TODO: this is kind of a hack-y spot to put this
showPageAction(tabId);
}
// TODO: not sure how I feel about special-casing `refresh`
if (message !== 'refresh') {
message = JSON.parse(message);
}
bufferData(tabId, message);
if (devToolsPort) {
devToolsPort.postMessage(message);
}
}
function resetState(tabId) {
data[tabId] = {
hints: [],
scopes: {}
};
}
function bufferData(tabId, message) {
var tabData = data[tabId],
scope;
if (message.message) {
return tabData.hints.push(message);
}
if (message.event) {
if (message.event === 'scope:new') {
tabData.scopes[message.child] = {
parent: message.parent,
children: [],
models: {}
};
if (tabData.scopes[message.parent]) {
tabData.scopes[message.parent].children.push(message.child);
}
} else if (message.id && (scope = tabData.scopes[message.id])) {
if (message.event === 'scope:destroy') {
if (scope.parent) {
scope.parent.children.splice(scope.parent.children.indexOf(child), 1);
}
delete scopes[message.id];
} else if (message.event === 'model:change') {
scope.models[message.path] = (typeof message.value === 'undefined') ?
undefined : JSON.parse(message.value);
} else if (message.event === 'scope:link') {
scope.descriptor = message.descriptor;
}
}
}
}
// context script > background
chrome.runtime.onMessage.addListener(bufferOrForward);
chrome.runtime.onConnect.addListener(function(devToolsPort) {
devToolsPort.onMessage.addListener(registerInspectedTabId);
function registerInspectedTabId(inspectedTabId) {
inspectedTabs[inspectedTabId] = devToolsPort;
if (!data[inspectedTabId]) {
resetState(inspectedTabId);
}
devToolsPort.postMessage({
event: 'hydrate',
data: data[inspectedTabId]
});
devToolsPort.onDisconnect.addListener(function () {
delete inspectedTabs[inspectedTabId];
});
//devToolsPort.onMessage.removeListener(registerInspectedTabId);
}
});
chrome.tabs.onRemoved.addListener(function (tabId) {
if (data[tabId]) {
delete data[tabId];
}
});
function showPageAction(tabId) {
chrome.pageAction.show(tabId);
chrome.pageAction.setTitle({
tabId: tabId,
title: 'Batarang Active'
});
}

@ -0,0 +1,21 @@
/*
* karma.conf.js and karma.es5.conf.js optionally load this
*/
var CUSTOM_LAUNCHERS = {
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome',
version: '35'
}
};
module.exports = function(options) {
options.sauceLabs = {
testName: 'AngularJS Batarang Unit Tests',
startConnect: true
};
options.customLaunchers = CUSTOM_LAUNCHERS;
options.browsers = Object.keys(CUSTOM_LAUNCHERS);
options.reporters = ['dots', 'saucelabs'];
};

@ -0,0 +1,24 @@
/*
* karma.conf.js optionally loads this
*/
module.exports = function(options) {
if (!isTravis()) {
return;
} else if (!options.sauceLabs) {
throw new Error('This should be loaded after karma.sauce config');
}
options.sauceLabs.build = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
options.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
options.sauceLabs.startConnect = false;
// TODO(vojta): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
options.transports = ['xhr-polling'];
options.singleRun = true;
};
function isTravis() {
return !!process.env.TRAVIS;
}

@ -1,815 +0,0 @@
/*!
* Bootstrap Responsive v2.0.4
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.hide-text {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.input-block-level {
display: block;
width: 100%;
min-height: 28px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.hidden {
display: none;
visibility: hidden;
}
.visible-phone {
display: none !important;
}
.visible-tablet {
display: none !important;
}
.hidden-desktop {
display: none !important;
}
@media (max-width: 767px) {
.visible-phone {
display: inherit !important;
}
.hidden-phone {
display: none !important;
}
.hidden-desktop {
display: inherit !important;
}
.visible-desktop {
display: none !important;
}
}
@media (min-width: 768px) and (max-width: 979px) {
.visible-tablet {
display: inherit !important;
}
.hidden-tablet {
display: none !important;
}
.hidden-desktop {
display: inherit !important;
}
.visible-desktop {
display: none !important ;
}
}
@media (max-width: 480px) {
.nav-collapse {
-webkit-transform: translate3d(0, 0, 0);
}
.page-header h1 small {
display: block;
line-height: 18px;
}
input[type="checkbox"],
input[type="radio"] {
border: 1px solid #ccc;
}
.form-horizontal .control-group > label {
float: none;
width: auto;
padding-top: 0;
text-align: left;
}
.form-horizontal .controls {
margin-left: 0;
}
.form-horizontal .control-list {
padding-top: 0;
}
.form-horizontal .form-actions {
padding-right: 10px;
padding-left: 10px;
}
.modal {
position: absolute;
top: 10px;
right: 10px;
left: 10px;
width: auto;
margin: 0;
}
.modal.fade.in {
top: auto;
}
.modal-header .close {
padding: 10px;
margin: -10px;
}
.carousel-caption {
position: static;
}
}
@media (max-width: 767px) {
body {
padding-right: 20px;
padding-left: 20px;
}
.navbar-fixed-top,
.navbar-fixed-bottom {
margin-right: -20px;
margin-left: -20px;
}
.container-fluid {
padding: 0;
}
.dl-horizontal dt {
float: none;
width: auto;
clear: none;
text-align: left;
}
.dl-horizontal dd {
margin-left: 0;
}
.container {
width: auto;
}
.row-fluid {
width: 100%;
}
.row,
.thumbnails {
margin-left: 0;
}
[class*="span"],
.row-fluid [class*="span"] {
display: block;
float: none;
width: auto;
margin-left: 0;
}
.input-large,
.input-xlarge,
.input-xxlarge,
input[class*="span"],
select[class*="span"],
textarea[class*="span"],
.uneditable-input {
display: block;
width: 100%;
min-height: 28px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.input-prepend input,
.input-append input,
.input-prepend input[class*="span"],
.input-append input[class*="span"] {
display: inline-block;
width: auto;
}
}
@media (min-width: 768px) and (max-width: 979px) {
.row {
margin-left: -20px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
margin-left: 20px;
}
.container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 724px;
}
.span12 {
width: 724px;
}
.span11 {
width: 662px;
}
.span10 {
width: 600px;
}
.span9 {
width: 538px;
}
.span8 {
width: 476px;
}
.span7 {
width: 414px;
}
.span6 {
width: 352px;
}
.span5 {
width: 290px;
}
.span4 {
width: 228px;
}
.span3 {
width: 166px;
}
.span2 {
width: 104px;
}
.span1 {
width: 42px;
}
.offset12 {
margin-left: 764px;
}
.offset11 {
margin-left: 702px;
}
.offset10 {
margin-left: 640px;
}
.offset9 {
margin-left: 578px;
}
.offset8 {
margin-left: 516px;
}
.offset7 {
margin-left: 454px;
}
.offset6 {
margin-left: 392px;
}
.offset5 {
margin-left: 330px;
}
.offset4 {
margin-left: 268px;
}
.offset3 {
margin-left: 206px;
}
.offset2 {
margin-left: 144px;
}
.offset1 {
margin-left: 82px;
}
.row-fluid {
width: 100%;
*zoom: 1;
}
.row-fluid:before,
.row-fluid:after {
display: table;
content: "";
}
.row-fluid:after {
clear: both;
}
.row-fluid [class*="span"] {
display: block;
float: left;
width: 100%;
min-height: 28px;
margin-left: 2.762430939%;
*margin-left: 2.709239449638298%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
margin-left: 0;
}
.row-fluid .span12 {
width: 99.999999993%;
*width: 99.9468085036383%;
}
.row-fluid .span11 {
width: 91.436464082%;
*width: 91.38327259263829%;
}
.row-fluid .span10 {
width: 82.87292817100001%;
*width: 82.8197366816383%;
}
.row-fluid .span9 {
width: 74.30939226%;
*width: 74.25620077063829%;
}
.row-fluid .span8 {
width: 65.74585634900001%;
*width: 65.6926648596383%;
}
.row-fluid .span7 {
width: 57.182320438000005%;
*width: 57.129128948638304%;
}
.row-fluid .span6 {
width: 48.618784527%;
*width: 48.5655930376383%;
}
.row-fluid .span5 {
width: 40.055248616%;
*width: 40.0020571266383%;
}
.row-fluid .span4 {
width: 31.491712705%;
*width: 31.4385212156383%;
}
.row-fluid .span3 {
width: 22.928176794%;
*width: 22.874985304638297%;
}
.row-fluid .span2 {
width: 14.364640883%;
*width: 14.311449393638298%;
}
.row-fluid .span1 {
width: 5.801104972%;
*width: 5.747913482638298%;
}
input,
textarea,
.uneditable-input {
margin-left: 0;
}
input.span12,
textarea.span12,
.uneditable-input.span12 {
width: 714px;
}
input.span11,
textarea.span11,
.uneditable-input.span11 {
width: 652px;
}
input.span10,
textarea.span10,
.uneditable-input.span10 {
width: 590px;
}
input.span9,
textarea.span9,
.uneditable-input.span9 {
width: 528px;
}
input.span8,
textarea.span8,
.uneditable-input.span8 {
width: 466px;
}
input.span7,
textarea.span7,
.uneditable-input.span7 {
width: 404px;
}
input.span6,
textarea.span6,
.uneditable-input.span6 {
width: 342px;
}
input.span5,
textarea.span5,
.uneditable-input.span5 {
width: 280px;
}
input.span4,
textarea.span4,
.uneditable-input.span4 {
width: 218px;
}
input.span3,
textarea.span3,
.uneditable-input.span3 {
width: 156px;
}
input.span2,
textarea.span2,
.uneditable-input.span2 {
width: 94px;
}
input.span1,
textarea.span1,
.uneditable-input.span1 {
width: 32px;
}
}
@media (min-width: 1200px) {
.row {
margin-left: -30px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
margin-left: 30px;
}
.container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 1170px;
}
.span12 {
width: 1170px;
}
.span11 {
width: 1070px;
}
.span10 {
width: 970px;
}
.span9 {
width: 870px;
}
.span8 {
width: 770px;
}
.span7 {
width: 670px;
}
.span6 {
width: 570px;
}
.span5 {
width: 470px;
}
.span4 {
width: 370px;
}
.span3 {
width: 270px;
}
.span2 {
width: 170px;
}
.span1 {
width: 70px;
}
.offset12 {
margin-left: 1230px;
}
.offset11 {
margin-left: 1130px;
}
.offset10 {
margin-left: 1030px;
}
.offset9 {
margin-left: 930px;
}
.offset8 {
margin-left: 830px;
}
.offset7 {
margin-left: 730px;
}
.offset6 {
margin-left: 630px;
}
.offset5 {
margin-left: 530px;
}
.offset4 {
margin-left: 430px;
}
.offset3 {
margin-left: 330px;
}
.offset2 {
margin-left: 230px;
}
.offset1 {
margin-left: 130px;
}
.row-fluid {
width: 100%;
*zoom: 1;
}
.row-fluid:before,
.row-fluid:after {
display: table;
content: "";
}
.row-fluid:after {
clear: both;
}
.row-fluid [class*="span"] {
display: block;
float: left;
width: 100%;
min-height: 28px;
margin-left: 2.564102564%;
*margin-left: 2.510911074638298%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
margin-left: 0;
}
.row-fluid .span12 {
width: 100%;
*width: 99.94680851063829%;
}
.row-fluid .span11 {
width: 91.45299145300001%;
*width: 91.3997999636383%;
}
.row-fluid .span10 {
width: 82.905982906%;
*width: 82.8527914166383%;
}
.row-fluid .span9 {
width: 74.358974359%;
*width: 74.30578286963829%;
}
.row-fluid .span8 {
width: 65.81196581200001%;
*width: 65.7587743226383%;
}
.row-fluid .span7 {
width: 57.264957265%;
*width: 57.2117657756383%;
}
.row-fluid .span6 {
width: 48.717948718%;
*width: 48.6647572286383%;
}
.row-fluid .span5 {
width: 40.170940171000005%;
*width: 40.117748681638304%;
}
.row-fluid .span4 {
width: 31.623931624%;
*width: 31.5707401346383%;
}
.row-fluid .span3 {
width: 23.076923077%;
*width: 23.0237315876383%;
}
.row-fluid .span2 {
width: 14.529914530000001%;
*width: 14.4767230406383%;
}
.row-fluid .span1 {
width: 5.982905983%;
*width: 5.929714493638298%;
}
input,
textarea,
.uneditable-input {
margin-left: 0;
}
input.span12,
textarea.span12,
.uneditable-input.span12 {
width: 1160px;
}
input.span11,
textarea.span11,
.uneditable-input.span11 {
width: 1060px;
}
input.span10,
textarea.span10,
.uneditable-input.span10 {
width: 960px;
}
input.span9,
textarea.span9,
.uneditable-input.span9 {
width: 860px;
}
input.span8,
textarea.span8,
.uneditable-input.span8 {
width: 760px;
}
input.span7,
textarea.span7,
.uneditable-input.span7 {
width: 660px;
}
input.span6,
textarea.span6,
.uneditable-input.span6 {
width: 560px;
}
input.span5,
textarea.span5,
.uneditable-input.span5 {
width: 460px;
}
input.span4,
textarea.span4,
.uneditable-input.span4 {
width: 360px;
}
input.span3,
textarea.span3,
.uneditable-input.span3 {
width: 260px;
}
input.span2,
textarea.span2,
.uneditable-input.span2 {
width: 160px;
}
input.span1,
textarea.span1,
.uneditable-input.span1 {
width: 60px;
}
.thumbnails {
margin-left: -30px;
}
.thumbnails > li {
margin-left: 30px;
}
.row-fluid .thumbnails {
margin-left: 0;
}
}
@media (max-width: 979px) {
body {
padding-top: 0;
}
.navbar-fixed-top,
.navbar-fixed-bottom {
position: static;
}
.navbar-fixed-top {
margin-bottom: 18px;
}
.navbar-fixed-bottom {
margin-top: 18px;
}
.navbar-fixed-top .navbar-inner,
.navbar-fixed-bottom .navbar-inner {
padding: 5px;
}
.navbar .container {
width: auto;
padding: 0;
}
.navbar .brand {
padding-right: 10px;
padding-left: 10px;
margin: 0 0 0 -5px;
}
.nav-collapse {
clear: both;
}
.nav-collapse .nav {
float: none;
margin: 0 0 9px;
}
.nav-collapse .nav > li {
float: none;
}
.nav-collapse .nav > li > a {
margin-bottom: 2px;
}
.nav-collapse .nav > .divider-vertical {
display: none;
}
.nav-collapse .nav .nav-header {
color: #999999;
text-shadow: none;
}
.nav-collapse .nav > li > a,
.nav-collapse .dropdown-menu a {
padding: 6px 15px;
font-weight: bold;
color: #999999;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.nav-collapse .btn {
padding: 4px 10px 4px;
font-weight: normal;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.nav-collapse .dropdown-menu li + li a {
margin-bottom: 2px;
}
.nav-collapse .nav > li > a:hover,
.nav-collapse .dropdown-menu a:hover {
background-color: #222222;
}
.nav-collapse.in .btn-group {
padding: 0;
margin-top: 5px;
}
.nav-collapse .dropdown-menu {
position: static;
top: auto;
left: auto;
display: block;
float: none;
max-width: none;
padding: 0;
margin: 0 15px;
background-color: transparent;
border: none;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.nav-collapse .dropdown-menu:before,
.nav-collapse .dropdown-menu:after {
display: none;
}
.nav-collapse .dropdown-menu .divider {
display: none;
}
.nav-collapse .navbar-form,
.nav-collapse .navbar-search {
float: none;
padding: 9px 15px;
margin: 9px 0;
border-top: 1px solid #222222;
border-bottom: 1px solid #222222;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
}
.navbar .nav-collapse .nav.pull-right {
float: none;
margin-left: 0;
}
.nav-collapse,
.nav-collapse.collapse {
height: 0;
overflow: hidden;
}
.navbar .btn-navbar {
display: block;
}
.navbar-static .navbar-inner {
padding-right: 10px;
padding-left: 10px;
}
}
@media (min-width: 980px) {
.nav-collapse.collapse {
height: auto !important;
overflow: visible !important;
}
}

4978
css/bootstrap.css vendored

File diff suppressed because it is too large Load Diff

@ -1,50 +0,0 @@
path.arc {
fill: #fff;
}
.node {
font-size: 10px;
}
.node:hover {
fill: #1f77b4;
}
.link {
fill: none;
stroke: #1f77b4;
stroke-opacity: .4;
pointer-events: none;
}
.link.source, .link.target {
stroke-opacity: 1;
stroke-width: 2px;
}
.node.target {
fill: #d62728 !important;
}
.link.source {
stroke: #d62728;
}
.node.source {
fill: #2ca02c;
}
.link.target {
stroke: #2ca02c;
}
d3 {
display: block;
max-width: 600px;
height: 100%;
margin: auto;
}
svg text {
font-size: 1.4em;
}

@ -1,106 +0,0 @@
.col {
float: left;
width: 200px;
}
.col-2 {
float: left;
width: 400px;
}
.scope-branch {
margin-left: 30px;
background-color: rgba(0,0,0,0.06);
}
body {
margin: 10px;
}
.well-top {
border-radius: 4px 4px 0 0;
margin-bottom: 0;
}
.well-bottom {
border-radius: 0 0 4px 4px;
border-top: none;
background-color: #E0E0E0;
}
.bat-nav-check {
background-color: #fff;
border: 1px solid #ddd;
border-bottom-color: transparent;
border-radius: 4px 4px 0 0;
padding: 8px 12px 8px 12px;
margin-right: 2px;
line-height: 18px;
}
.bat-nav-check input[type="checkbox"] {
margin: 0;
}
bat-scope-tree .selected {
font-weight: bold;
text-decoration: underline;
color: #333;
}
/*
* Slider widget style based on jquery-ui-bootstrap
* http://addyosmani.github.com/jquery-ui-bootstrap
*/
.ui-slider {
position: relative;
text-align: left;
height: .8em;
border-radius: 4px;
border: 1px solid #aaaaaa;
background: #ffffff;
}
.ui-slider .ui-slider-handle {
position: absolute;
z-index: 2;
width: 1.2em;
height: 1.2em;
top: -.3em;
margin-left: -.6em;
cursor: default;
border-radius: 4px;
background-color: #e6e6e6;
background-repeat: no-repeat;
background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
/*background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);*/
font-size: 13px;
line-height: normal;
border: 1px solid #ccc;
border-bottom-color: #bbb;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-webkit-transition: 0.1s linear background-image;
transition: 0.1s linear background-image;
overflow: visible;
}
.ui-slider .ui-slider-range {
position: absolute;
top: 0;
height: 100%;
z-index: 1;
font-size: .7em;
display: block;
border: 0;
background-position: 0 0;
background-color: #0064cd;
background-repeat: repeat-x;
background-image: -webkit-linear-gradient(top, #049cdb, #0064cd);
/*background-image: linear-gradient(top, #049cdb, #0064cd);*/
}

@ -1,5 +1,5 @@
<html>
<body>
<script src="js/devtoolsBackground.js"></script>
<script src="devtoolsBackground.js"></script>
</body>
</html>
</html>

@ -6,8 +6,11 @@ var getPanelContents = function () {
if (window.angular && $0) {
//TODO: can we move this scope export into updateElementProperties
var scope = window.angular.element($0).scope();
var injector = window.angular.element($0).injector();
// Export $scope to the console
window.$scope = scope;
// Export $injector to the console
window.$injector = injector;
return (function (scope) {
var panelContents = {
__private__: {}
@ -30,16 +33,15 @@ var getPanelContents = function () {
};
panels.elements.createSidebarPane(
"AngularJS Properties",
"$scope",
function (sidebar) {
panels.elements.onSelectionChanged.addListener(function updateElementProperties() {
sidebar.setExpression("(" + getPanelContents.toString() + ")()");
});
});
// Angular panel
var angularPanel = panels.create(
"AngularJS",
"img/angular.png",
"panel.html"
"panel/app.html"
);

2513
dist/hint.js vendored

File diff suppressed because it is too large Load Diff

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>yo</title>
</head>
<body ng-app="myApp">
<div ng-controller="myKontroller">
<h1>name: {{name}}</h1>
<input ng-model="name">
<h1>complex.a.b.c: {{complex.a.b.c}}</h1>
<input ng-model="complex.a.b.c">
<p>scope: {{$id}}</p>
<ul>
<li ng-repeat="num in items">{{num}}</li>
</ul>
</div>
<script src="../node_modules/angular/angular.js"></script>
<script>
angular.module('myApp', []).
controller('myKontroller', function ($scope) {
$scope.name = 'hey';
$scope.items = [1, 2, 3];
$scope.complex = { a: { b: { c: 'yo' }}};
var thing = 'document';
});
</script>
</body>
</html>

@ -0,0 +1,42 @@
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var browserify = require('browserify');
var zip = require('gulp-zip');
var main = require('./package.json').main;
// TODO: make sure manifest version === package.json version === bower.json version
var version = require('./manifest.json').version;
gulp.task('watch', function(){
gulp.watch(['hint.js', '!./dist/*.js'], ['browserify']);
});
gulp.task('browserify', function() {
var bundleStream = browserify('./' + main).bundle().pipe(source(main));
return bundleStream.pipe(gulp.dest('./dist'));
});
/*
* I use this to make a zip for the chrome store
*/
gulp.task('package', ['browserify'], function () {
return gulp.src([
'./dist/**',
'./img/**',
'./panel/**',
'background.js',
'devtoolsBackground.*',
'inject.js',
'manifest.json',
'./node_modules/angular/angular.js'
], {base: '.'})
.pipe(gulp.dest('./package'));
});
gulp.task('zip', ['package'], function () {
return gulp.src('package/**')
.pipe(zip('batarang-' + version + '.zip'))
.pipe(gulp.dest('.'));
});
gulp.task('default', ['browserify']);

@ -0,0 +1,38 @@
/*
* Batarang instrumentation
*
* This gets loaded into the context of the app you are inspecting
*/
require('./loader.js');
require('angular-hint');
angular.hint.onMessage = function (moduleName, message, messageType, category) {
if (!message) {
message = moduleName;
moduleName = 'Unknown'
}
if (typeof messageType === 'undefined') {
messageType = 1;
}
sendMessage({
module: moduleName,
message: message,
severity: messageType,
category: category
});
};
angular.hint.emit = function (ev, data) {
data.event = ev;
sendMessage(data);
};
var eventProxyElement = document.getElementById('__ngBatarangElement');
var customEvent = document.createEvent('Event');
customEvent.initEvent('batarangDataEvent', true, true);
function sendMessage (obj) {
eventProxyElement.innerText = JSON.stringify(obj);
eventProxyElement.dispatchEvent(customEvent);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,28 @@
if (document.cookie.indexOf('__ngDebug=true') != -1) {
bootstrapHint();
}
function bootstrapHint () {
chrome.extension.sendMessage('refresh');
var html = document.getElementsByTagName('html')[0];
var eventProxyElement = document.createElement('div');
eventProxyElement.id = '__ngBatarangElement';
eventProxyElement.style.display = 'none';
html.appendChild(eventProxyElement);
// inject into the application context from the content script context
var script = window.document.createElement('script');
script.src = chrome.extension.getURL('dist/hint.js');
eventProxyElement.addEventListener('batarangDataEvent', function () {
var eventData = eventProxyElement.innerText;
chrome.extension.sendMessage(eventData);
});
html.setAttribute('ng-hint', '');
html.appendChild(script);
}

@ -1,19 +0,0 @@
// notify of page refreshes
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function (msg) {
if (msg.action === 'register') {
var respond = function (tabId, changeInfo, tab) {
if (tabId !== msg.inspectedTabId) {
return;
}
port.postMessage('refresh');
};
chrome.tabs.onUpdated.addListener(respond);
port.onDisconnect.addListener(function () {
chrome.tabs.onUpdated.removeListener(respond);
});
}
});
});

@ -1,9 +0,0 @@
angular.module('panelApp').controller('DepsCtrl', function DepsCtrl($scope, appDeps) {
$scope.$on('poll', function () {
appDeps.get(function (deps) {
$scope.$apply(function () {
$scope.deps = deps;
});
});
});
});

@ -1,68 +0,0 @@
angular.module('panelApp').controller('ModelCtrl', function ModelCtrl($scope, appContext, appModel) {
$scope.inspect = function () {
appContext.inspect(this.val.id);
};
$scope.select = function () {
$scope.selectedScope = this.val.id;
};
// TODO: fix this
$scope.edit = function () {
appContext.executeOnScope(this.val.id, function (scope, elt, args) {
scope.$apply(function () {
scope[args.name] = args.value;
});
}, {
name: this.key,
value: JSON.parse(this.item)
});
};
$scope.roots = [];
$scope.model = null;
$scope.selectedRoot = null;
$scope.selectedScope = null;
$scope.enableInspector = appModel.enableInspector;
$scope.$on('poll', function () {
// get the list of root scopes
appModel.getRootScopes(function (rootScopes) {
$scope.$apply(function () {
$scope.roots = rootScopes;
if ($scope.roots.length === 0) {
$scope.selectedRoot = null;
} else if (!$scope.selectedRoot) {
$scope.selectedRoot = $scope.roots[0];
}
if ($scope.selectedRoot && !$scope.selectedScope) {
$scope.selectedScope = $scope.selectedRoot;
}
});
});
// get scope tree
if ($scope.selectedRoot) {
appModel.getScopeTree($scope.selectedRoot, function (tree) {
$scope.$apply(function () {
$scope.tree = tree;
});
});
}
// get models on the selected scope
if ($scope.selectedScope) {
appModel.getModel($scope.selectedScope, function (model) {
$scope.$apply(function () {
$scope.model = model;
});
});
}
});
});

@ -1,40 +0,0 @@
angular.module('panelApp').controller('OptionsCtrl', function OptionsCtrl($scope, appInfo, appHighlight) {
$scope.debugger = {
scopes: false,
bindings: false,
app: false
};
['scopes', 'bindings', 'app'].forEach(function (thing) {
$scope.$watch('debugger.' + thing, function (val) {
appHighlight[thing](val);
});
});
appInfo.getAngularVersion(function (version) {
$scope.$apply(function () {
$scope.version = version;
});
});
appInfo.getAngularSrc(function (status) {
$scope.$apply(function () {
switch(status) {
case 'good':
$scope.status = 'success';
$scope.explain = 'CDN detected';
break;
case 'bad':
$scope.status = 'important';
$scope.explain = 'You are using the old code.angularjs.org links, which are slow! You should switch to the new CDN link. See <a target="_blank" href="http://blog.angularjs.org/2012/07/angularjs-now-hosted-on-google-cdn.html">this post</a> for more info';
break;
case 'info':
$scope.status = 'info';
$scope.explain = 'You may want to use the CDN-hosted AngularJS files. See <a target="_blank" href="http://blog.angularjs.org/2012/07/angularjs-now-hosted-on-google-cdn.html">this post</a> for more info';
break;
}
});
});
});

@ -1,47 +0,0 @@
angular.module('panelApp').controller('PerfCtrl', function PerfCtrl($scope, appContext, appPerf, appModel, appWatch, filesystem) {
$scope.histogram = [];
$scope.roots = [];
$scope.min = 0;
$scope.max = 100;
$scope.clearHistogram = function () {
appPerf.clear();
};
$scope.exportData = function () {
filesystem.exportJSON('file.json', $scope.histogram);
};
$scope.$watch('log', function (newVal, oldVal) {
appContext.setLog(newVal);
});
$scope.inspect = function () {
appContext.inspect(this.val.id);
};
$scope.$on('poll', function () {
appPerf.get(function (histogram) {
$scope.$apply(function () {
$scope.histogram = histogram;
});
});
appModel.getRootScopes(function (rootScopes) {
$scope.$apply(function () {
$scope.roots = rootScopes;
if ($scope.roots.length === 0) {
$scope.selectedRoot = null;
} else if (!$scope.selectedRoot) {
$scope.selectedRoot = $scope.roots[0];
}
});
});
appWatch.getWatchTree($scope.selectedRoot, function (tree) {
$scope.tree = tree;
});
});
});

274
js/directives/d3.js vendored

@ -1,274 +0,0 @@
// D3 visualization
// TODO: D3 as a service
angular.module('panelApp').directive('batD3', function () {
return {
restrict: 'E',
terminal: true,
scope: {
val: '=val'
},
link: function (scope, element, attrs) {
// Based on code from: http://mbostock.github.com/d3/talk/20111116/bundle.html
// Initialize Element
// ------------------
var div = d3.select(element[0]);
// Constants
// ---------
var w = 600,
h = 600,
rx = w / 2,
ry = h / 2,
m0,
rotate = 0;
// Helpers
// -------
// generate element ids that do not have '$'
var sanitize = function (key) {
return key.replace('$', 'dollar')
}
// TODO: refactor the data transformation to make it faster
// For instance, build up the ideal structure in inject/degug.js
var packages = {
// Lazily construct the package hierarchy from class names.
root: function(classes) {
var map = {};
// add "classes" with no dependencies
var exist = {},
toAdd = [];
classes.forEach(function (cl) {
exist[cl.name] = true;
});
classes.forEach(function (cl) {
cl.imports.forEach(function (im) {
if (!exist[im]) {
toAdd.push(im);
exist[im] = true;
}
});
});
toAdd.forEach(function (a) {
classes.push({
name: a,
imports: []
});
});
function find(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
},
// Return a list of imports for the given array of nodes.
imports: function(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({source: map[d.name], target: map[i]});
});
});
return imports;
}
};
// Instantiate and Style D3 Objects
// --------------------------------
var cluster = d3.layout.cluster().
size([360, ry - 120]).
sort(function(a, b) { return d3.ascending(a.key, b.key); });
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial().
interpolate("bundle").
tension(.85).
radius(function(d) { return d.y; }).
angle(function(d) { return d.x / 180 * Math.PI; });
var svg = div.append("svg:svg").
attr("preserveAspectRatio", "xMinYMin meet").
attr("viewBox", [0, 0, w, h].join(' ')).
attr("height", h).
append("svg:g").
attr("transform", "translate(" + rx + "," + ry + ")");
// Render the data whenever "val" changes
// --------------------------------------
scope.$watch('val', function (newVal, oldVal) {
var classes;
if (!newVal || newVal.length === 0) {
svg.selectAll('*').remove();
return;
}
if (oldVal && oldVal.length === newVal.length) {
var changed = false;
for (i = 0; i < oldVal.length; i++) {
if (oldVal[i].name !== newVal[i].name || newVal[i].imports.length !== oldVal[i].imports.length) {
changed = true;
break;
}
}
if (!changed) {
return;
}
}
classes = newVal.slice(0);
classes.sort(function (a, b) {
return .5 - (a.name < b.name);
});
svg.selectAll('*').remove();
svg.append("svg:path")
.attr("class", "arc")
.attr("d", d3.svg.arc().outerRadius(ry - 120).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
.on("mousedown", mousedown);
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = svg.selectAll("path.link")
.data(links)
.enter().append("svg:path")
.attr("class", function(d) { return "link source-" + sanitize(d.source.key) + " target-" + sanitize(d.target.key); })
.attr("d", function(d, i) { return line(splines[i]); });
svg.selectAll("g.node")
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) { return "node-" + sanitize(d.key); })
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.append("svg:text")
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) { return d.key; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
});
/*
d3.select("input[type=range]").on("change", function() {
line.tension(this.value / 100);
path.attr("d", function(d, i) { return line(splines[i]); });
});
*/
//TODO: decide where to attach these events
/*
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
*/
function mouse(e) {
return [e.pageX - rx, e.pageY - ry];
}
function mousedown() {
m0 = mouse(d3.event);
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
}
}
function mouseup() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
rotate += dm;
if (rotate > 360) rotate -= 360;
else if (rotate < 0) rotate += 360;
m0 = null;
div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");
svg
.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
.selectAll("g.node text")
.attr("dx", function(d) { return (d.x + rotate) % 360 < 180 ? 8 : -8; })
.attr("text-anchor", function(d) { return (d.x + rotate) % 360 < 180 ? "start" : "end"; })
.attr("transform", function(d) { return (d.x + rotate) % 360 < 180 ? null : "rotate(180)"; });
}
}
function mouseover(d) {
svg.selectAll("path.link.target-" + sanitize(d.key))
.classed("target", true)
.each(updateNodes("source", true));
svg.selectAll("path.link.source-" + sanitize(d.key))
.classed("source", true)
.each(updateNodes("target", true));
}
function mouseout(d) {
svg.selectAll("path.link.source-" + sanitize(d.key))
.classed("source", false)
.each(updateNodes("target", false));
svg.selectAll("path.link.target-" + sanitize(d.key))
.classed("target", false)
.each(updateNodes("source", false));
}
function updateNodes(name, value) {
return function(d) {
if (value) this.parentNode.appendChild(this);
svg.select("#node-" + sanitize(d[name].key)).classed(name, value);
};
}
function cross(a, b) {
return a[0] * b[1] - a[1] * b[0];
}
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1];
}
}
};
});

@ -1,65 +0,0 @@
angular.module('panelApp').directive('batJsonTree', function($compile) {
return {
restrict: 'E',
terminal: true,
scope: {
val: '='
//edit: '=',
},
link: function (scope, element, attrs) {
// this is more complicated then it should be
// see: https://github.com/angular/angular.js/issues/898
var buildDom = function (object) {
var html = '';
var prop;
if (object === undefined) {
html += '<i>undefined</i>';
} else if (object === null) {
html += '<i>null</i>';
} else if (object instanceof Array) {
var i;
html += '<div class="scope-branch">[ ';
if (object.length > 0) {
html += buildDom(object[0]);
for (i = 1; i < object.length; i++) {
html += ', ' + buildDom(object[i]);
}
}
html += ' ]</div>';
} else if (object instanceof Object) {
html += ' { ';
for (prop in object) {
if (object.hasOwnProperty(prop)) {
html += '<div class="scope-branch">' + prop + ': ' + buildDom(object[prop]) + '</div>';
}
}
html += ' } ';
} else {
html += '<span>' + object.toString() + '</span>';
}
return html;
};
var isEmpty = function (object) {
var prop;
for (prop in object) {
if (object.hasOwnProperty(prop)) {
return false;
}
}
return true;
};
scope.$watch('val', function (newVal, oldVal) {
if (newVal === null) {
element.html('<div class="alert alert-info">Select a scope to view its models.</div>');
} else if (isEmpty(newVal)) {
element.html('<pre>{ This scope has no models }</pre>');
} else {
element.html('<pre>' + buildDom(newVal) + '</pre>');
}
});
}
};
});

@ -1,46 +0,0 @@
angular.module('panelApp').directive('batScopeTree', function ($compile) {
// make toggle settings persist across $compile
var modelState = {};
var scopeState = {};
var selected = null;
var template =
'<div class="scope-branch">' +
'<a href ng-click="inspect()">&lt;</a> ' +
'<a href ng-click="select()" ng-class="{selected: selectedScope == val.id}">Scope ({{val.id}})</a>' +
'<div ng-repeat="child in val.children">' +
'<bat-scope-tree ' +
'val="child" ' +
'inspect="inspect" ' +
'select="select" ' +
'selected-scope="selectedScope">' +
'</bat-scope-tree>' +
'</div>' +
'</div>';
return {
restrict: 'E',
terminal: true,
scope: {
val: '=',
select: '=',
selectedScope: '=',
inspect: '='
},
link: function (scope, element, attrs) {
// this is more complicated then it should be
// see: https://github.com/angular/angular.js/issues/898
element.append(template);
var childScope = scope.$new();
childScope.select = scope.select;
//childScope.selectedScope = scope.selectedScope;
childScope.inspect = scope.inspect;
$compile(element.contents())(childScope);
}
};
});

@ -1,43 +0,0 @@
// range slider
angular.module('panelApp').directive('batSlider', function($compile) {
return {
restrict: 'E',
terminal: true,
scope: {
minimum: '=minimum',
maximum: '=maximum'
},
link: function (scope, element, attrs) {
var dom = $('<div class="ui-slider">' +
'<a class="ui-slider-handle" href></a>' +
'<a class="ui-slider-handle" href></a>' +
'<div class="ui-slider-range"></div>' +
'</div>');
element.append(dom);
$compile(element.contents())(scope.$new());
dom.slider({
range: true,
values: [0, 100],
slide: function () {
var min = $(this).slider('values', 0);
var max = $(this).slider('values', 1);
scope.minimum = min;
scope.maximum = max;
scope.$apply();
},
stop: function () {
var min = $(this).slider('values', 0);
var max = $(this).slider('values', 1);
scope.minimum = min;
scope.maximum = max;
scope.$apply();
}
});
}
};
});

@ -1,43 +0,0 @@
// watchers tree
angular.module('panelApp').directive('batWatcherTree', function($compile) {
// make toggle settings persist across $compile
var scopeState = {};
return {
restrict: 'E',
terminal: true,
scope: {
val: '=val',
inspect: '=inspect'
},
link: function (scope, element, attrs) {
// this is more complicated then it should be
// see: https://github.com/angular/angular.js/issues/898
element.append(
'<div class="scope-branch">' +
'<a href ng-click="inspect()">Scope ({{val.id}})</a> | ' +
'<a href ng-click="scopeState[val.id] = !scopeState[val.id]">toggle</a>' +
'<div ng-hide="scopeState[val.id]">' +
'<ul>' +
'<li ng-repeat="item in val.watchers">' +
'<a href ng-hide="item.split(\'\n\').length < 2" ng-click="showState = !showState">toggle</a> ' +
'<code ng-hide="showState && item.split(\'\n\').length > 1">{{item | first}}</code>' +
'<pre ng-hide="!showState || item.split(\'\n\').length < 2">' +
'{{item}}' +
'</pre>' +
'</li>' +
'</ul>' +
'<div ng-repeat="child in val.children">' +
'<bat-watcher-tree val="child" inspect="inspect"></bat-watcher-tree>' +
'</div>' +
'</div>' +
'</div>');
var childScope = scope.$new();
childScope.scopeState = scopeState;
$compile(element.contents())(childScope);
}
};
});

@ -1,6 +0,0 @@
// returns the first line of a multi-line string
angular.module('panelApp').filter('first', function () {
return function (input, output) {
return input.split("\n")[0];
};
});

@ -1,6 +0,0 @@
// returns the number's first 4 decimals
angular.module('panelApp').filter('precision', function () {
return function (input, output) {
return input.toPrecision(4);
};
});

@ -1,20 +0,0 @@
// Sort watchers by time
// Used by the performance tab
angular.module('panelApp').filter('sortByTime', function () {
return function (input, min, max) {
var copy = input.slice(0);
copy = copy.sort(function (a, b) {
return b.time - a.time;
});
if (typeof min !== 'number' || typeof max !== 'number') {
return copy;
}
var start = Math.floor(input.length * min/100);
var end = Math.ceil(input.length * max/100) - start;
return copy.splice(start, end);
};
});

@ -1,888 +0,0 @@
var inject = function () {
document.head.appendChild((function () {
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.
var ngLoaded = function () {
if (!window.angular) {
return false;
}
try {
window.angular.module('ng');
}
catch (e) {
return false;
}
return true;
};
if (!ngLoaded()) {
(function () {
// 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);
bootstrap(window);
}
if (oldOnload) {
oldOnload.apply(this, arguments);
}
};
}
};
document.addEventListener('DOMNodeInserted', areWeThereYet);
}());
return;
}
// do not patch twice
if (window.__ngDebug) {
return;
}
// Helpers
// =======
// polyfill for performance.now on older webkit
if (!performance.now) {
performance.now = performance.webkitNow;
}
// Based on cycle.js
// 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 occurrence.
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 instanceof HTMLElement) {
return value.innerHTML.toString().trim();
}
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 (value instanceof Array) {
nu = [];
for (i = 0; i < value.length; i += 1) {
nu[i] = derez(value[i], path + '[' + i + ']');
}
} else {
nu = {};
for (name in value) {
if (name[0] !== '$' && 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
// ===
// 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;
};
// helper to extract dependencies from function arguments
// not all versions of AngularJS expose annotate
var annotate = angular.injector().annotate;
if (!annotate) {
annotate = (function () {
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;
}
return 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;
};
}());
}
// throttle based on _.throttle from Lo-Dash
// https://github.com/bestiejs/lodash/blob/master/lodash.js#L4625
var throttle = function (func, wait) {
var args,
result,
thisArg,
timeoutId,
lastCalled = 0;
function trailingCall() {
lastCalled = new Date();
timeoutId = null;
result = func.apply(thisArg, args);
}
return function() {
var now = new Date(),
remaining = wait - (now - lastCalled);
args = arguments;
thisArg = this;
if (remaining <= 0) {
clearTimeout(timeoutId);
timeoutId = null;
lastCalled = now;
result = func.apply(thisArg, args);
}
else if (!timeoutId) {
timeoutId = setTimeout(trailingCall, remaining);
}
return result;
};
};
var debounce = function (func, wait, immediate) {
var args,
result,
thisArg,
timeoutId;
function delayed() {
timeoutId = null;
if (!immediate) {
result = func.apply(thisArg, args);
}
}
return function() {
var isImmediate = immediate && !timeoutId;
args = arguments;
thisArg = this;
clearTimeout(timeoutId);
timeoutId = setTimeout(delayed, wait);
if (isImmediate) {
result = func.apply(thisArg, args);
}
return result;
};
};
var updateScopeModelCache = function (scope) {
debug.models[scope.$id] = getScopeLocals(scope);
debug.scopeDirty[scope.$id] = false;
};
var popover = null;
// Public API
// ==========
var api = window.__ngDebug = {
getDeps: function () {
return debug.deps;
},
getRootScopeIds: function () {
var ids = [];
angular.forEach(debug.rootScopes, function (elt, id) {
ids.push(id);
});
return ids;
},
// returns null or cached scope
getModel: function (id) {
if (debug.scopeDirty[id]) {
updateScopeModelCache(debug.scopes[id]);
return debug.models[id];
}
},
getScopeTree: function (id) {
if (debug.scopeTreeDirty[id] === false) {
return;
}
var traverse = function (sc) {
var tree = {
id: 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);
if (tree) {
debug.scopeTreeDirty[id] = false;
}
return tree;
},
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;
},
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;
},
enable: 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);
};
}
};
// 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: {},
// map of scope.$ids --> model objects
models: {},
// map of $ids --> bools
scopeDirty: {},
// map of $ids --> refs to $rootScope objects
rootScopes: {},
scopeTreeDirty: {},
deps: []
};
// Instrumentation
// ===============
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 () {
debug.deps.push({
name: name,
imports: annotate(tempGet)
});
return tempGet.apply(this, arguments);
};
});
} else if (definition instanceof Array) {
// it is a constructoctor with array syntax
var tempConstructor = definition[definition.length - 1];
definition[definition.length - 1] = function () {
debug.deps.push({
name: name,
imports: 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 () {
debug.deps.push({
name: name,
imports: 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 () {
debug.deps.push({
name: name,
imports: annotate(tempGet)
});
return tempGet.apply(this, arguments);
});
} else {
debug.deps.push({
name: name,
imports: 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) {
name[key] = function () {
debug.deps.push({
name: key,
imports: annotate(value)
});
return value.apply(this, arguments);
};
});
} else {
debug.deps.push({
name: name,
imports: annotate(definition)
});
}
return temp.apply(this, arguments);
};
});
$provide.decorator('$rootScope', function ($delegate) {
var watchFnToHumanReadableString = function (fn) {
if (fn.exp) {
return fn.exp.trim();
} else if (fn.name) {
return fn.name.trim();
} else {
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);
// 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;
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;
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();
debug.scopeDirty[this.$id] = true;
//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;
return ret;
};
}
return _watch.apply(this, arguments);
};
// patch $destroy
// --------------
var _destroy = $delegate.__proto__.$destroy;
$delegate.__proto__.$destroy = function () {
if (debug.watchers[this.$id]) {
delete debug.watchers[this.$id];
}
if (debug.models[this.$id]) {
delete debug.models[this.$id];
}
if (debug.scopes[this.$id]) {
delete debug.scopes[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;
debug.scopeTreeDirty[ret.$root.$id] = true;
}
// 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;
debug.scopeDirty[ret.$id] = true;
return ret;
};
// patch $digest
// -------------
var _digest = $delegate.__proto__.$digest;
$delegate.__proto__.$digest = function (fn) {
var ret = _digest.apply(this, arguments);
debug.scopeDirty[this.$id] = true;
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();
debug.scopeDirty[this.$id] = true;
// 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;
});
});
};
// Return a script element with the above code embedded in it
var script = window.document.createElement('script');
script.innerHTML = '(' + fn.toString() + '(window))';
return script;
}()));
};
// only inject if cookie is set
if (document.cookie.indexOf('__ngDebug=true') != -1) {
document.addEventListener('DOMContentLoaded', inject);
}

File diff suppressed because it is too large Load Diff

14760
js/lib/angular.js vendored

File diff suppressed because it is too large Load Diff

4645
js/lib/d3.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,6 +0,0 @@
// Broadcast poll events
angular.module('panelApp', []).run(function ($rootScope) {
setInterval(function () {
$rootScope.$broadcast('poll');
}, 500);
});

@ -1,101 +0,0 @@
// Service for running code in the context of the application being debugged
angular.module('panelApp').factory('appContext', function (chromeExtension) {
// Public API
// ==========
return {
// TODO: Fix selection of scope
// https://github.com/angular/angularjs-batarang/issues/6
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);
},
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) {
window.document.cookie = '__ngDebug=true;';
window.document.location.reload();
});
} else {
chromeExtension.eval(function (window) {
window.document.cookie = '__ngDebug=false;';
window.document.location.reload();
});
}
},
getDebug: function (cb) {
chromeExtension.eval(function (window) {
return document.cookie.indexOf('__ngDebug=true') !== -1;
}, cb);
},
// takes a bool
setLog: function (setting) {
setting = !!setting;
chromeExtension.eval('function (window) {' +
'window.__ngDebug.log = ' + setting.toString() + ';' +
'}');
},
// Registering events
// ------------------
// TODO: depreciate this; only poll from now on?
// There are some cases where you need to gather data on a once-per-bootstrap basis, for
// instance getting the version of AngularJS
// 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);
});
}
};
});

@ -1,23 +0,0 @@
// Service for injecting CSS into the application
angular.module('panelApp').factory('appCss', function (chromeExtension) {
return {
addCssRule: function (args) {
chromeExtension.eval(function (window, args) {
var styleSheet = document.styleSheets[document.styleSheets.length - 1];
styleSheet.insertRule(args.selector + '{' + args.style + '}', styleSheet.cssRules.length);
}, args);
},
removeCssRule: function (args) {
chromeExtension.eval(function (window, args) {
var styleSheet = document.styleSheets[document.styleSheets.length - 1];
var i;
for (i = styleSheet.cssRules.length - 1; i >= 0; i -= 1) {
if (styleSheet.cssRules[i].cssText === args.selector + ' { ' + args.style + '; }') {
styleSheet.deleteRule(i);
}
}
}, args);
}
};
});

@ -1,26 +0,0 @@
// Service for retrieving and caching application dependencies
angular.module('panelApp').factory('appDeps', function (chromeExtension, appContext) {
var _depsCache = [];
// clear cache on page refresh
appContext.watchRefresh(function () {
_depsCache = [];
});
return {
get: function (callback) {
chromeExtension.eval(function (window) {
if (window.__ngDebug) {
return window.__ngDebug.getDeps();
}
},
function (data) {
if (data) {
_depsCache = data;
}
callback(_depsCache);
});
}
};
});

@ -1,39 +0,0 @@
// Service for highlighting parts of the application
angular.module('panelApp').factory('appHighlight', function (appCss) {
//TODO: improve look of highlighting; for instance, if an element is bound and a scope,
// you will only see the most recently applied outline
var styles = {
scopes: {
selector: '.ng-scope',
style: 'border: 1px solid red'
},
bindings: {
selector: '.ng-binding',
style: 'border: 1px solid blue'
},
app: {
selector: '[ng-app]',
style: 'border: 1px solid green'
}
};
var api = {};
for (i in styles) {
if (styles.hasOwnProperty(i)) {
// create closure around "styles"
(function () {
var style = styles[i];
api[i] = function (setting) {
if (setting) {
appCss.addCssRule(style);
} else {
appCss.removeCssRule(style);
}
}
}());
}
}
return api;
});

@ -1,67 +0,0 @@
// Service for running code in the context of the application being debugged
angular.module('panelApp').factory('appInfo', function (chromeExtension, appContext) {
var _versionCache = null,
_srcCache = null;
// clear cache on page refresh
appContext.watchRefresh(function () {
_versionCache = null;
_srcCache = null;
});
return {
getAngularVersion: function (callback) {
if (_versionCache) {
setTimeout(function () {
callback(_versionCache);
}, 0);
} else {
chromeExtension.eval(function () {
return window.angular.version.full +
' ' +
window.angular.version.codeName;
}, function (data) {
_versionCache = data;
callback(_versionCache);
});
}
},
getAngularSrc: function (callback) {
if (_srcCache) {
setTimeout(function () {
callback(_srcCache);
}, 0);
} else {
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';
}, function (src) {
if (src) {
_srcCache = src;
}
callback(_srcCache);
});
}
}
};
});

@ -1,27 +0,0 @@
// Service for highlighting parts of the application
angular.module('panelApp').factory('appInspect', function (chromeExtension) {
return {
enable: function () {
chromeExtension.eval(function (window) {
var angular = window.angular;
var popover = angular.element('<div style="position: fixed; left: 10px; top: 10px; z-index: 9999; background-color: white; padding: 10px;"></div>');
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);
});
});
}
};
});

@ -1,66 +0,0 @@
// Service for running code in the context of the application being debugged
angular.module('panelApp').factory('appModel', function (chromeExtension, appContext) {
var _scopeTreeCache = {},
_scopeCache = {},
_rootScopeCache = [];
// clear cache on page refresh
appContext.watchRefresh(function () {
_scopeCache = {};
_rootScopeCache = [];
});
return {
getRootScopes: function (callback) {
chromeExtension.eval(function (window) {
if (!window.__ngDebug) {
return;
}
return window.__ngDebug.getRootScopeIds();
},
function (data) {
if (data) {
_rootScopeCache = data;
}
callback(_rootScopeCache);
});
},
// only runs callback if model has changed since last call
getModel: function (id, callback) {
if (!id) {
return;
}
chromeExtension.eval(function (window, args) {
return window.__ngDebug.getModel(args.id);
}, {id: id}, function (tree) {
if (tree) {
_scopeCache[id] = tree;
}
callback(_scopeCache[id]);
});
},
getScopeTree: function (id, callback) {
if (!id) {
return;
}
chromeExtension.eval(function (window, args) {
return window.__ngDebug.getScopeTree(args.id);
}, {id: id}, function (tree) {
if (tree) {
_scopeTreeCache[id] = tree;
}
callback(_scopeTreeCache[id]);
});
},
enableInspector: function (argument) {
chromeExtension.eval(function (window, args) {
return window.__ngDebug.enable();
});
}
};
});

@ -1,62 +0,0 @@
// Service for retrieving and caching performance data
angular.module('panelApp').factory('appPerf', function (chromeExtension, appContext) {
var _histogramCache = [],
_watchNameToPerf = {},
_totalCache = 0;
var clear = function () {
_histogramCache = [];
_watchNameToPerf = {};
_totalCache = 0;
};
// clear cache on page refresh
appContext.watchRefresh(function () {
clear();
});
var getHistogramData = function (callback) {
chromeExtension.eval(function (window) {
if (!window.__ngDebug) {
return {};
}
return window.__ngDebug.getWatchPerf();
},
function (data) {
if (data && data.length) {
updateHistogram(data);
}
callback();
});
};
var updateHistogram = function (data) {
data.forEach(function (info) {
_totalCache += info.time;
if (_watchNameToPerf[info.name]) {
_watchNameToPerf[info.name].time += info.time;
} else {
_watchNameToPerf[info.name] = info;
_histogramCache.push(info);
}
});
// recalculate all percentages
_histogramCache.forEach(function (item) {
item.percent = (100 * item.time / _totalCache).toPrecision(3);
});
};
// Public API
// ==========
return {
get: function (callback) {
getHistogramData(function () {
callback(_histogramCache);
});
},
clear: clear
};
});

@ -1,22 +0,0 @@
// Service for running code in the context of the application being debugged
angular.module('panelApp').factory('appWatch', function (chromeExtension) {
var _watchCache = {};
// Public API
// ==========
return {
getWatchTree: function (id, callback) {
chromeExtension.eval("function (window, args) {" +
"return window.__ngDebug.getWatchTree(args.id);" +
"}", {id: id}, function (tree) {
if (tree) {
_watchCache[id] = tree;
}
callback(_watchCache[id]);
});
}
};
});

@ -1,27 +0,0 @@
// abstraction layer for Chrome Extension APIs
angular.module('panelApp').value('chromeExtension', {
sendRequest: function (requestName, cb) {
chrome.extension.sendRequest({
script: requestName,
tab: chrome.devtools.inspectedWindow.tabId
}, cb || function () {});
},
// evaluates in the context of a window
//written because I don't like the API for chrome.devtools.inspectedWindow.eval;
// passing strings instead of functions are gross.
eval: function (fn, args, cb) {
// with two args
if (!cb && typeof args === 'function') {
cb = args;
args = {};
} else if (!args) {
args = {};
}
chrome.devtools.inspectedWindow.eval('(' +
fn.toString() +
'(window, ' +
JSON.stringify(args) +
'));', cb);
}
});

@ -1,30 +0,0 @@
// Service for exporting as JSON
angular.module('panelApp').factory('filesystem', function(chromeExtension) {
// taken from:
// http://html5-demos.appspot.com/static/html5storage/index.html#slide59
// TODO: error handlers?
return {
exportJSON: function (name, data) {
//TODO: file size/limits? 1024*1024
window.webkitRequestFileSystem(window.TEMPORARY, 1024*1024, function (fs) {
fs.root.getFile(name + '.json', {create: true}, function (fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var blob = new Blob([ JSON.stringify(data) ], { type: 'text/plain' });
fileWriter.onwriteend = function () {
// navigate to file, will download
//location.href = fileEntry.toURL();
window.open(fileEntry.toURL());
};
fileWriter.write(blob);
}, function() {});
}, function() {});
}, function() {});
}
};
});

@ -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,31 @@
/*
* This karma conf tests just the panel app
*/
var sauceConfig = require('./config/karma.sauce.conf');
var travisConfig = require('./config/karma.travis.conf');
module.exports = function(config) {
var options = {
frameworks: ['browserify', 'jasmine'],
files: [
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'panel/app.js',
'panel/**/*.js',
'panel/**/*.spec.js'
],
exclude: [],
preprocessors: {
'hint.js': [ 'browserify' ]
},
browsers: ['Chrome'],
};
if (process.argv.indexOf('--sauce') > -1) {
sauceConfig(options);
travisConfig(options);
}
config.set(options);
};

@ -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,432 @@
/**
* @license AngularJS v1.3.0-build.3042+sha.76e57a7
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function() {'use strict';
/**
* @description
*
* This object provides a utility for producing rich Error messages within
* Angular. It can be called as follows:
*
* var exampleMinErr = minErr('example');
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
*
* The above creates an instance of minErr in the example namespace. The
* resulting error will have a namespaced error code of example.one. The
* resulting error will replace {0} with the value of foo, and {1} with the
* value of bar. The object is not restricted in the number of arguments it can
* take.
*
* If fewer arguments are specified than necessary for interpolation, the extra
* interpolation markers will be preserved in the final string.
*
* Since data will be parsed statically during a build step, some restrictions
* are applied with respect to how minErr instances are created and called.
* Instances should have names of the form namespaceMinErr for a minErr created
* using minErr('namespace') . Error codes, namespaces and template strings
* should all be static strings, not variables or general expressions.
*
* @param {string} module The namespace to use for the new minErr instance.
* @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
*/
function minErr(module) {
return function () {
var code = arguments[0],
prefix = '[' + (module ? module + ':' : '') + code + '] ',
template = arguments[1],
templateArgs = arguments,
stringify = function (obj) {
if (typeof obj === 'function') {
return obj.toString().replace(/ \{[\s\S]*$/, '');
} else if (typeof obj === 'undefined') {
return 'undefined';
} else if (typeof obj !== 'string') {
return JSON.stringify(obj);
}
return obj;
},
message, i;
message = prefix + template.replace(/\{\d+\}/g, function (match) {
var index = +match.slice(1, -1), arg;
if (index + 2 < templateArgs.length) {
arg = templateArgs[index + 2];
if (typeof arg === 'function') {
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
} else if (typeof arg === 'undefined') {
return 'undefined';
} else if (typeof arg !== 'string') {
return toJson(arg);
}
return arg;
}
return match;
});
message = message + '\nhttp://errors.angularjs.org/1.3.0-build.3042+sha.76e57a7/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
encodeURIComponent(stringify(arguments[i]));
}
return new Error(message);
};
}
/**
* @ngdoc type
* @name angular.Module
* @module ng
* @description
*
* Interface for configuring angular {@link angular.module modules}.
*/
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** @type {Object.<string, angular.Module>} */
var modules = {};
/**
* @ngdoc function
* @name angular.module
* @module ng
* @description
*
* The `angular.module` is a global place for creating, registering and retrieving Angular
* modules.
* All modules (angular core or 3rd party) that should be available to an application must be
* registered using this mechanism.
*
* When passed two or more arguments, a new module is created. If passed only one argument, an
* existing module (the name passed as the first argument to `module`) is retrieved.
*
*
* # Module
*
* A module is a collection of services, directives, controllers, filters, and configuration information.
* `angular.module` is used to configure the {@link auto.$injector $injector}.
*
* ```js
* // Create a new module
* var myModule = angular.module('myModule', []);
*
* // register a new service
* myModule.value('appName', 'MyCoolApp');
*
* // configure existing services inside initialization blocks.
* myModule.config(['$locationProvider', function($locationProvider) {
* // Configure existing providers
* $locationProvider.hashPrefix('!');
* }]);
* ```
*
* Then you can create an injector and load your modules like this:
*
* ```js
* var injector = angular.injector(['ng', 'myModule'])
* ```
*
* However it's more likely that you'll just use
* {@link ng.directive:ngApp ngApp} or
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
* @param {!Array.<string>=} requires If specified then new module is being created. If
* unspecified then the module is being retrieved for further configuration.
* @param {Function=} configFn Optional configuration function for the module. Same as
* {@link angular.Module#config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
*/
return function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument.", name);
}
/** @type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** @type {!Array.<Function>} */
var configBlocks = [];
/** @type {!Array.<Function>} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_configBlocks: [],
_runBlocks: runBlocks,
/**
* @ngdoc property
* @name angular.Module#requires
* @module ng
* @returns {Array.<string>} List of module names which must be loaded before this module.
* @description
* Holds the list of modules which the injector will load before the current module is
* loaded.
*/
requires: requires,
/**
* @ngdoc property
* @name angular.Module#name
* @module ng
* @returns {string} Name of the module.
* @description
*/
name: name,
/**
* @ngdoc method
* @name angular.Module#provider
* @module ng
* @param {string} name service name
* @param {Function} providerType Construction function for creating new instance of the
* service.
* @description
* See {@link auto.$provide#provider $provide.provider()}.
*/
provider: invokeLater('$provide', 'provider'),
/**
* @ngdoc method
* @name angular.Module#factory
* @module ng
* @param {string} name service name
* @param {Function} providerFunction Function for creating new instance of the service.
* @description
* See {@link auto.$provide#factory $provide.factory()}.
*/
factory: invokeLater('$provide', 'factory'),
/**
* @ngdoc method
* @name angular.Module#service
* @module ng
* @param {string} name service name
* @param {Function} constructor A constructor function that will be instantiated.
* @description
* See {@link auto.$provide#service $provide.service()}.
*/
service: invokeLater('$provide', 'service'),
/**
* @ngdoc method
* @name angular.Module#value
* @module ng
* @param {string} name service name
* @param {*} object Service instance object.
* @description
* See {@link auto.$provide#value $provide.value()}.
*/
value: invokeLater('$provide', 'value'),
/**
* @ngdoc method
* @name angular.Module#constant
* @module ng
* @param {string} name constant name
* @param {*} object Constant value.
* @description
* Because the constant are fixed, they get applied before other provide methods.
* See {@link auto.$provide#constant $provide.constant()}.
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
/**
* @ngdoc method
* @name angular.Module#animation
* @module ng
* @param {string} name animation name
* @param {Function} animationFactory Factory function for creating new instance of an
* animation.
* @description
*
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
*
*
* Defines an animation hook that can be later used with
* {@link ngAnimate.$animate $animate} service and directives that use this service.
*
* ```js
* module.animation('.animation-name', function($inject1, $inject2) {
* return {
* eventName : function(element, done) {
* //code to run the animation
* //once complete, then run done()
* return function cancellationFunction(element) {
* //code to cancel the animation
* }
* }
* }
* })
* ```
*
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
* {@link ngAnimate ngAnimate module} for more information.
*/
animation: invokeLater('$animateProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#filter
* @module ng
* @param {string} name Filter name.
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
*/
filter: invokeLater('$filterProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#controller
* @module ng
* @param {string|Object} name Controller name, or an object map of controllers where the
* keys are the names and the values are the constructors.
* @param {Function} constructor Controller constructor function.
* @description
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
*/
controller: invokeLater('$controllerProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#directive
* @module ng
* @param {string|Object} name Directive name, or an object map of directives where the
* keys are the names and the values are the factories.
* @param {Function} directiveFactory Factory function for creating new instance of
* directives.
* @description
* See {@link ng.$compileProvider#directive $compileProvider.directive()}.
*/
directive: invokeLater('$compileProvider', 'directive'),
/**
* @ngdoc method
* @name angular.Module#config
* @module ng
* @param {Function} configFn Execute this function on module load. Useful for service
* configuration.
* @description
* Use this method to register work which needs to be performed on module loading.
* For more about how to configure services, see
* {@link providers#providers_provider-recipe Provider Recipe}.
*/
config: config,
/**
* @ngdoc method
* @name angular.Module#run
* @module ng
* @param {Function} initializationFn Execute this function after injector creation.
* Useful for application initialization.
* @description
* Use this method to register work which should be performed when the injector is done
* loading all modules.
*/
run: function(block) {
runBlocks.push(block);
return this;
}
};
/**
* ANGULAR HINT ALTERATION
* To make this loader compatible with apps that are running
* both Angular 1.2 and 1.3, the loader must handle 1.3 applications
* that expect to initialize their config blocks after all providers
* are registered. Hence, the configBlocks are added to the end
* of the exisiting invokeQueue.
*/
Object.defineProperty(moduleInstance, '_invokeQueue', {
get: function() {
return invokeQueue.concat(configBlocks);
}
});
if (configFn) {
config(configFn);
}
return moduleInstance;
/**
* @param {string} provider
* @param {string} method
* @param {String=} insertMethod
* @returns {angular.Module}
*/
function invokeLater(provider, method, insertMethod, queue) {
if (!queue) queue = invokeQueue;
return function() {
queue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
});
};
});
}
setupModuleLoader(window);
})(window);
/**
* Closure compiler type information
*
* @typedef { {
* requires: !Array.<string>,
* invokeQueue: !Array.<Array.<*>>,
*
* service: function(string, Function):angular.Module,
* factory: function(string, Function):angular.Module,
* value: function(string, *):angular.Module,
*
* filter: function(string, Function):angular.Module,
*
* init: function(Function):angular.Module
* } }
*/
angular.Module;

@ -1,22 +1,44 @@
{
"name": "AngularJS Batarang",
"version": "0.4.3",
"version": "0.7.4",
"description": "Extends the Developer Tools, adding tools for debugging and profiling AngularJS applications.",
"background": {
"page": "background.html"
},
"devtools_page": "devtoolsBackground.html",
"manifest_version": 2,
"permissions": [
"tabs",
"<all_urls>"
],
"icons": {
"16": "img/webstore-icon.png",
"48": "img/webstore-icon.png",
"128": "img/webstore-icon.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/inject/debug.js"],
"js": ["inject.js"],
"run_at": "document_start"
}
],
"background": {
"scripts": [
"background.js"
]
},
"page_action": {
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
"default_title": "AngularJS Super-Powered"
},
"web_accessible_resources": [
"dist/hint.js"
],
"minimum_chrome_version": "21.0.1180.57"
}

@ -1,13 +1,41 @@
{
"name": "angularjs-batarang",
"version": "0.7.4",
"description": "chrome extension for inspecting angular apps",
"main": "hint.js",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.1.1",
"grunt-conventional-changelog": "~0.1.2",
"grunt-karma": "~0.4.3",
"grunt-release": "https://github.com/btford/grunt-release/archive/feat-tag-name.tar.gz",
"marked": "~0.2.8",
"grunt-zip": "~0.7.0",
"semver": "~1.1.4"
}
"angular-mocks": "^1.3.6",
"gulp-zip": "^2.0.2",
"karma-bro": "^0.6.0",
"karma-chrome-launcher": "^0.1.4",
"karma-sauce-launcher": "^0.2.9",
"karma-jasmine": "^0.1.5"
},
"dependencies": {
"angular": "^1.3.6",
"angular-hint": "~0.0.0",
"browserify": "^5.9.1",
"gulp": "^3.8.7",
"vinyl-source-stream": "^0.1.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "gulp"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angularjs-batarang.git"
},
"keywords": [
"angular",
"angularjs",
"chrome",
"extension"
],
"author": "Brian Ford <btford@umich.edu>",
"license": "MIT",
"bugs": {
"url": "https://github.com/angular/angularjs-batarang/issues"
},
"homepage": "https://github.com/angular/angularjs-batarang"
}

@ -1,55 +0,0 @@
<!doctype html>
<html ng-csp ng-app="panelApp">
<head>
<link rel="stylesheet" href="css/bootstrap.css">
<link rel="stylesheet" href="css/bootstrap-responsive.css">
<link rel="stylesheet" href="css/d3.css">
<link rel="stylesheet" href="css/panel.css">
<!-- libs -->
<script src="js/lib/angular.js"></script>
<script src="js/lib/jquery-1.7.2.min.js"></script>
<script src="js/lib/jquery-ui-1.8.21.custom.min.js"></script>
<script src="js/lib/d3.js"></script>
<script src="js/lib/d3.layout.js"></script>
<script src="js/panelApp.js"></script>
<script src="js/directives/d3.js"></script>
<script src="js/directives/jsonTree.js"></script>
<script src="js/directives/scopeTree.js"></script>
<script src="js/directives/slider.js"></script>
<script src="js/directives/tabs.js"></script>
<script src="js/directives/watcherTree.js"></script>
<script src="js/filters/first.js"></script>
<script src="js/filters/precision.js"></script>
<script src="js/filters/sortByTime.js"></script>
<script src="js/services/appContext.js"></script>
<script src="js/services/appCss.js"></script>
<script src="js/services/appDeps.js"></script>
<script src="js/services/appHighlight.js"></script>
<script src="js/services/appInfo.js"></script>
<script src="js/services/appModel.js"></script>
<script src="js/services/appPerf.js"></script>
<script src="js/services/appWatch.js"></script>
<script src="js/services/chromeExtension.js"></script>
<script src="js/services/filesystem.js"></script>
<script src="js/controllers/DepsCtrl.js"></script>
<script src="js/controllers/ModelCtrl.js"></script>
<script src="js/controllers/OptionsCtrl.js"></script>
<script src="js/controllers/PerfCtrl.js"></script>
</head>
<body>
<bat-tabs>
<bat-pane title="Models" src="panes/model.html"></bat-pane>
<bat-pane title="Performance" src="panes/perf.html"></bat-pane>
<bat-pane title="Dependencies" src="panes/deps.html"></bat-pane>
<bat-pane title="Options" src="panes/options.html"></bat-pane>
<bat-pane title="Help" src="panes/help.html"></bat-pane>
</bat-tabs>
</body>
</html>

@ -0,0 +1,305 @@
.col {
float: left;
width: 200px;
}
.col-2 {
float: left;
width: 400px;
}
.scope-branch {
margin-left: 30px;
background-color: rgba(0,0,0,0.06);
}
.well-top {
border-radius: 4px 4px 0 0;
margin-bottom: 0;
}
.well-bottom {
border-radius: 0 0 4px 4px;
border-top: none;
background-color: #E0E0E0;
}
.bat-nav-check {
background-color: #fff;
border: 1px solid #ddd;
border-bottom-color: transparent;
border-radius: 4px 4px 0 0;
padding: 8px 12px 8px 12px;
margin-right: 2px;
line-height: 18px;
}
.bat-nav-check input[type="checkbox"] {
margin: 0;
}
/* Mimic Chrome's Devtools */
/* split */
.split-view {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
.outline-disclosure,
.outline-disclosure ol {
list-style-type: none;
-webkit-padding-start: 12px;
margin: 0;
}
.split-view-vertical > .split-view-contents-first {
left: 0;
}
.split-view-vertical > .split-view-contents {
top: 0;
bottom: 0;
}
.split-view-contents {
position: absolute;
overflow: auto;
cursor: default;
}
.split-view-vertical > .split-view-sidebar.split-view-contents-second:not(.maximized) {
border-left: 1px solid rgb(64%, 64%, 64%);
}
.sidebar-pane-stack > .sidebar-pane.visible:nth-last-of-type(1) {
border-bottom: 1px solid rgb(189, 189, 189);
}
.split-view-vertical > .split-view-contents-second {
right: 0;
}
.split-view-vertical > .split-view-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 5px;
z-index: 1500;
cursor: ew-resize;
}
.fill {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.sidebar-pane-title {
position: relative;
background: rgb(230, 230, 230);
height: 20px;
padding: 0 5px;
border-top: 1px solid rgb(189, 189, 189);
border-bottom: 1px solid rgb(189, 189, 189);
line-height: 18px;
background-origin: padding;
background-clip: padding;
margin-top: -1px;
}
.sidebar-pane-title::before {
background-image: url(../img/statusbarButtonGlyphs.png);
background-size: 320px 144px;
background-position: -4px -96px;
opacity: 0.5;
float: left;
width: 11px;
height: 11px;
margin-right: 2px;
content: "a";
color: transparent;
position: relative;
top: 3px;
}
.sidebar-pane-title.expanded::before {
background-position: -20px -96px;
}
.sidebar-pane-toolbar {
line-height: 18px;
left: 0;
right: 4px;
top: 0;
height: 20px;
position: absolute;
pointer-events: none;
}
.sidebar-pane-toolbar > * {
pointer-events: auto;
}
.sidebar-pane-subtitle {
position: absolute;
right: 0;
}
.sidebar-pane-subtitle input, .section > .header input[type=checkbox] {
font-size: inherit;
height: 1em;
width: 1em;
margin-left: 0;
margin-top: 0;
margin-bottom: 0.25em;
vertical-align: bottom;
}
/* sidebar tree */
.sidebar-tree,
.sidebar-tree .children {
position: relative;
padding: 0;
margin: 0;
list-style: none;
}
.sidebar-tree-item {
position: relative;
height: 36px;
padding: 0 5px 0 5px;
white-space: nowrap;
overflow-x: hidden;
overflow-y: hidden;
margin-top: 1px;
line-height: 34px;
border-top: 1px solid transparent;
}
.sidebar-tree-item.selected {
color: white;
border-top: 1px solid rgb(151, 151, 151);
background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(180, 180, 180)), to(rgb(138, 138, 138)));
text-shadow: rgba(0, 0, 0, 0.33) 1px 1px 0;
background-origin: padding-box;
background-clip: padding-box;
}
body:focus .sidebar-tree-item.selected {
border-top: 1px solid rgb(68, 128, 200);
background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(92, 147, 213)), to(rgb(21, 83, 170)));
}
.sidebar-tree-item .icon {
float: left;
width: 32px;
height: 32px;
margin-top: 1px;
margin-right: 3px;
}
.sidebar-tree-item .titles {
position: relative;
top: 5px;
line-height: 12px;
padding-bottom: 1px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.sidebar-tree-item .titles.no-subtitle {
top: 10px;
}
.sidebar {
background-color: rgb(232, 232, 232);
}
.split-view-vertical > .split-view-sidebar.split-view-contents-first:not(.maximized) {
border-right: 1px solid rgb(64%, 64%, 64%);
}
.profile-launcher-view-tree-item > .icon {
padding: 15px;
background-image: url(../img/toolbarIcons.png);
background-position-x: -160px;
}
li .status {
float: right;
height: 16px;
margin-top: 9px;
margin-left: 4px;
line-height: 1em;
}
li .status:empty {
display: none;
}
/* audit stuff */
.audit-result-view .severity-warning {
background-position: -246px -96px;
}
/*@media (-webkit-min-device-pixel-ratio: 1.5)
.audit-result-view .severity-severe,
.audit-result-view .severity-warning,
.audit-result-view .severity-info {
background-image: url(../img/statusbarButtonGlyphs_2x.png);
}*/
.audit-result-tree,
.audit-result-tree ol {
list-style-type: none;
-webkit-padding-start: 12px;
margin: 0;
}
.audit-result-tree {
line-height: 16px;
-webkit-user-select: text;
}
.audit-result-tree li.parent {
margin-left: -12px;
}
.audit-result-tree li {
padding: 0 0 0 14px;
margin-top: 1px;
margin-bottom: 1px;
word-wrap: break-word;
margin-left: -2px;
}
.audit-result {
font-weight: bold;
}
.audit-result-tree li.parent::before {
background-position: -4px -96px;
}
.audit-result-view .severity-severe,
.audit-result-view .severity-warning,
.audit-result-view .severity-info {
background-image: url(../img/statusbarButtonGlyphs.png);
background-size: 320px 144px;
display: inline-block;
width: 10px;
margin-right: -10px;
height: 10px;
position: relative;
left: -28px;
margin-top: 3px;
}

@ -0,0 +1,32 @@
<!doctype html>
<html ng-csp ng-app="batarang.app">
<head>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="app.css">
<link rel="stylesheet" href="components/json-tree/json-tree.css">
<link rel="stylesheet" href="components/scope-tree/scope-tree.css">
<script src="../node_modules/angular/angular.js"></script>
<!-- components -->
<script src="components/inspected-app/inspected-app.js"></script>
<script src="components/code/code.js"></script>
<script src="components/json-tree/json-tree.js"></script>
<script src="components/scope-tree/scope-tree.js"></script>
<script src="components/tabs/tabs.js"></script>
<script src="components/vertical-split/vertical-split.js"></script>
<!-- panes -->
<script src="hints/hints.js"></script>
<script src="scopes/scopes.js"></script>
<script src="app.js"></script>
</head>
<body>
<bat-tabs>
<bat-pane title="Scopes" src="scopes/scopes.html"></bat-pane>
<bat-pane title="Hints" src="hints/hints.html"></bat-pane>
</bat-tabs>
</body>
</html>

@ -0,0 +1,16 @@
'use strict';
angular.module('batarang.app', [
'batarang.app.hint',
'batarang.app.scopes',
'batarang.scope-tree',
'batarang.code',
'batarang.inspected-app',
'batarang.json-tree',
'batarang.scope-tree',
'batarang.tabs',
'batarang.vertical-split'
]).
// immediately instantiate this service
run(['inspectedApp', angular.noop]);

@ -0,0 +1,28 @@
'use strict';
angular.module('batarang.code', []).
directive('batCode', function() {
return {
restrict: 'A',
terminal: true,
scope: {
batCode: '='
},
link: function (scope, element, attrs) {
scope.$watch('batCode', function (newVal) {
if (newVal) {
element.html(replaceCodeInString(newVal));
}
});
}
};
});
// super lite version of markdown
var CODE_RE = /\`(.+?)\`/g;
function replaceCodeInString(str) {
return str.replace(CODE_RE, function (match, contents) {
return ['<code>', contents, '</code>'].join('');
});
}

@ -0,0 +1,131 @@
'use strict';
angular.module('batarang.inspected-app', []).
service('inspectedApp', ['$rootScope', '$q', inspectedAppService]);
function inspectedAppService($rootScope, $q) {
var scopes = this.scopes = {},
hints = this.hints = [];
this.watch = function (scopeId, path) {
return invokeAngularHintMethod('watch', scopeId, path);
};
this.unwatch = function (scopeId, path) {
return invokeAngularHintMethod('unwatch', scopeId, path);
};
this.assign = function (scopeId, path, value) {
return invokeAngularHintMethod('assign', scopeId, path, value);
};
this.enableInstrumentation = function (setting) {
setting = !!setting;
chrome.devtools.inspectedWindow.eval(
"(function () {" +
"var prev = document.cookie.indexOf('__ngDebug=true') !== -1;" +
"if (prev !== " + setting + ") {" +
"window.document.cookie = '__ngDebug=" + setting + ";';" +
"window.document.location.reload();" +
"}" +
"}())"
);
};
this.getInstrumentationStatus = function () {
return $q(function(resolve, reject) {
chrome.devtools.inspectedWindow.eval(
"document.cookie.indexOf('__ngDebug=true') !== -1", resolve);
});
};
/*
* sets window.$scope to the scope of the given id
*/
this.inspectScope = function (scopeId) {
return invokeAngularHintMethod('inspectScope', scopeId);
};
function invokeAngularHintMethod(method, scopeId, path, value) {
var args = [parseInt(scopeId, 10), path || ''].
map(JSON.stringify).
concat(value ? [value] : []).
join(',');
chrome.devtools.inspectedWindow.eval('angular.hint.' + method + '(' + args + ')');
}
var port = chrome.extension.connect();
port.postMessage(chrome.devtools.inspectedWindow.tabId);
port.onMessage.addListener(function(msg) {
$rootScope.$applyAsync(function () {
if (msg === 'refresh') {
onRefreshMessage();
$rootScope.$broadcast('refresh');
} else if (typeof msg === 'string') {
var hint = JSON.parse(msg);
onHintMessage(hint);
} else if (typeof msg === 'object') {
onHintMessage(msg);
}
});
});
port.onDisconnect.addListener(function (a) {
console.log(a);
});
function onHintMessage(hint) {
if (hint.message) {
hints.push(hint);
} else if (hint.event) {
if (hint.event === 'hydrate') {
Object.keys(hint.data.scopes).forEach(function (scopeId) {
scopes[scopeId] = hint.data.scopes[scopeId];
});
hint.data.hints.forEach(function (hint) {
hints.push(hint);
});
} else if (hint.event === 'scope:new') {
addNewScope(hint);
} else if (hint.id && scopes[hint.id]) {
var scope = scopes[hint.id];
if (hint.event === 'scope:destroy') {
if (scope.parent) {
scope.parent.children.splice(scope.parent.children.indexOf(child), 1);
}
delete scopes[hint.id];
} else if (hint.event === 'model:change') {
scope.models[hint.path] = (typeof hint.value === 'undefined') ?
undefined : JSON.parse(hint.value);
} else if (hint.event === 'scope:link') {
scope.descriptor = hint.descriptor;
}
}
$rootScope.$broadcast(hint.event, hint);
}
}
function onRefreshMessage() {
clear(scopes);
hints.length = 0;
}
function addNewScope (hint) {
scopes[hint.child] = {
parent: hint.parent,
children: [],
models: {}
};
if (scopes[hint.parent]) {
scopes[hint.parent].children.push(hint.child);
}
}
function clear (obj) {
Object.keys(obj).forEach(function (key) {
delete obj[key];
});
}
}

@ -0,0 +1,111 @@
'use strict';
describe('inspectedApp', function() {
var inspectedApp, port;
beforeEach(function() {
module('batarang.inspected-app')
window.chrome = createMockChrome();
inject(function(_inspectedApp_) {
inspectedApp = _inspectedApp_;
});
});
describe('when instantiated', function () {
it('should post a message with the inspected tabId', function () {
expect(port.postMessage).
toHaveBeenCalledWith(window.chrome.devtools.inspectedWindow.tabId);
});
});
describe('messaging', function () {
it('should track hints', inject(function ($browser) {
port.onMessage.trigger(JSON.stringify({ message: 'hi' }));
$browser.defer.flush();
expect(inspectedApp.hints).toEqual([{message: 'hi'}]);
}));
it('should track new scopes', inject(function ($browser) {
port.onMessage.trigger(JSON.stringify({ event: 'scope:new', child: 1 }));
$browser.defer.flush();
expect(inspectedApp.scopes).toEqual({ 1: { parent: undefined, children: [], models: {} } });
}));
it('should track updates to scope descriptors', inject(function ($browser) {
port.onMessage.trigger(JSON.stringify({ event: 'scope:new', child: 1 }));
port.onMessage.trigger(JSON.stringify({ event: 'scope:link', id: 1, descriptor: 'pasta' }));
$browser.defer.flush();
expect(inspectedApp.scopes[1].descriptor).toBe('pasta');
}));
})
describe('watch', function () {
it('should call chrome devtools APIs', function() {
inspectedApp.watch(1, '');
expect(chrome.devtools.inspectedWindow.eval).toHaveBeenCalledWith('angular.hint.watch(1,"")');
});
});
describe('unwatch', function () {
it('should call chrome devtools APIs', function() {
inspectedApp.unwatch(1, '');
expect(chrome.devtools.inspectedWindow.eval).toHaveBeenCalledWith('angular.hint.unwatch(1,"")');
});
});
describe('inspectScope', function () {
it('should call chrome devtools APIs', function() {
inspectedApp.inspectScope(2);
expect(chrome.devtools.inspectedWindow.eval).toHaveBeenCalledWith('angular.hint.inspectScope(2,"")');
});
});
function createMockChrome() {
return {
extension: {
connect: function () {
return port = createMockSocket();
}
},
devtools: {
inspectedWindow: {
tabId: 1,
eval: jasmine.createSpy('inspectedWindowEval')
}
}
};
}
});
function createListenerSpy(name) {
var symbol = '_' + name;
var listener = {
addListener: function (fn) {
listener[symbol].push(fn);
},
removeListener: function (fn) {
listener[symbol].splice(fn, 1);
},
trigger: function () {
var args = arguments;
listener[symbol].forEach(function (fn) {
fn.apply(listener, args);
});
}
};
listener[symbol] = [];
return listener;
}
function createMockSocket() {
return {
onMessage: createListenerSpy('messageFunction'),
postMessage: jasmine.createSpy('postMessageFunction'),
onDisconnect: createListenerSpy('onDisconnect')
};
}

@ -0,0 +1,76 @@
/* bat-json-tree */
bat-json-tree {
font-size: 11px !important;
font-family: Menlo, monospace;
display: block;
margin-left: 5px;
}
bat-json-tree .name {
color: rgb(136, 19, 145);
}
bat-json-tree .console-formatted-string {
color: rgb(196, 26, 22);
}
bat-json-tree
.console-formatted-null,
.console-formatted-undefined {
color: rgb(128, 128, 128);
}
bat-json-tree .console-formatted-number,
.console-formatted-boolean {
color: rgb(28, 0, 207);
}
bat-json-tree
.console-formatted-object,
.console-formatted-node,
.console-formatted-array {
color: #222;
}
bat-json-tree .properties-tree li {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
-webkit-user-select: text;
cursor: default;
padding-top: 2px;
line-height: 12px;
list-style: none;
}
bat-json-tree .properties-tree li.parent {
margin-left: -15px;
}
bat-json-tree .properties-tree li.parent li {
padding-left: 28px;
}
bat-json-tree .properties-tree li.parent::before {
-webkit-user-select: none;
background-image: url(../../img/statusbarButtonGlyphs.png);
background-size: 320px 120px;
opacity: 0.5;
content: "a";
width: 8px;
float: left;
margin-right: 2px;
color: transparent;
text-shadow: none;
margin-top: -2px;
}
bat-json-tree .properties-tree li.parent::before {
background-position: -4px -96px;
}
bat-json-tree .properties-tree li.parent.expanded::before {
background-position: -20px -96px;
}

@ -0,0 +1,188 @@
angular.module('batarang.json-tree', []).
directive('batJsonTree', [batJsonTreeDirective]);
var BAT_JSON_TREE_TEMPLATE = '<div class="properties-tree"></div>';
var ENTER_KEY = 13,
EXIT_KEY = 27;
var BAT_JSON_TREE_UNEDITABLE = [
'$id',
// managed by ngRepeat
'$first',
'$last',
'$index',
'$even',
'$odd'
];
/*
* TODO: remove dependency on inspectedApp service
*/
function batJsonTreeDirective() {
return {
restrict: 'E',
terminal: true,
scope: {
batInspect: '&',
batAssign: '&',
batModel: '='
},
link: jsonTreeLinkFn
};
function jsonTreeLinkFn(scope, element, attrs) {
var root = angular.element(BAT_JSON_TREE_TEMPLATE);
element.append(root);
var branches = {
'': root
};
scope.$watch('batModel', function (val) {
if (!val) {
root = angular.element(BAT_JSON_TREE_TEMPLATE);
element.html('');
element.append(root);
branches = {
'': root
};
return;
}
Object.
keys(val).
filter(function (key) {
return key.substr(0, 2) !== '$$';
}).
sort(byPathDepth).
forEach(function (key) {
buildDom(val[key], key);
});
}, true);
function buildDom(object, depth) {
branches[depth].html('');
if (!typeof object === 'undefined') {
return;
}
var buildBranch = function (key) {
var val = object[key];
var fullPath = depth;
if (depth) {
if (Number.isNaN(parseInt(key, 10))) {
fullPath += '.' + key;
} else {
fullPath += '[' + key + ']';
}
} else {
fullPath += key;
}
var parentElt = angular.element('<li title>' +
'<span class="name">' + key + '</span>' +
'<span class="separator">: </span>' +
'</li>'),
childElt;
if (val === null) {
childElt = angular.element('<span class="value console-formatted-null">null</span>');
} else if (val['~object'] || val['~array-length'] !== undefined) {
parentElt.addClass('parent');
// you can't expand an empty array
if (val['~array-length'] !== 0) {
parentElt.on('click', function () {
scope.batInspect({ path: fullPath });
parentElt.addClass('expanded');
});
}
if (val['~object']) {
childElt = angular.element('<span class="console-formatted-object">Object</span>');
} else {
childElt = angular.element(
'<span class="console-formatted-object">Array[' +
val['~array-length'] +
']</span>');
}
} else {
// TODO: what doe sregex look like?
if (typeof val === 'string') {
val = '"' + val + '"';
}
// TODO: test this
// some properties (like $id) shouldn't be edited
if (BAT_JSON_TREE_UNEDITABLE.indexOf(fullPath) > -1) {
childElt = angular.element(
'<span class="console-formatted-' + (typeof val) + '">' +
val +
'</span>');
} else {
childElt = angular.element(
'<span contentEditable="true" class="console-formatted-' + (typeof val) + '">' +
val +
'</span>');
// TODO: test this
childElt.on('keydown', function (ev) {
if (ev.keyCode === ENTER_KEY || ev.keyCode === EXIT_KEY) {
ev.preventDefault();
childElt[0].blur();
doAssign();
}
});
// TODO: test this
childElt.on('blur', doAssign);
function doAssign() {
scope.batAssign({
path: fullPath,
value: childElt.text()
});
}
}
}
parentElt.append(childElt);
branches[fullPath] = childElt;
return parentElt;
};
var properties;
if (object instanceof Array) {
properties = object.map(function (item, i) {
return i;
});
} else if (object != null) {
properties = Object.keys(object);
} else {
properties = [];
}
properties.
map(buildBranch).
forEach(function (elt) {
branches[depth].append(elt);
});
};
}
}
function byPathDepth(a, b) { // sort '' first
if (a === '') {
return -1;
} else if (b === '') {
return 1;
} else { // sort by tree depth
return a.split('.').length - b.split('.').length;
}
}

@ -0,0 +1,33 @@
'use strict';
describe('batJsonTree', function () {
var $compile, $rootScope, element;
beforeEach(module('batarang.json-tree'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should not throw on an undefined model', function () {
expect(compileTree).not.toThrow();
});
it('should render a simple model', function () {
$rootScope.data = {
'': { '$id': 1 }
};
compileTree();
expect(element.text()).toBe('$id: 1');
});
function compileTree() {
element = compile('<bat-json-tree bat-model="data"></bat-json-tree>');
$rootScope.$apply();
}
function compile(template) {
return $compile(template)($rootScope);
}
});

@ -0,0 +1,130 @@
/* Stolen from WebKit Inspector CSS */
.source-code {
font-size: 11px !important;
font-family: Menlo, monospace;
}
.source-code li {
display: list-item;
text-align: -webkit-match-parent;
}
.outline-disclosure,
.outline-disclosure ol {
list-style-type: none;
-webkit-padding-start: 12px;
margin: 0;
}
.outline-disclosure ol.children.expanded {
display: block;
}
.outline-disclosure > ol {
position: relative;
padding: 2px 6px !important;
margin: 0;
cursor: default;
min-width: 100%;
}
.outline-disclosure li {
padding: 0 0 0 14px;
margin-top: 1px;
margin-left: -2px;
word-wrap: break-word;
}
.outline-disclosure li.selected .selection {
display: block;
background-color: rgb(212, 212, 212);
}
.elements-tree-outline li.parent::before {
top: 0 !important;
}
.outline-disclosure li.parent::before {
-webkit-user-select: none;
background-image: url(../../img/statusbarButtonGlyphs.png);
background-size: 320px 144px;
opacity: 0.5;
float: left;
width: 8px;
height: 10px;
content: "a";
color: transparent;
margin-left: 3px;
margin-right: 4px;
position: relative;
top: 2px;
}
.outline-disclosure li.parent::before {
float: left;
width: 8px;
padding-right: 2px;
}
.webkit-html-tag {
color: rgb(136, 18, 128);
}
.webkit-html-attribute-name {
color: rgb(153, 69, 0);
}
.webkit-html-attribute-value {
color: rgb(26, 26, 166);
}
.webkit-html-doctype {
color: rgb(192, 192, 192);
}
.webkit-html-comment {
color: rgb(35, 110, 37);
}
.outline-disclosure .selection {
display: none;
position: absolute;
left: 0;
right: 0;
height: 13px;
z-index: -1;
}
.outline-disclosure .selection:not(.selected):hover {
display: block;
left: 3px;
right: 3px;
background-color: rgba(56, 121, 217, 0.1);
border-radius: 5px;
}
.outline-disclosure .selection.selected {
display: block;
background-color: rgb(212, 212, 212);
}
:focus .sidebar-tree-item.selected {
border-top: 1px solid rgb(68, 128, 200);
background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(92, 147, 213)), to(rgb(21, 83, 170)));
}
/* */
bat-scope-tree {
white-space: nowrap;
}
/*
bat-scope-tree .selected {
font-weight: bold;
text-decoration: underline;
color: #333;
}
*/

@ -0,0 +1,102 @@
angular.module('batarang.scope-tree', []).
directive('batScopeTree', ['$compile', batScopeTreeDirective]);
function batScopeTreeDirective($compile) {
return {
restrict: 'E',
terminal: true,
scope: {
batModel: '='
},
link: batScopeTreeLink
};
function batScopeTreeLink(scope, element, attrs) {
// scope.$id > DOM node
var map = {};
var selectedElt = angular.element();
// init
var scopes = scope.batModel;
if (scopes) {
Object.keys(scopes).forEach(function (scopeId) {
var parentId = scopes[scopeId].parent;
renderScopeElement(scopeId, parentId);
renderScopeDescriptorElement(scopeId, scopes[scopeId].descriptor);
});
}
scope.$on('scope:new', function (ev, data) {
renderScopeElement(data.child, data.parent);
});
// when a scope is linked, we can apply the descriptor info
scope.$on('scope:link', function (ev, data) {
renderScopeDescriptorElement(data.id, data.descriptor);
});
scope.$on('refresh', function () {
map = {};
element.html('');
});
function renderScopeElement (id, parentId) {
if (map[id]) {
return;
}
var elt = map[id] = newBranchElement(id);
var parentElt = map[parentId] || element;
elt.children().eq(1).on('click', function () {
scope.$apply(function () {
scope.$emit('inspected-scope:change', {
id: id
});
selectedElt.children().eq(0).removeClass('selected');
selectedElt.children().eq(1).removeClass('selected');
selectedElt = elt;
selectedElt.children().eq(0).addClass('selected');
selectedElt.children().eq(1).addClass('selected');
});
});
parentElt.append(elt);
}
function renderScopeDescriptorElement (id, descriptor) {
var elt = map[id];
if (!elt) {
return;
}
elt.children().eq(1).children().eq(1).html(descriptor);
}
// TODO: also destroy elements corresponding to descendant scopes
scope.$on('scope:destroy', function (ev, data) {
var id = data.id;
var elt = map[id];
if (elt) {
elt.remove();
}
delete map[id];
});
}
}
// TODO: tabindex
function newBranchElement(descriptor) {
return angular.element([
'<ol class="children expanded">',
'<div class="selection"></div>',
'<span>',
'<span class="webkit-html-tag">&lt;</span>',
'<span class="webkit-html-attribute">Scope #', descriptor, '</span>',
'<span class="webkit-html-tag">&gt;</span>',
'</span>',
'</ol>'].join(''));
}

@ -0,0 +1,49 @@
<div class="split-view split-view-vertical visible">
<div
class="split-view-contents
scroll-target
split-view-contents-first
split-view-sidebar
sidebar"
style="width: 200px;">
<ol class="sidebar-tree" tabindex="0">
<li class="sidebar-tree-item
profile-launcher-view-tree-item">
<img class="icon">
<div class="status"></div>
<div class="titles no-subtitle">
<span class="title ng-binding">Enable <input type="checkbox" ng-model="enabled"></span>
<span class="subtitle"></span>
</div>
</li>
<li ng-repeat="pane in panes"
ng-click="select(pane)"
class="sidebar-tree-item
profile-launcher-view-tree-item"
ng-class="{selected:pane.selected}">
<img class="icon">
<div class="status"></div>
<div class="titles no-subtitle">
<span class="title">{{pane.title}}</span>
<span class="subtitle"></span>
</div>
</li>
</ol>
</div>
<div
class="split-view-contents
scroll-target
split-view-contents-second
outline-disclosure
bat-tabs-inside"
style="left: 200px;">
</div>
<div ng-transclude></div>
</div>

@ -1,52 +1,23 @@
angular.module('panelApp').directive('batTabs', function ($compile, $templateCache, $http) {
angular.module('batarang.tabs', []).
directive('batTabs', function ($compile, $templateCache, $http, inspectedApp) {
return {
restrict: 'E',
transclude: true,
scope: {},
template:
'<div class="container-fluid">' +
'<div class="row-fluid">' +
'<ul class="nav nav-tabs span12">' +
'<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+
'<a href="" ng-click="select(pane)">{{pane.title}}</a>' +
'</li>' +
'<li>' +
'<div class="bat-nav-check">' +
'<input type="checkbox" ng-model="enable" id="enable-instrumentation"> ' +
'Enable' +
'</div>' +
'</li>' +
templateUrl: 'components/tabs/tabs.html',
'</ul>' +
'</div>' +
'<div class="row-fluid bat-tabs-inside"></div>' +
'<div ng-transclude></div>' +
'</div>',
replace: true,
controller: function ($scope, appContext) {
controller: function ($scope) {
var panes = $scope.panes = [];
this.addPane = function(pane) {
panes.push(pane);
};
appContext.getDebug(function (result) {
$scope.enable = result;
$scope.$watch('enable', function (newVal, oldVal) {
appContext.setDebug(newVal);
if (!newVal) {
$scope.lastPane = $scope.currentPane;
$scope.select($scope.panes[$scope.panes.length - 1]);
} else {
$scope.select($scope.lastPane);
}
});
if (result) {
$scope.select($scope.panes[0]);
}
inspectedApp.getInstrumentationStatus().then(function (status) {
$scope.enabled = status;
$scope.$watch('enabled', inspectedApp.enableInstrumentation);
});
},
link: function (scope, element, attr) {
@ -62,9 +33,6 @@ angular.module('panelApp').directive('batTabs', function ($compile, $templateCac
}
scope.select = function (pane) {
if (!scope.enable && pane !== scope.panes[scope.panes.length - 1]) {
return;
}
$http.get(pane.src, { cache: $templateCache }).
then(function (response) {
var template = response.data;

@ -0,0 +1,87 @@
angular.module('batarang.vertical-split', []).
constant('defaultSplit', 360).
directive('batVerticalSplit', function ($document, defaultSplit) {
var classes = [
'split-view',
'split-view-vertical',
'visible'
];
var body = angular.element($document[0].body);
return {
restrict: 'A',
compile: function (element) {
classes.forEach(element.addClass.bind(element));
var children = element.children();
var left = angular.element(children[0]);
var right = angular.element(children[1]);
return function (scope, element, attr) {
var slider = angular.element('<div class="split-view-resizer" style="right: ' + defaultSplit + 'px; margin-right: -2.5px;"></div>');
var drag = function (ev) {
var x = $document[0].body.clientWidth - ev.x;
left.css('right', x + 'px');
right.css('width', x + 'px');
slider.css('right', x + 'px');
};
var oldCursor;
slider.bind('mousedown', function (ev) {
drag(ev);
oldCursor = body.css('cursor');
body.css('cursor', 'ew-resize');
$document.bind('mousemove', drag);
$document.bind('mouseup', stopDrag);
});
var stopDrag = function () {
body.css('cursor', oldCursor);
$document.unbind('mousemove', drag);
$document.unbind('mouseup', stopDrag);
};
element.append(slider);
};
}
};
}).
directive('batVerticalLeft', function (defaultSplit) {
var classes = [
'split-view-contents',
'scroll-target',
'split-view-contents-first',
'outline-disclosure'
];
return {
require: '^batVerticalSplit',
restrict: 'A',
compile: function (element) {
classes.forEach(element.addClass.bind(element));
element.css('right', defaultSplit + 'px');
}
};
}).
directive('batVerticalRight', function (defaultSplit) {
var classes = [
'split-view-contents',
'scroll-target',
'split-view-contents-second',
'split-view-sidebar'
];
return {
require: '^batVerticalSplit',
restrict: 'A',
compile: function (element) {
classes.forEach(element.addClass.bind(element));
element.css('width', defaultSplit + 'px');
}
};
});

@ -0,0 +1,28 @@
<div class="sidebar-pane-stack audit-result-view fill visible" ng-controller="HintController">
<div></div>
<div class="sidebar-pane-title expanded"
ng-repeat-start="(groupName, group) in groupedHints">{{groupName}}
<div class="sidebar-pane-toolbar"></div>
</div>
<div class="sidebar-pane visible"
ng-repeat-end
ng-repeat="(summary, hints) in group">
<div class="body audit-result-tree">
<ol>
<li class="parent audit-result">
<div class="severity-warning"></div>{{summary}} ({{hints.length}})
</li>
<li class="parent-expanded expanded selected">
Controller names should start with an uppercase character and end with the suffix <code>Controller</code>. For example: <code>UserController</code>.
</li>
<ol class="children expanded">
<li class="selected" ng-repeat="hint in hints" bat-code="hint.message"></li>
</ol>
</ol>
</div>
</div>
</div>

@ -0,0 +1,24 @@
'use strict';
angular.module('batarang.app.hint', []).
controller('HintController', ['$scope', 'inspectedApp', HintController]);
function HintController($scope, inspectedApp) {
$scope.$watch(function () {
return inspectedApp.hints.length;
}, function () {
var newHints = inspectedApp.hints;
$scope.groupedHints = {};
newHints.forEach(function (hint) {
var moduleName = hint.module || 'Hints';
var category = hint.category || moduleName;
if (!$scope.groupedHints[moduleName]) {
$scope.groupedHints[moduleName] = {};
}
if (!$scope.groupedHints[moduleName][category]) {
$scope.groupedHints[moduleName][category] = [];
}
$scope.groupedHints[moduleName][category].push(hint);
});
});
}

@ -0,0 +1,26 @@
/* reset */
* {
box-sizing: border-box;
}
/* TODO: defaults for other platforms?? */
body {
cursor: default;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
font-size: 12px;
margin: 0;
tab-size: 4;
-webkit-user-select: none;
color: rgb(48, 57, 66);
font-family: 'Lucida Grande', sans-serif;
}
img {
-webkit-user-drag: none;
}

@ -0,0 +1,43 @@
<div bat-vertical-split ng-controller="ScopesController">
<div bat-vertical-left class="source-code">
<bat-scope-tree bat-model="scopes"></bat-scope-tree>
</div>
<div bat-vertical-right>
<div class="sidebar-pane-stack fill visible">
<div class="sidebar-pane-title"
ng-class="{expanded: modelsExpanded}"
ng-click="modelsExpanded = !modelsExpanded">Models</div>
<div class="sidebar-pane visible" ng-if="modelsExpanded">
<div class="body">
<div class="section expanded">
<bat-json-tree
bat-model="scopes[inspectedScope].models"
bat-inspect="inspect(path)"
bat-assign="assign(path, value)"></bat-json-tree>
</div>
</div>
</div>
<!--
<div class="sidebar-pane-title"
ng-class="{expanded: watchExpanded}"
ng-click="watchExpanded = !watchExpanded">Watchers</div>
<div class="sidebar-pane visible" ng-if="watchExpanded">
<div class="body">
<div class="section expanded">
<div class="watcher-list">
<li ng-repeat="watcher in watchers">{{watcher}}</li>
</div>
</div>
</div>
</div> -->
</div>
</div>
</div>

@ -0,0 +1,42 @@
'use strict';
angular.module('batarang.app.scopes', []).
controller('ScopesController', ['$scope', 'inspectedApp', ScopesController]);
function ScopesController($scope, inspectedApp) {
$scope.scopes = inspectedApp.scopes;
$scope.watch = inspectedApp.watch;
$scope.inspect = function (path) {
inspectedApp.watch($scope.inspectedScope, path);
};
$scope.assign = function (path, value) {
inspectedApp.assign($scope.inspectedScope, path, value);
};
$scope.inspectedScope = null;
$scope.$on('refresh', function () {
$scope.inspectedScope = null;
});
// expand models the fist time we inspect a scope
var cancelWatch = $scope.$watch('inspectedScope', function (newScope) {
if (newScope) {
$scope.modelsExpanded = true;
cancelWatch();
}
});
$scope.$on('inspected-scope:change', function (ev, data) {
inspectScope(data.id);
});
function inspectScope(scopeId) {
$scope.watch(scopeId);
$scope.inspectedScope = scopeId;
inspectedApp.inspectScope(scopeId);
};
}

@ -1,8 +0,0 @@
<div ng-controller="DepsCtrl">
<div class="span12">
<h2>Service Dependencies</h2>
<div class="well">
<bat-d3 val="deps"></bat-d3>
</div>
</div>
</div>

@ -1,23 +0,0 @@
<p>In order to begin using the Batarang you need to click the &quot;enable&quot; checkbox. This will cause the application&#39;s tab to refresh, and the Batarang to begin collecting perfomance and debug information about the inspected app.</p>
<p>The Batarang has five tabs: Model, Performance, Dependencies, Options, and Help.</p>
<h3>Models</h3>
<p><img src="/img/models.png" alt="Batarang screenshot"></p>
<p>Starting at the top of this tab, there is the root selection. If the application has only one <code>ng-app</code> declaration (as most applications do) then you will not see the option to change roots.</p>
<p>Below that is a tree showing how scopes are nested, and which models are attached to them. Clicking on a scope name will take you to the Elements tab, and show you the DOM element associated with that scope. Models and methods attached to each scope are listed with bullet points on the tree. Just the name of methods attached to a scope are shown. Models with a simple value and complex objects are shown as JSON. You can edit either, and the changes will be reflected in the application being debugged.</p>
<h3>Performance</h3>
<p><img src="/img/perf.png" alt="Batarang performance tab screenshot"></p>
<p>The performance tab must be enabled separately because it causes code to be injected into AngularJS to track and report performance metrics. There is also an option to output performance metrics to the console.</p>
<p>Below that is a tree of watched expressions, showing which expressions are attached to which scopes. Much like the model tree, you can collapse sections by clicking on &quot;toggle&quot; and you can inspect the element that a scope is attached to by clicking on the scope name.</p>
<p>Underneath that is a graph showing the relative performance of all of the application&#39;s expressions. This graph will update as you interact with the application.</p>
<h3>Dependencies</h3>
<p><img src="/img/deps.png" alt="Batarang dependencies tab screenshot"></p>
<p>The dependencies tab shows a visualization of the application&#39;s dependencies. When you hover over a service name, services that depend on the hovered service turn green, and those the hovered service depend on turn red.</p>
<h3>Options</h3>
<p><img src="/img/options.png" alt="Batarang options tab screenshot"></p>
<p>Last, there is the options tab. The options tab has three checkboxes: one for &quot;show applications,&quot; &quot;show scopes,&quot; and &quot;show bindings.&quot; Each of these options, when enabled, highlights the respective feature of the application being debugged; scopes will have a red outline, and bindings will have a blue outline, and applications a green outline.</p>
<h3>Elements</h3>
<p><img src="/img/inspect.png" alt="Batarang console screenshot"></p>
<p>The Batarang also hooks into some of the existing features of the Chrome developer tools. For AngularJS applications, there is now a properties pane on in the Elements tab. Much like the model tree in the AngularJS tab, you can use this to inspect the models attached to a given element&#39;s scope.</p>
<h3>Console</h3>
<p><img src="/img/console.png" alt="Batarang console screenshot"></p>
<p>The Batarang exposes some convenient features to the Chrome developer tools console. To access the scope of an element selected in the Elements tab of the developer tools, in console, you can type <code>$scope</code>. If you change value of some model on <code>$scope</code> and want to have this change reflected in the running application, you need to call <code>$scope.$apply()</code> after making the change.</p>

@ -1,29 +0,0 @@
<div ng-controller="ModelCtrl">
<div class="span6">
<h2>Scopes</h2>
<div ng-show="roots.length > 1">
<label for="select-root">Root
<select
ng-options="root.toString() for root in roots"
ng-model="selectedRoot"></select>
</label>
</div>
<pre>
<bat-scope-tree
val="tree"
inspect="inspect"
select="select"
selected-scope="selectedScope"
edit="edit"></bat-scope-tree>
</pre>
</div>
<div class="span6">
<h2>Models<span ng-show="selectedScope"> for ({{selectedScope}})</span></h2>
<bat-json-tree val="model"></bat-json-tree>
</div>
<div class="span6">
<button class="btn" ng-click="enableInspector()">Enable Inspector</button>
</div>
</div>

@ -1,30 +0,0 @@
<div ng-controller="OptionsCtrl">
<div class="span6">
<h2>Options</h2>
<form class="well">
<label class="checkbox" for="app">
<input type="checkbox" ng-model="debugger.app" id="app"> Show applications
</label>
<label class="checkbox" for="bindings">
<input type="checkbox" ng-model="debugger.bindings" id="bindings"> Show bindings
</label>
<label class="checkbox" for="scopes">
<input type="checkbox" ng-model="debugger.scopes" id="scopes"> Show scopes
</label>
</form>
</div>
<div class="span6">
<h2>Info</h2>
<div class="well">
<p>Angular version: {{version}}</p>
<p>Angular CDN status: <span class="label" ng-class="'label-' + status" ng-bind-html-unsafe="explain"></span></p>
</div>
</div>
</div>

@ -1,48 +0,0 @@
<div ng-controller="PerfCtrl">
<h2>Performance</h2>
<form class="well form-inline" class="row-fluid">
<label class="checkbox span4" for="log">
<input type="checkbox" ng-model="log" id="log"> Log to console
</label>
</form>
<div class="row-fluid">
<div class="span6">
<h3>Watch Tree</h3>
<div class="well well-top" style="height: 400px; overflow-y: auto;">
<bat-watcher-tree val="tree" inspect="inspect"></bat-watcher-tree>
</div>
<div class="well well-bottom">
<label for="select-root" ng-hide="roots.length <= 1">Root
<select id="select-root" ng-options="p for p in roots" ng-model="selectedRoot"></select>
</label>
</div>
</div>
<div class="span6">
<h3>Watch Expressions</h3>
<div class="well well-top" style="height: 400px; overflow-y: auto;">
<div ng-repeat="watch in histogram|sortByTime:min:max">
<span style="font-family: monospace;">{{watch.name | first}} </span>
<span> | {{watch.percent}}% | {{watch.time | precision}}ms</span>
<div class="progress">
<div ng-style="{width: (watch.percent) + '%'}" class= "bar">
</div>
</div>
</div>
</div>
<div class="well well-bottom">
<form class="form-inline">
<label>Filter expressions</label>
<bat-slider minimum="min" maximum="max"></bat-slider>
</form>
<button class="btn btn-success" ng-click="exportData()"><i class="icon-download-alt icon-white"></i> Save Data as JSON</button>
<button class="btn btn-danger" ng-click="clearHistogram()">Clear Data</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,29 @@
#!/bin/bash
#
# Switch a dependency to git repo.
# Remove the NPM package and link it to a repo in parent directory.
DEP_NAME=$1
SCRIPT_DIR=$(dirname $0)
cd $SCRIPT_DIR/..
if [ -L ./node_modules/$DEP_NAME ]; then
echo "$DEP_NAME is already a symlink"
else
PKG_INFO=($($SCRIPT_DIR/read-pkg-url.js ./node_modules/$DEP_NAME/package.json))
URL=${PKG_INFO[0]}
DIR_NAME=${PKG_INFO[1]}
echo "Switching $DEP_NAME"
rm -rf ./node_modules/$DEP_NAME
if [ -d ../$DIR_NAME ]; then
echo "Repo already cloned in ../$DIR_NAME"
else
cd ..
git clone $URL $DIR_NAME
cd -
fi
echo "Link ./node_modules/$DEP_NAME -> ../$DIR_NAME"
ln -s ../../$DIR_NAME ./node_modules/$DEP_NAME
fi

@ -0,0 +1,15 @@
#!/bin/bash
#
# Switch a dependency to NPM.
# Remove the symlink and install from NPM.
DEP_NAME=$1
SCRIPT_DIR=$(dirname $0)
cd $SCRIPT_DIR/..
if [ ! -L ./node_modules/$DEP_NAME ]; then
echo "$DEP_NAME is not a symlink"
else
rm ./node_modules/$DEP_NAME
npm install $DEP_NAME
fi

@ -0,0 +1,11 @@
#!/bin/bash
LOG_FILES=$LOGS_DIR/*
for FILE in $LOG_FILES; do
echo -e "\n\n\n"
echo "=================================================================="
echo " $FILE"
echo "=================================================================="
cat $FILE
done

Some files were not shown because too many files have changed in this diff Show More