├── ng-inspector.firefox
├── .jpmignore
├── data
│ ├── btn19.png
│ ├── btn38.png
│ ├── icon16.png
│ ├── icon48.png
│ ├── icon128.png
│ ├── processing.png
│ └── inject.js
├── package.json
└── lib
│ └── main.js
├── gulpfile.js
├── logo.png
├── screenshot.png
├── src
├── icons
│ ├── array.png
│ ├── caret.png
│ ├── date.png
│ ├── model.png
│ ├── null.png
│ ├── scope.png
│ ├── boolean.png
│ ├── element.png
│ ├── number.png
│ ├── object.png
│ ├── promise.png
│ ├── string.png
│ ├── circular.png
│ ├── function.png
│ ├── undefined.png
│ └── isolate-scope.png
├── artwork
│ ├── ui.sketch
│ ├── icons.sketch
│ └── toolbar-button.sketch
├── js
│ ├── Highlighter.js
│ ├── Module.js
│ ├── Utils.js
│ ├── ModelMixin.js
│ ├── Inspector.js
│ ├── bootstrap.js
│ ├── Model.js
│ ├── Scope.js
│ ├── Service.js
│ ├── InspectorAgent.js
│ ├── App.js
│ ├── TreeView.js
│ └── InspectorPane.js
└── less
│ └── stylesheet.less
├── gulp
├── tasks
│ ├── default.js
│ ├── watch.js
│ ├── build-safari.js
│ ├── scenarios.js
│ ├── build-icons.js
│ ├── newtest.js
│ ├── build-css.js
│ ├── build-js.js
│ ├── e2e.js
│ └── bump.js
└── config.js
├── ng-inspector.chrome
├── btn19.png
├── btn38.png
├── empty.png
├── icon128.png
├── icon16.png
├── icon48.png
├── processing.png
├── btn19-disabled.png
├── btn38-disabled.png
├── background.js
├── inject.js
└── manifest.json
├── .jshintrc
├── ng-inspector.safariextension
├── ng.png
├── empty.png
├── Icon-32.png
├── Icon-48.png
├── Icon-64.png
├── ng-disabled.png
├── processing.png
├── Settings.plist
├── global.html
├── inject-end.js
└── Info.plist
├── test
└── e2e
│ ├── helpers
│ ├── getAngularVersion.js
│ ├── preparePage.js
│ └── buildCapabilities.js
│ ├── tests
│ ├── requirejs
│ │ ├── main.js
│ │ ├── app.js
│ │ ├── bootstrap.js
│ │ ├── controllers.js
│ │ ├── index.html
│ │ └── index.js
│ ├── circular-reference
│ │ ├── index.js
│ │ └── index.html
│ ├── bootstrap-jquery
│ │ ├── index.js
│ │ └── index.html
│ ├── anon
│ │ ├── index.html
│ │ └── index.js
│ ├── dependency
│ │ ├── index.js
│ │ └── index.html
│ ├── base-template.html
│ ├── empty-directive
│ │ ├── index.html
│ │ └── index.js
│ ├── strict-di
│ │ ├── index.html
│ │ └── index.js
│ ├── directive-object-declaration
│ │ ├── index.html
│ │ └── index.js
│ └── collapse-expand
│ │ ├── index.html
│ │ └── index.js
│ ├── boilerplate-test
│ ├── index.js
│ └── index.html
│ ├── protractor.conf.js
│ ├── scenario-server
│ ├── directory.html
│ └── index.js
│ ├── angular-versions.conf.js
│ └── lib
│ ├── requirejs.min.js
│ └── angular
│ └── 1.0.6.min.js
├── .travis.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── package.json
└── CONTRIBUTING.md
/ng-inspector.firefox/.jpmignore:
--------------------------------------------------------------------------------
1 | *.xpi
2 | tmp/
3 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | require('require-dir')('./gulp/tasks');
2 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/logo.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/screenshot.png
--------------------------------------------------------------------------------
/src/icons/array.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/array.png
--------------------------------------------------------------------------------
/src/icons/caret.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/caret.png
--------------------------------------------------------------------------------
/src/icons/date.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/date.png
--------------------------------------------------------------------------------
/src/icons/model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/model.png
--------------------------------------------------------------------------------
/src/icons/null.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/null.png
--------------------------------------------------------------------------------
/src/icons/scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/scope.png
--------------------------------------------------------------------------------
/src/artwork/ui.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/artwork/ui.sketch
--------------------------------------------------------------------------------
/src/icons/boolean.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/boolean.png
--------------------------------------------------------------------------------
/src/icons/element.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/element.png
--------------------------------------------------------------------------------
/src/icons/number.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/number.png
--------------------------------------------------------------------------------
/src/icons/object.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/object.png
--------------------------------------------------------------------------------
/src/icons/promise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/promise.png
--------------------------------------------------------------------------------
/src/icons/string.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/string.png
--------------------------------------------------------------------------------
/gulp/tasks/default.js:
--------------------------------------------------------------------------------
1 | require('gulp').task('default', ['build:icons', 'build:js', 'build:css']);
2 |
--------------------------------------------------------------------------------
/src/artwork/icons.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/artwork/icons.sketch
--------------------------------------------------------------------------------
/src/icons/circular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/circular.png
--------------------------------------------------------------------------------
/src/icons/function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/function.png
--------------------------------------------------------------------------------
/src/icons/undefined.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/undefined.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/btn19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/btn19.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/btn38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/btn38.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/empty.png
--------------------------------------------------------------------------------
/src/icons/isolate-scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/icons/isolate-scope.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/icon128.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/icon16.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/icon48.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/processing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/processing.png
--------------------------------------------------------------------------------
/src/artwork/toolbar-button.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/src/artwork/toolbar-button.sketch
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "strict": false,
3 | "expr": true,
4 | "boss": true,
5 | "shadow": true,
6 | "predef": ["console"]
7 | }
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/btn19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.firefox/data/btn19.png
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/btn38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.firefox/data/btn38.png
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.firefox/data/icon16.png
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.firefox/data/icon48.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/ng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/ng.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/btn19-disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/btn19-disabled.png
--------------------------------------------------------------------------------
/ng-inspector.chrome/btn38-disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.chrome/btn38-disabled.png
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.firefox/data/icon128.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/empty.png
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/processing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.firefox/data/processing.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/Icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/Icon-32.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/Icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/Icon-48.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/Icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/Icon-64.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/ng-disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/ng-disabled.png
--------------------------------------------------------------------------------
/ng-inspector.safariextension/processing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rev087/ng-inspector/HEAD/ng-inspector.safariextension/processing.png
--------------------------------------------------------------------------------
/test/e2e/helpers/getAngularVersion.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | return browser.getProcessedConfig().then(function(config) {
3 | return config.capabilities.ngVersion;
4 | });
5 | };
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | sudo: false
5 |
6 | env:
7 | global:
8 | - SAUCE_USERNAME: rev087
9 | - SAUCE_ACCESS_KEY: 8247a039-7c68-484e-ae27-a8f2ef94c47f
10 |
11 | addons:
12 | sauce_connect: true
13 |
--------------------------------------------------------------------------------
/ng-inspector.chrome/background.js:
--------------------------------------------------------------------------------
1 | chrome.browserAction.onClicked.addListener(function(tab) {
2 | var message = {
3 | command: 'ngi-toggle'
4 | };
5 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
6 | chrome.tabs.sendMessage(tabs[0].id, message);
7 | });
8 | });
--------------------------------------------------------------------------------
/test/e2e/tests/requirejs/main.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | noGlobal: true,
3 | baseUrl: '/tests/requirejs',
4 | paths: {
5 | 'angular': window.angularLibPath
6 | },
7 | shim: {
8 | 'angular': {
9 | exports: 'angular'
10 | }
11 | },
12 | deps: ['bootstrap'] // depend on (and load) bootstrap.js
13 | });
14 |
--------------------------------------------------------------------------------
/test/e2e/boilerplate-test/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('!TEST_NAME!');
2 |
3 | describe('suite name', function() {
4 |
5 | // Navigate to test page and open ng-inspector
6 | beforeEach(preparePage);
7 |
8 | it('should pass', function() {
9 | expect(true).toBe(true);
10 | });
11 |
12 | });
13 |
--------------------------------------------------------------------------------
/gulp/tasks/watch.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var format = require('util').format;
3 | var config = require('../config');
4 |
5 | gulp.task('watch', function() {
6 | gulp.watch(format('%s/*.png', config.iconsDir), ['build:icons']);
7 | gulp.watch(format('%s/*.js', config.jsDir), ['build:js']);
8 | gulp.watch(format('%s/*.less', config.lessDir), ['build:css']);
9 | });
--------------------------------------------------------------------------------
/test/e2e/tests/circular-reference/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('circular-reference');
2 |
3 | describe('detect circular references', function() {
4 |
5 | beforeEach(preparePage);
6 |
7 | it('should detect a circular reference', function() {
8 | expect($$('.ngi-model.ngi-model-circular').count()).toBe(1);
9 | });
10 |
11 | });
12 |
--------------------------------------------------------------------------------
/gulp/tasks/build-safari.js:
--------------------------------------------------------------------------------
1 | // Here would be a good place to build the Safari archive. But it requires a
2 | // custom build of the `xar` executable to extract certificates from a
3 | // Safari-built .safariextz, then signing the .xar archive (renamed .safariextz)
4 |
5 | // Steps to build from the command line:
6 | // http://developer.streak.com/2013/01/how-to-build-safari-extension-using.html
7 |
--------------------------------------------------------------------------------
/test/e2e/tests/bootstrap-jquery/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('bootstrap-jquery');
2 |
3 | describe('bootstrap with a jquery object', function() {
4 |
5 | beforeEach(preparePage);
6 |
7 | it('should inspect the directive in a dependency', function() {
8 | expect($('.ngi-inspector .ngi-value').getText()).toBe('"John Doe"');
9 | });
10 |
11 | });
12 |
--------------------------------------------------------------------------------
/test/e2e/helpers/preparePage.js:
--------------------------------------------------------------------------------
1 | var getAngularVersion = require('../helpers/getAngularVersion')();
2 |
3 | module.exports = function(testName) {
4 | return function(done) {
5 | getAngularVersion.then(function(version) {
6 | browser.get([testName, version].join('/'));
7 | element(by.id('ngInspectorToggle')).click().then(done);
8 | });
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/gulp/tasks/scenarios.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var format = require('util').format;
3 | var config = require('../config');
4 | var scenarioServer = require('../../test/e2e/scenario-server');
5 |
6 | // Pass --noEmbed or -n to supress the embeding of ng-inspector to use the
7 | // scenarios with the extension itself.
8 | gulp.task('scenarios', function() {
9 | scenarioServer(3000);
10 | });
11 |
--------------------------------------------------------------------------------
/test/e2e/tests/anon/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Hello {{yourName}}!
6 |
7 |
8 |
This test uses is an anonymous, auto-bootstrapped ngApp directive.
9 |
10 |
11 |
--------------------------------------------------------------------------------
/gulp/tasks/build-icons.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var config = require('../config');
3 | var format = require('util').format;
4 |
5 | gulp.task('build:icons', function() {
6 | return gulp.src([format('%s/*.png', config.iconsDir)])
7 | .pipe(gulp.dest(format('%s/icons/', config.safariDir)))
8 | .pipe(gulp.dest(format('%s/icons/', config.chromeDir)))
9 | .pipe(gulp.dest(format('%s/data/icons/', config.firefoxDir)));
10 | });
--------------------------------------------------------------------------------
/gulp/config.js:
--------------------------------------------------------------------------------
1 | var format = require('util').format;
2 |
3 | var testDir = 'test';
4 |
5 | module.exports = {
6 | lessDir: 'src/less',
7 | safariDir: 'ng-inspector.safariextension',
8 | firefoxDir: 'ng-inspector.firefox',
9 | chromeDir: 'ng-inspector.chrome',
10 | iconsDir: 'src/icons',
11 | jsDir: 'src/js',
12 | browserifyEntry: 'src/js/bootstrap.js',
13 | jsOutputName: 'ng-inspector.js',
14 | testDir: testDir,
15 | e2eDir: format('%s/e2e', testDir)
16 | };
--------------------------------------------------------------------------------
/test/e2e/tests/dependency/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('dependency');
2 |
3 | describe('dependency', function() {
4 |
5 | beforeEach(preparePage);
6 |
7 | it('should inspect the directive in a dependency', function() {
8 | expect($$('.ngi-scope').count()).toBe(2);
9 | expect($$('.ngi-scope .ngi-annotation-dir').count()).toBe(1);
10 | expect($('.ngi-scope .ngi-annotation-dir').getText()).toBe('sayHi');
11 | });
12 |
13 | });
14 |
--------------------------------------------------------------------------------
/test/e2e/tests/requirejs/app.js:
--------------------------------------------------------------------------------
1 | define([
2 | // This RequireJS module depends on the 'angular'
3 | // RequireJS module already defined in main.js, and the
4 | // 'controllers' RequireJS module defined in controllers.js
5 | 'angular',
6 | 'controllers'
7 | ], function (angular) {
8 | 'use strict';
9 | // Define the emtpy Angular app
10 | return angular.module('DemoApp', [
11 | // Angular module dependencies
12 | 'app.controllers'
13 | ]);
14 | });
15 |
--------------------------------------------------------------------------------
/test/e2e/tests/requirejs/bootstrap.js:
--------------------------------------------------------------------------------
1 | // Bootstrap angular onto the window.document node.
2 | // Must do this rather than defining the ng-app directive
3 | // so that RequireJS is able to first load dependencies.
4 | define([
5 | 'require',
6 | 'angular',
7 | 'app'
8 | ], function (require, angular) {
9 | 'use strict';
10 | angular.element(document).ready(function() {
11 | angular.bootstrap(document.querySelector('.angular-root-element'), ['DemoApp']);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/ng-inspector.safariextension/Settings.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | DefaultValue
7 |
8 | Key
9 | showWarnings
10 | Title
11 | Show warnings
12 | Type
13 | CheckBox
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/e2e/tests/requirejs/controllers.js:
--------------------------------------------------------------------------------
1 | define([
2 | // This RequireJS module depends on the 'angular'
3 | // RequireJS module already defined in main.js
4 | 'angular'
5 | ], function (angular) {
6 | // This RequireJS module depends on the 'angular'
7 | // RequireJS module defined in main.js
8 | 'use strict';
9 | return angular.module('app.controllers', [])
10 | .controller('DemoCtrl', ['$scope', function ($scope) {
11 | $scope.foo = 'value initially set by DemoCtrl';
12 | }]);
13 | });
14 |
--------------------------------------------------------------------------------
/ng-inspector.safariextension/global.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ng-inspector
6 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/e2e/tests/circular-reference/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
Hello {{username}}
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 |
5 | # Generated files
6 |
7 | ng-inspector.firefox/*.xpi
8 | ng-inspector.firefox/data/icons/
9 | ng-inspector.firefox/data/ng-inspector.js
10 | ng-inspector.firefox/data/stylesheet.css
11 |
12 | ng-inspector.safariextension/icons/
13 | ng-inspector.safariextension/ng-inspector.js
14 | ng-inspector.safariextension/stylesheet.css
15 |
16 | ng-inspector.chrome/icons/
17 | ng-inspector.chrome/ng-inspector.js
18 | ng-inspector.chrome/stylesheet.css
19 |
20 | test/e2e/lib/ng-inspector.js
21 | test/e2e/lib/stylesheet.css
--------------------------------------------------------------------------------
/gulp/tasks/newtest.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var replace = require('gulp-replace');
3 | var argv = require('yargs').argv;
4 | var format = require('util').format;
5 | var config = require('../config');
6 |
7 | gulp.task('newtest', function() {
8 | var testName = argv.name;
9 | if (!testName) throw new Error('Test name required. Use --name');
10 |
11 | gulp.src([format('%s/boilerplate-test/**', config.e2eDir)])
12 | .pipe(replace(/!TEST_NAME!/, testName))
13 | .pipe(gulp.dest(format('%s/tests/%s', config.e2eDir, testName)));
14 | });
15 |
--------------------------------------------------------------------------------
/test/e2e/boilerplate-test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
Hello {{sample.username}}!
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/e2e/tests/base-template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Scenario: <%= scenario %>
6 |
10 | <% if (!noEmbed) { %>
11 |
12 |
13 | <% } %>
14 |
15 |
16 | <%- include(scenarioPath); %>
17 |
18 |
--------------------------------------------------------------------------------
/test/e2e/tests/dependency/index.html:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
Hello
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/e2e/tests/empty-directive/index.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
Hello {{name}}!
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/ng-inspector.firefox/data/inject.js:
--------------------------------------------------------------------------------
1 | if (window.top === window) {
2 | // Inject the bridge script
3 | var inspectorScript = document.createElement('script');
4 | inspectorScript.type = 'text/javascript';
5 | inspectorScript.src = self.options.ngInspectorURL;
6 | document.head.appendChild(inspectorScript);
7 |
8 | // In Firefox, we use this thing
9 |
10 | self.port.on("ngi-command", function(message) {
11 | if (message.command && message.command === 'ngi-toggle') {
12 | document.defaultView.postMessage(JSON.stringify(message), window.location.origin);
13 | }
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/test/e2e/tests/bootstrap-jquery/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
Hello {{username}}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/ng-inspector.chrome/inject.js:
--------------------------------------------------------------------------------
1 | if (window.top === window) {
2 |
3 | // Inject the bridge script
4 | var inspectorScript = document.createElement('script');
5 | inspectorScript.type = 'text/javascript';
6 | inspectorScript.src = chrome.extension.getURL('/ng-inspector.js');
7 | document.head.appendChild(inspectorScript);
8 |
9 | // In Chrome, we use this thing
10 | if ('chrome' in window) {
11 | chrome.runtime.onMessage.addListener(function(message, sender) {
12 | if (message.command && message.command === 'ngi-toggle') {
13 | window.postMessage(JSON.stringify(message), window.location.origin);
14 | }
15 | });
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/test/e2e/tests/anon/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('anon');
2 |
3 | describe('anonymous app', function() {
4 |
5 | beforeEach(preparePage);
6 |
7 | var ROOT_ELEMENT = 'div.angular-root-element';
8 |
9 | it('should inspect the anonymous app', function() {
10 | expect($$('.ngi-app').count()).toBe(1);
11 | expect($('.ngi-app > label').getText()).toBe(ROOT_ELEMENT + '.ng-scope');
12 | });
13 |
14 | it('should inspect the root scope', function() {
15 | expect($$('.ngi-scope').count()).toBe(1);
16 | expect($('.ngi-scope .ngi-annotation-builtin').getText()).toBe('$rootScope');
17 | });
18 |
19 | });
20 |
--------------------------------------------------------------------------------
/test/e2e/tests/strict-di/index.html:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
Hello {{name}}!
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/e2e/tests/strict-di/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('strict-di');
2 |
3 | describe('strict di', function() {
4 |
5 | beforeEach(preparePage);
6 |
7 | it('should not throw an error when unannotated directive is used', function() {
8 | browser.manage().logs().get('browser').then(function (browserLog) {
9 | var severeWarnings = browserLog.filter(function(log) {
10 | return log.level.name === 'SEVERE';
11 | });
12 |
13 | if (!severeWarnings.length) return;
14 |
15 | severeWarnings.forEach(function(log) {
16 | expect(log.message).toNotMatch(/strictdi/);
17 | });
18 | });
19 | });
20 |
21 | });
--------------------------------------------------------------------------------
/test/e2e/tests/requirejs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ng-inspector.safariextension/inject-end.js:
--------------------------------------------------------------------------------
1 | if (window.top === window) {
2 |
3 | // Inject the bridge script
4 | var inspectorScript = document.createElement('script');
5 | inspectorScript.type = 'text/javascript';
6 | inspectorScript.src = safari.extension.baseURI + 'ng-inspector.js';
7 | document.head.appendChild(inspectorScript);
8 |
9 | // Forward the toggle event
10 | safari.self.addEventListener('message', function(event) {
11 | if ( event.name == 'toggle-inspector') {
12 | var message = {
13 | command: 'ngi-toggle',
14 | settings: event.message
15 | };
16 | window.postMessage(JSON.stringify(message), window.location.origin);
17 | }
18 | }, false);
19 |
20 | }
--------------------------------------------------------------------------------
/test/e2e/tests/requirejs/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('requirejs');
2 |
3 | describe('require.js', function() {
4 |
5 | var ROOT_ELEMENT = 'div.angular-root-element';
6 |
7 | beforeEach(preparePage);
8 |
9 | it('should inspect app loaded with require.js', function() {
10 | expect($('.ngi-app > label').getText()).toBe(ROOT_ELEMENT);
11 | expect($$('.ngi-scope').count()).toBe(2);
12 | expect($('.ngi-scope .ngi-annotation-builtin').getText()).toBe('$rootScope');
13 | expect($('.ngi-scope .ngi-annotation-ctrl').getText()).toBe('DemoCtrl');
14 | expect($('.ngi-model .ngi-indicator').getText()).toBe('31');
15 | expect($('#foo').getAttribute('value')).toBe('value initially set by DemoCtrl');
16 | });
17 |
18 | });
--------------------------------------------------------------------------------
/test/e2e/tests/directive-object-declaration/index.html:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/gulp/tasks/build-css.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var less = require('gulp-less');
3 | var replace = require('gulp-replace');
4 | var format = require('util').format;
5 | var config = require('../config');
6 |
7 | gulp.task('build:css', function() {
8 | return gulp.src([format('%s/stylesheet.less', config.lessDir)])
9 | .pipe(less())
10 | .pipe(gulp.dest(format('%s/', config.safariDir)))
11 | .pipe(gulp.dest(format('%s/data/', config.firefoxDir)))
12 | .pipe(replace(/url\(/g, 'url(chrome-extension://__MSG_@@extension_id__/')) // Add path prefix for Chrome
13 | .pipe(gulp.dest(format('%s/', config.chromeDir)))
14 | .pipe(replace(/(\s*)(.+url\(.+)/g, '$1/* $2 */$1background-image: none !important;')) // Remove images from the test build
15 | .pipe(gulp.dest(format('%s/lib/', config.e2eDir)));
16 | });
--------------------------------------------------------------------------------
/gulp/tasks/build-js.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify');
2 | var gulp = require('gulp');
3 | var source = require('vinyl-source-stream');
4 | var buffer = require('vinyl-buffer');
5 | var gutil = require('gulp-util');
6 | var format = require('util').format;
7 | var config = require('../config');
8 |
9 | gulp.task('build:js', function () {
10 | var b = browserify({
11 | entries: config.browserifyEntry,
12 | debug: true
13 | });
14 |
15 | return b.bundle()
16 | .pipe(source(config.jsOutputName))
17 | .pipe(buffer())
18 | .on('error', gutil.log)
19 | .pipe(gulp.dest(format('%s/', config.safariDir)))
20 | .pipe(gulp.dest(format('%s/', config.chromeDir)))
21 | .pipe(gulp.dest(format('%s/data/', config.firefoxDir)))
22 | .pipe(gulp.dest(format('%s/lib/', config.e2eDir)));
23 | });
--------------------------------------------------------------------------------
/test/e2e/tests/empty-directive/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('empty-directive');
2 |
3 | describe('empty directive', function() {
4 |
5 | var _warn, _warnMessage;
6 |
7 | beforeEach(preparePage);
8 |
9 | it('should not throw exception when directive with empty DDO is used', function() {
10 | browser.manage().logs().get('browser').then(function (browserLog) {
11 | var severeWarnings = browserLog.filter(function(log) {
12 | // Not concerned about info or warnings
13 | return log.level.name === 'SEVERE';
14 | });
15 |
16 | severeWarnings.forEach(function(warning) {
17 | // https://github.com/rev087/ng-inspector/issues/39
18 | expect(warning.message).toNotMatch(/Cannot read property 'restrict' of undefined/i);
19 | });
20 | });
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/test/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | var angularVersionsConfig = require('./angular-versions.conf');
2 | var buildCapabilities = require('./helpers/buildCapabilities')(angularVersionsConfig);
3 |
4 | exports.config = {
5 | sauceUser: process.env.SAUCE_USERNAME,
6 |
7 | sauceKey: process.env.SAUCE_ACCESS_KEY,
8 |
9 | allScriptsTimeout: 11000,
10 |
11 | rootElement: 'div.angular-root-element',
12 |
13 | // Default specs for all angular versions
14 | // Include/Exclude additional tests for specific
15 | // versions in angular-versions.conf.js
16 | specs: [
17 | 'tests/**/index.js'
18 | ],
19 |
20 | getMultiCapabilities: buildCapabilities,
21 |
22 | baseUrl: 'http://localhost:3000/app/',
23 |
24 | jasmineNodeOpts: {
25 | showColors: true,
26 | defaultTimeoutInterval: 30000
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/test/e2e/tests/collapse-expand/index.html:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
This scenario tests the expand/collapse functionality for scopes, array models and object models.
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/test/e2e/scenario-server/directory.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Index
6 |
7 |
8 | Scenario Server - Index
9 |
10 |
Angular Versions
11 |
12 | <% Object.keys(angularVersions).forEach(function(version) { %>
13 | -
14 | <%= version %>
15 |
16 | <% }) %>
17 |
18 |
19 |
20 |
Scenarios (<%= angularVersion %>)
21 |
22 | <% testNames.forEach(function(testName) { %>
23 | -
24 | <%= testName %>
25 |
26 | <% }) %>
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/js/Highlighter.js:
--------------------------------------------------------------------------------
1 | function Highlighter() {}
2 |
3 | function offsets(node) {
4 | var vals = {
5 | x: node.offsetLeft,
6 | y: node.offsetTop,
7 | w: node.offsetWidth,
8 | h: node.offsetHeight
9 | };
10 | while (node = node.offsetParent) {
11 | vals.x += node.offsetLeft;
12 | vals.y += node.offsetTop;
13 | }
14 | return vals;
15 | }
16 |
17 | var hls = [];
18 | Highlighter.hl = function(node, label) {
19 | var box = document.createElement('div');
20 | box.className = 'ngi-hl ngi-hl-scope';
21 | if (label) {
22 | box.textContent = label;
23 | }
24 | var pos = offsets(node);
25 | box.style.left = pos.x + 'px';
26 | box.style.top = pos.y + 'px';
27 | box.style.width = pos.w + 'px';
28 | box.style.height = pos.h + 'px';
29 | document.body.appendChild(box);
30 | hls.push(box);
31 | return box;
32 | };
33 |
34 | Highlighter.clear = function() {
35 | var box;
36 | while (box = hls.pop()) {
37 | box.parentNode.removeChild(box);
38 | }
39 | };
40 |
41 | module.exports = Highlighter;
42 |
--------------------------------------------------------------------------------
/ng-inspector.chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "ng-inspector for AngularJS",
4 | "description": "Inspector pane for AngularJS apps",
5 | "version": "0.5.11",
6 | "background": {
7 | "scripts": [
8 | "background.js"
9 | ],
10 | "persistent": false
11 | },
12 | "permissions": [
13 | "tabs",
14 | ""
15 | ],
16 | "icons": {
17 | "16": "icon16.png",
18 | "48": "icon48.png",
19 | "128": "icon128.png"
20 | },
21 | "browser_action": {
22 | "default_icon": {
23 | "19": "btn19.png",
24 | "38": "btn38.png"
25 | },
26 | "default_title": "ng-inspector"
27 | },
28 | "content_scripts": [
29 | {
30 | "matches": [
31 | "*://*/*",
32 | "file:///*"
33 | ],
34 | "css": [
35 | "stylesheet.css"
36 | ],
37 | "js": [
38 | "inject.js"
39 | ]
40 | }
41 | ],
42 | "web_accessible_resources": [
43 | "ng-inspector.js",
44 | "processing.png",
45 | "icons/*"
46 | ]
47 | }
--------------------------------------------------------------------------------
/test/e2e/angular-versions.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | defaults: {
3 | browsers: [
4 | 'chrome',
5 | 'firefox'
6 | ],
7 | extraCapabilities: {
8 | 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
9 | }
10 | },
11 | versions: {
12 | "1.3.0": {
13 | path: "/lib/angular/1.3.0.min.js",
14 | additionalSpecs: [],
15 | excludeSpecs: []
16 | },
17 | "1.2.0": {
18 | path: "/lib/angular/1.2.0.min.js",
19 | additionalSpecs: [],
20 | excludeSpecs: []
21 | },
22 | "1.1.4": {
23 | path: "/lib/angular/1.1.4.min.js",
24 | additionalSpecs: [],
25 | excludeSpecs: []
26 | },
27 | "1.0.6": {
28 | path: "/lib/angular/1.0.6.min.js",
29 | additionalSpecs: [],
30 | excludeSpecs: [
31 | // No manual bootstrapping feature in 1.0.6
32 | "tests/requirejs/index.js"
33 | ]
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Bruno Daniel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 
4 |
5 | __ng-inspector__ is a browser extension for Chrome and Safari that displays an inspector panel showing the AngularJS scope hierarchy in the current page in real time, as well as which controllers or directives are associated with which scope.
6 |
7 | Hovering over a scope in the inspector will highlight the DOM element that scope is attached to. Clicking on a model will console.log that model's contents.
8 |
9 | The extension adds a button next to the address bar with the AngularJS logo that toggles the pane on and off.
10 |
11 | 
12 |
13 | ## Installing
14 |
15 | ### Chrome
16 |
17 | Install it from the [Chrome Web Store](https://chrome.google.com/webstore/detail/ng-inspector/aadgmnobpdmgmigaicncghmmoeflnamj)
18 |
19 | ### Safari
20 |
21 | Download the latest build from [ng-inspector.org](http://ng-inspector.org), then double click the `ng-inspector.safariextz` file to install.
22 |
23 | ## License
24 |
25 | The MIT License (MIT)
26 |
27 | See [LICENSE.md](LICENSE.md) for details.
28 |
--------------------------------------------------------------------------------
/src/js/Module.js:
--------------------------------------------------------------------------------
1 | var NGI = {
2 | Service: require('./Service')
3 | };
4 |
5 | function Module(app, name) {
6 |
7 | // The AngularJS module name
8 | this.name = name;
9 |
10 | // Array with `NGI.Module` instance references
11 | this.requires = [];
12 |
13 | // The AngularJS module instance
14 | this.ngModule = window.angular.module(name);
15 |
16 | // `NGI.Service` instances representing services defined in this module
17 | this.services = NGI.Service.parseQueue(app, this.ngModule);
18 | }
19 |
20 | // A cache with all NGI.Module instances
21 | var moduleCache = [];
22 |
23 | Module.register = function(app, name) {
24 | // Ensure only a single `NGI.Module` instance exists for each AngularJS
25 | // module name
26 | if (typeof name === typeof '' && !moduleCache[name]) {
27 | moduleCache[name] = new Module(app, name);
28 |
29 | // Register the dependencies
30 | var requires = moduleCache[name].ngModule.requires;
31 | for (var i = 0; i < requires.length; i++) {
32 | var dependency = Module.register(app, requires[i]);
33 | moduleCache[name].requires.push(dependency);
34 | }
35 | }
36 |
37 | return moduleCache[name];
38 | };
39 |
40 | module.exports = Module;
41 |
--------------------------------------------------------------------------------
/ng-inspector.firefox/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-inspector",
3 | "license": "MIT",
4 | "description": "Inspector pane for AngularJS apps",
5 | "author": "Bruno Daniel",
6 | "contributors": [
7 | "Bruno Daniel",
8 | "Luca Greco (ALCA Societa' Cooperativa)"
9 | ],
10 | "title": "ng-inspector for AngularJS",
11 | "version": "0.5.11",
12 | "engines": {
13 | "firefox": ">=38 <=*"
14 | },
15 | "id": "firefox-addon@ng-inspector.org",
16 | "icon": "data/icon128.png",
17 | "main": "lib/main.js",
18 | "firefox-devtools": {
19 | "page-mods": [
20 | {
21 | "include": [
22 | "*"
23 | ],
24 | "contentStyleFiles": [
25 | "./stylesheet.css"
26 | ],
27 | "contentScriptFiles": [
28 | "./inject.js"
29 | ]
30 | }
31 | ],
32 | "toolbar-buttons": [
33 | {
34 | "label": "ng-inspector",
35 | "icons": {
36 | "19": "./btn19.png",
37 | "38": "./btn38.png"
38 | }
39 | }
40 | ]
41 | }
42 | }
--------------------------------------------------------------------------------
/test/e2e/helpers/buildCapabilities.js:
--------------------------------------------------------------------------------
1 | var format = require('util').format;
2 | var objAssign = require('object-assign');
3 |
4 | var BUILD_NUMBER = process.env.TRAVIS_BUILD_NUMBER;
5 |
6 | function capabilityFromConfig(version, config) {
7 | return config.browsers.map(function(browser) {
8 | return objAssign({
9 | browserName: browser,
10 | ngVersion: version,
11 | name: getFriendlyName(version),
12 | specs: config.additionalSpecs,
13 | exclude: config.excludeSpecs
14 | }, config.extraCapabilities || {});
15 | });
16 | };
17 |
18 | function getFriendlyName(version) {
19 | var buildNumString = BUILD_NUMBER ? format(' - Build: %s', BUILD_NUMBER) : '';
20 | return format('Angular Version: %s%s', version, buildNumString);
21 | }
22 |
23 | function flattenArray(array) {
24 | return [].concat.apply([], array);
25 | }
26 |
27 | module.exports = function(config) {
28 | return function() {
29 | return flattenArray(Object.keys(config.versions).map(function(key) {
30 | var configWithDefaults = objAssign({}, config.defaults, config.versions[key]);
31 | return capabilityFromConfig(key, configWithDefaults);
32 | }));
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/test/e2e/tests/directive-object-declaration/index.js:
--------------------------------------------------------------------------------
1 | var preparePage = require('../../helpers/preparePage')('directive-object-declaration');
2 |
3 | /**
4 | * Receives N CSS class names as arguments, and returns a selector in the
5 | * format:
6 | * path('ngi-model-object', [ngi-model, 3]);
7 | * .ngi-scope > .ngi-drawer > .ngi-model-object > .ngi-drawer > .ngi-model:nth-child(3)
8 | */
9 | function path() {
10 | var sel = '.ngi-scope > .ngi-drawer ';
11 | for (var i = 0; i < arguments.length; i++) {
12 | if (typeof arguments[i] == 'object' && arguments[i].length == 2)
13 | sel += ' > .' + arguments[i][0] + ':nth-child(' + arguments[i][1] + ')'
14 | else
15 | sel += ' > .' + arguments[i];
16 |
17 | if (i < arguments.length - 1)
18 | sel += ' > .ngi-drawer'
19 | }
20 | return sel;
21 | }
22 |
23 | function toggle() {
24 | var sel = path.apply(null, arguments);
25 | sel += ' > label > .ngi-caret';
26 | element(by.css(sel)).click();
27 | }
28 |
29 | describe('identify directives declared with the object style', function() {
30 |
31 | beforeEach(preparePage);
32 |
33 | it('should expand and collapse object models', function() {
34 | toggle('ngi-model-object');
35 | expect($('.ngi-model-string .ngi-value').getText()).toBe('"John Doe"');
36 | });
37 |
38 | });
39 |
--------------------------------------------------------------------------------
/gulp/tasks/e2e.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var path = require('path');
3 | var config = require('../config');
4 | var format = require('util').format;
5 | var child_process = require('child_process');
6 | var scenarioServer = require('../../test/e2e/scenario-server');
7 |
8 | // Protractor execution adapted from: https://github.com/mllrsohn/gulp-protractor
9 |
10 | function getProtractorBinary() {
11 | var winExt = /^win/.test(process.platform) ? '.cmd' : '';
12 | var pkgPath = require.resolve('protractor');
13 | var protractorDir = path.resolve(path.join(path.dirname(pkgPath), '..', 'bin'));
14 | return path.join(protractorDir, '/protractor' + winExt);
15 | }
16 |
17 | function getProtractorConfPath() {
18 | return format('%s/protractor.conf.js', config.e2eDir);
19 | }
20 |
21 | gulp.task('e2e', ['default'], function(done) {
22 | var server = scenarioServer(3000);
23 | var argv = process.argv.slice(3);
24 | var protractorArgs = [].concat.apply(getProtractorConfPath(), argv);
25 |
26 | var finishTask = function(exitCode) {
27 | var gulpExitVal = exitCode ? 'Tests Failed' : null;
28 | server.close(done.bind(this, gulpExitVal));
29 | };
30 |
31 | child_process.spawn(getProtractorBinary(), protractorArgs, {
32 | stdio: 'inherit'
33 | }).once('close', finishTask);
34 | });
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-inspector",
3 | "version": "v0.5.11",
4 | "description": "A Chrome/Safari/Firefox extension to inspect AngularJS applications",
5 | "main": "ng-inspector.js",
6 | "scripts": {
7 | "postinstall": "./node_modules/.bin/webdriver-manager update",
8 | "build-xpi": "gulp && cd ng-inspector.firefox && jpm xpi ",
9 | "run-xpi": "gulp && cd ng-inspector.firefox && jpm run ",
10 | "test": "gulp e2e",
11 | "test-throttled": "gulp e2e --maxSessions 1"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/rev087/ng-inspector.git"
16 | },
17 | "keywords": [
18 | "angularjs",
19 | "angular",
20 | "inspector",
21 | "ng-inspector"
22 | ],
23 | "author": "Bruno Daniel",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/rev087/ng-inspector/issues"
27 | },
28 | "homepage": "https://github.com/rev087/ng-inspector",
29 | "devDependencies": {
30 | "browserify": "^11.0.1",
31 | "colors": "~0.6.2",
32 | "ejs": "^2.3.1",
33 | "express": "^4.12.3",
34 | "gulp": "~3.9.0",
35 | "gulp-less": "~1.2.3",
36 | "gulp-replace": "~0.3.0",
37 | "gulp-util": "^3.0.5",
38 | "jpm": "^1.0.0",
39 | "object-assign": "^3.0.0",
40 | "plist": "~0.4.3",
41 | "protractor": "^2.0.0",
42 | "require-dir": "^0.3.0",
43 | "semver": "~2.3.0",
44 | "vinyl-buffer": "^1.0.0",
45 | "vinyl-source-stream": "^1.1.0",
46 | "yargs": "^3.18.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/e2e/scenario-server/index.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var fs = require('fs');
3 | var util = require('util');
4 | var express = require('express');
5 | var colors = require('colors');
6 | var argv = require('yargs').argv;
7 | var versionConfig = require('../angular-versions.conf');
8 |
9 | function getTestNames() {
10 | var testDirPath = path.join(__dirname, '../tests');
11 | // Sync because this is only used for basic debugging in development
12 | return fs.readdirSync(testDirPath).filter(function(file) {
13 | return fs.statSync(require('path').resolve(testDirPath, file)).isDirectory();
14 | });
15 | }
16 |
17 | function getLibPath(angularVersion) {
18 | return versionConfig.versions[angularVersion].path;
19 | }
20 |
21 | function scenarioServer(port) {
22 | var app = express();
23 |
24 | app.set('views', __dirname);
25 | app.engine('.html', require('ejs').__express);
26 | app.set('view engine', 'html');
27 | app.use(express.static(path.join(__dirname, '..')));
28 |
29 | app.get('/', function(req, res) {
30 | var defaultVersion = Object.keys(versionConfig.versions)[0];
31 | res.render('directory', {
32 | angularVersions: versionConfig.versions,
33 | testNames: getTestNames(),
34 | angularVersion: req.query.version || defaultVersion
35 | });
36 | });
37 |
38 | app.get('/app/:scenario/:angularVersion', function(req, res) {
39 | var params = req.params;
40 | res.render('../tests/base-template', {
41 | angularVersion: params.angularVersion,
42 | angularLibPath: getLibPath(params.angularVersion),
43 | scenario: req.params.scenario,
44 | noEmbed: argv.noEmbed || argv.n,
45 | scenarioPath: '../tests/' + params.scenario + '/index.html'
46 | });
47 | });
48 |
49 | return app.listen(port, function() {
50 | var port = this.address().port;
51 | console.log('Serving scenarios on port %s'.green, port);
52 | });
53 | };
54 |
55 | var isRequired = !(require.main === module);
56 | if (!isRequired) scenarioServer();
57 |
58 | module.exports = scenarioServer;
59 |
--------------------------------------------------------------------------------
/ng-inspector.firefox/lib/main.js:
--------------------------------------------------------------------------------
1 | var manifest = {};
2 | try {
3 | // on jpm (Firefox >= 38) loads configuration from the package.json metadata
4 | manifest = require("package.json");
5 | } catch(e) {
6 | // on cfx (Firefox < 38) fallbacks to configure it here
7 | manifest["firefox-devtools"] = {
8 | "page-mods": [
9 | {
10 | "include": [
11 | "*"
12 | ],
13 | "contentStyleFiles": [
14 | "./stylesheet.css"
15 | ],
16 | "contentScriptFiles": [
17 | "./inject.js"
18 | ]
19 | }
20 | ],
21 | "toolbar-buttons": [
22 | {
23 | "label": "ng-inspector",
24 | "icons": {
25 | "19": "./btn19.png",
26 | "38": "./btn38.png"
27 | }
28 | }
29 | ]
30 | };
31 | }
32 |
33 | var toolbarButtonConfig = manifest["firefox-devtools"]["toolbar-buttons"][0];
34 |
35 | var { ActionButton } = require("sdk/ui/button/action");
36 | var tabs = require("sdk/tabs");
37 |
38 | var injectedTabPortsMap = new WeakMap();
39 |
40 | var button = ActionButton({
41 | id: "ng-inspector",
42 | label: toolbarButtonConfig.label,
43 | icon: {
44 | "16": toolbarButtonConfig.icons["19"],
45 | "32": toolbarButtonConfig.icons["38"]
46 | },
47 | onClick: function(state) {
48 | var port = injectedTabPortsMap.get(tabs.activeTab);
49 |
50 | if (port) {
51 | port.emit("ngi-command", {
52 | command: "ngi-toggle"
53 | });
54 | }
55 | }
56 | });
57 |
58 | var pageModConfig = manifest["firefox-devtools"]["page-mods"][0]
59 | var pageMod = require("sdk/page-mod");
60 | var self = require("sdk/self");
61 |
62 | pageMod.PageMod({
63 | include: pageModConfig.include,
64 | contentScriptFile: pageModConfig.contentScriptFiles.map(function(path) {
65 | return self.data.url(path);
66 | }),
67 | contentScriptOptions: {
68 | ngInspectorURL: self.data.url("./ng-inspector.js")
69 | },
70 | contentStyleFile: pageModConfig.contentStyleFiles.map(function(path) {
71 | return self.data.url(path);
72 | }),
73 | onAttach: function(worker) {
74 | injectedTabPortsMap.set(this.tab, worker.port);
75 | }
76 | });
77 |
--------------------------------------------------------------------------------
/src/js/Utils.js:
--------------------------------------------------------------------------------
1 | var Utils = {};
2 |
3 | var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
4 | var MOZ_HACK_REGEXP = /^moz([A-Z])/;
5 |
6 | /**
7 | * Converts snake_case to camelCase.
8 | * Also a special case for Moz prefix starting with upper case letter.
9 | */
10 | Utils.camelCase = function(name) {
11 | return name.
12 | replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
13 | return offset ? letter.toUpperCase() : letter;
14 | }).
15 | replace(MOZ_HACK_REGEXP, 'Moz$1');
16 | }
17 |
18 | var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
19 | var FN_ARG_SPLIT = /,/;
20 | var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
21 | var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
22 |
23 | var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
24 | /**
25 | * Converts all accepted directives format into proper directive name.
26 | * All of these will become 'myDirective':
27 | * my:Directive
28 | * my-directive
29 | * x-my-directive
30 | * data-my:directive
31 | */
32 | Utils.directiveNormalize = function(name) {
33 | return Utils.camelCase(name.replace(PREFIX_REGEXP, ''));
34 | }
35 |
36 | /**
37 | * Receives a service factory and returns an injection token. Only used in
38 | * older versions of AngularJS that did not expose `.annotate`
39 | *
40 | * Adapted from https://github.com/angular/angular.js/blob/0baa17a3b7ad2b242df2b277b81cebdf75b04287/src/auto/injector.js
41 | **/
42 | Utils.annotate = function(fn) {
43 | var $inject, fnText, argDecl;
44 |
45 | if (typeof fn === 'function') {
46 | if (!($inject = fn.$inject)) {
47 | $inject = [];
48 | if (fn.length) {
49 | fnText = fn.toString().replace(STRIP_COMMENTS, '');
50 | argDecl = fnText.match(FN_ARGS);
51 | var argDecls = argDecl[1].split(FN_ARG_SPLIT);
52 | for (var i = 0; i < argDecls.length; i++) {
53 | var arg = argDecls[i];
54 | arg.replace(FN_ARG, function(all, underscore, name) {
55 | $inject.push(name);
56 | });
57 | };
58 | }
59 | fn.$inject = $inject;
60 | }
61 | } else if (Array.isArray(fn)) {
62 | $inject = fn.slice(0, fn.length - 1);
63 | } else {
64 | return false;
65 | }
66 |
67 | return $inject;
68 | }
69 |
70 | module.exports = Utils;
71 |
--------------------------------------------------------------------------------
/src/js/ModelMixin.js:
--------------------------------------------------------------------------------
1 | function getUserDefinedKeys(values) {
2 | return Object.keys(values).filter(function(key) {
3 | return !isPrivateAngularProp(key);
4 | });
5 | }
6 |
7 | function isPrivateAngularProp(propName) {
8 | var PRIVATE_KEY_BLACKLIST = ['$parent', '$root', '$id'];
9 | var ANGULAR_PRIVATE_PREFIX = '$$';
10 | var firstTwoChars = propName[0] + propName[1];
11 |
12 | if (firstTwoChars === ANGULAR_PRIVATE_PREFIX) return true;
13 | if (PRIVATE_KEY_BLACKLIST.indexOf(propName) > -1 || propName === 'this') return true;
14 | return false;
15 | }
16 |
17 | function arrayDiff(a, b) {
18 | var i, ret = { added: [], removed: [], existing: [] };
19 |
20 | // Iterate through b checking for added and existing elements
21 | for (i = 0; i < b.length; i++) {
22 | if (a.indexOf(b[i]) < 0) {
23 | ret.added.push(b[i]);
24 | } else {
25 | ret.existing.push(b[i]);
26 | }
27 | }
28 |
29 | // Iterate through a checking for removed elements
30 | for (i = 0; i < a.length; i++) {
31 | if (b.indexOf(a[i]) < 0) {
32 | ret.removed.push(a[i]);
33 | }
34 | }
35 |
36 | return ret;
37 | }
38 |
39 | function ModelMixin() {}
40 |
41 | ModelMixin.update = function(values, depth, Model) {
42 |
43 | if (typeof this.modelObjs === 'undefined') this.modelObjs = {};
44 | if (typeof this.modelKeys === 'undefined') this.modelKeys = [];
45 |
46 | var newKeys = getUserDefinedKeys(values),
47 | diff = arrayDiff(this.modelKeys, newKeys),
48 | i, key;
49 |
50 | // Removed keys
51 | for (i = 0; i < diff.removed.length; i++) {
52 | var key = diff.removed[i];
53 | this.modelObjs[key].view.destroy();
54 | delete this.modelObjs[key];
55 | }
56 |
57 | // New keys
58 | for (i = 0; i < diff.added.length; i++) {
59 | key = diff.added[i];
60 | this.modelObjs[key] = Model.instance(key, values[key], depth.concat([values]));
61 | var insertAtTop = this.ngiType === 'Scope';
62 | this.view.addChild(this.modelObjs[key].view, insertAtTop);
63 | }
64 |
65 | // Updated keys
66 | for (i = 0; i < diff.existing.length; i++) {
67 | key = diff.existing[i];
68 | if (!this.modelObjs[key]) {
69 | var inst = this.ngiType === 'Scope' ? 'Scope' : this.ngiType === 'Model' ? 'Model' : 'UNKNOWN INSTANCE';
70 | continue;
71 | }
72 | this.modelObjs[key].setValue(values[key]);
73 | }
74 |
75 | this.modelKeys = newKeys;
76 | };
77 |
78 | ModelMixin.extend = function(obj) {
79 | obj.update = ModelMixin.update.bind(obj);
80 | };
81 |
82 | module.exports = ModelMixin;
83 |
--------------------------------------------------------------------------------
/src/js/Inspector.js:
--------------------------------------------------------------------------------
1 | var NGI = {
2 | InspectorPane: require('./InspectorPane'),
3 | App: require('./App'),
4 | Scope: require('./Scope')
5 | };
6 |
7 | module.exports = function() {
8 |
9 | // Settings defaults
10 | this.settings = {
11 | showWarnings: false
12 | };
13 |
14 | this.pane = new NGI.InspectorPane();
15 |
16 | // The actual toggling is done by the `NGI.InspectorPane`. Since the
17 | // `ng-inspector.js` script is injected into the page DOM with no direct
18 | // access to `safari.extension.settings`, settings can only be sent via
19 | // messages. To save on the number of messages sent back and forth between
20 | // this injected script and the browser extension, the browser settings are
21 | // sent along with the toggle command. A side effect is that changes in the
22 | // settings only take place after a toggle is triggered.
23 | this.toggle = function(settings) {
24 |
25 | // If angular is not present in the global scope, we stop the process
26 | if (!('angular' in window)) {
27 | alert('This page does not include AngularJS');
28 | return;
29 | }
30 |
31 | // Passing the settings parameter is optional
32 | this.settings.showWarnings = (settings && !!settings.showWarning);
33 |
34 | // Send the command forward to the NGI.InspectorPane, retrieving the state
35 | var visible = this.pane.toggle();
36 | if (visible) {
37 | NGI.App.inspectApps();
38 | } else {
39 | NGI.App.stopObservers();
40 | NGI.Scope.stopObservers();
41 | }
42 | }
43 |
44 | // Debugging utlity, to be used in the console. Retrieves the "breadcrumb" of
45 | // a specific scope in the hierarchy usage: ngInspector.scope('002')
46 | window.$scopeId = function(id) {
47 |
48 | function findRoot(el) {
49 | var child = el.firstChild;
50 | if (!child) return;
51 | do {
52 | var $el = angular.element(el);
53 |
54 | if ($el.data('$scope')) {
55 | return $el.data('$scope').$root;
56 | }
57 |
58 | var res = findRoot(child);
59 | if (res) return res;
60 |
61 | } while (child = child.nextSibling);
62 | }
63 |
64 | function dig(scope, breadcrumb) {
65 | var newBreadcrumb = breadcrumb.slice(0);
66 | newBreadcrumb.push(scope.$id);
67 |
68 | if (scope.$id == id) {
69 | console.log(newBreadcrumb);
70 | return scope;
71 | }
72 |
73 | var child = scope.$$childHead;
74 |
75 | if (!child) return;
76 |
77 | do {
78 | var res = dig(child, newBreadcrumb);
79 | if (res) return res;
80 | } while (child = child.$$nextSibling);
81 |
82 | }
83 |
84 | return dig(findRoot(document), []);
85 | };
86 |
87 | };
--------------------------------------------------------------------------------
/ng-inspector.safariextension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Author
6 | Bruno Daniel
7 | Builder Version
8 | 10600.4.10.7
9 | CFBundleDisplayName
10 | ng-inspector for AngularJS
11 | CFBundleIdentifier
12 | com.rev087.ng-inspector
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleShortVersionString
16 | v0.5.11
17 | CFBundleVersion
18 | v0.5.11
19 | Chrome
20 |
21 | Database Quota
22 | 1048576
23 | Global Page
24 | global.html
25 | Toolbar Items
26 |
27 |
28 | Command
29 | toggle-popover
30 | Identifier
31 | ng-inspector-toolbar-item
32 | Image
33 | ng.png
34 | Label
35 | ng-inspector
36 | Palette Label
37 | ng-inspector
38 | Popover
39 |
40 | Tool Tip
41 | Toggle ng-inspector
42 |
43 |
44 |
45 | Content
46 |
47 | Scripts
48 |
49 | End
50 |
51 | inject-end.js
52 |
53 |
54 | Stylesheets
55 |
56 | stylesheet.css
57 |
58 |
59 | Description
60 | Inspect the AngularJS scope hierarchy in a page.
61 | DeveloperIdentifier
62 | 76VT48N9LK
63 | ExtensionInfoDictionaryVersion
64 | 1.0
65 | Permissions
66 |
67 | Website Access
68 |
69 | Include Secure Pages
70 |
71 | Level
72 | All
73 |
74 |
75 | Update Manifest URL
76 | http://rev087.github.io/ng-inspector/update-manifest.plist
77 | Website
78 | https://github.com/rev087/ng-inspector
79 |
80 |
81 |
--------------------------------------------------------------------------------
/src/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | var NGI = {
2 | Inspector: require('./Inspector'),
3 | App: require('./App')
4 | };
5 |
6 | function bootstrap() {
7 |
8 | // Instantiate the inspector
9 | window.ngInspector = new NGI.Inspector();
10 |
11 | // True once the wrapBootstrap() method runs for the first time
12 | var didWrapBootstrap = false;
13 |
14 | // RequireJS and similar loaders work by injecting