├── 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 | 18 |
19 |
20 |

Scenarios (<%= angularVersion %>)

21 | 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 | ![logo](logo.png?raw=true) 2 | 3 | ![Travis CI Status](https://travis-ci.org/rev087/ng-inspector.svg?branch=master) 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 | ![screenshot](screenshot.png?raw=true) 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