├── .gitignore
├── .node-version
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── api.js
├── appveyor.yml
├── lib
├── about-view.js
├── accessibility-element-view.js
├── accessibility-rule-view.js
├── accessibility-view.js
├── accessibility.js
├── browser-globals.js
├── chrome-helpers.js
├── emitter-view.js
├── eval.js
├── event-helpers.js
├── event-listener-view.js
├── event-view.js
├── events-view.js
├── expandable-view.js
├── index.js
├── ipc-event-view.js
├── ipc-helpers.js
├── ipc-view.js
├── lint-helpers.js
├── lint-view.js
├── module-helpers.js
├── module-view.js
├── module.js
├── modules-view.js
├── node-integration-view.js
├── selectable-view.js
├── sidebar-view.js
└── view.js
├── manifest.json
├── out
└── .gitkeep
├── package-lock.json
├── package.json
├── static
├── devtron.css
├── devtron.html
└── index.html
├── test
├── fixtures
│ ├── app
│ │ ├── index.js
│ │ ├── package.json
│ │ └── preload.js
│ ├── foo.txt
│ ├── node-integration-instructions
│ │ ├── index.js
│ │ ├── package.json
│ │ └── preload.js
│ ├── node_modules
│ │ └── foo
│ │ │ ├── index.js
│ │ │ └── package.json
│ └── preload
│ │ ├── index.js
│ │ ├── package.json
│ │ └── preload.js
├── integration
│ ├── app-test.js
│ ├── node-integration-instructions-test.js
│ └── preload-test.js
├── server.js
└── unit
│ ├── chrome-test.js
│ ├── devtools.js
│ ├── eval-test.js
│ ├── event-test.js
│ ├── ipc-test.js
│ ├── lint-test.js
│ ├── module-test.js
│ ├── node_modules
│ └── electron
│ │ ├── index.js
│ │ └── package.json
│ └── setup-test.js
└── vendor
├── bootstrap
├── css
│ └── bootstrap.css
└── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── entypo
├── arrow-down.svg
├── arrow-up.svg
├── block.svg
├── bug.svg
├── check.svg
├── controller-paus.svg
├── controller-record.svg
├── controller-stop.svg
├── cycle.svg
├── flow-tree.svg
├── funnel.svg
├── globe.svg
├── help-with-circle.svg
├── old-phone.svg
├── open-book.svg
├── sound.svg
├── swap.svg
├── triangle-down.svg
├── triangle-right.svg
└── warning.svg
├── github.css
└── photon
├── css
└── photon.css
└── fonts
├── photon-entypo.eot
├── photon-entypo.svg
├── photon-entypo.ttf
└── photon-entypo.woff
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | npm-debug.log
3 | /out/
4 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | v5.10.0
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .node-version
2 | .gitkeep
3 | .npmignore
4 | /test/
5 | /lib/
6 | appveyor.yml
7 | .travis.yml
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js: "4.2"
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | before_script:
10 | - "export DISPLAY=:99.0"
11 | - "sh -e /etc/init.d/xvfb start"
12 | - sleep 3 # give xvfb some time to start
13 |
14 | cache:
15 | directories:
16 | - node_modules
17 |
18 | notifications:
19 | email:
20 | on_success: never
21 | on_failure: change
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 GitHub Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | > 🚨 **WARNING:** Devtron is currently **unmaintained**. If you're interested in becoming a maintainer, see Issue [#200](https://github.com/electron-userland/devtron/issues/200) for more information.
3 | # Devtron
4 |
5 | An [Electron](http://electron.atom.io) [DevTools](https://developer.chrome.com/devtools)
6 | extension to help you inspect, monitor, and debug your app.
7 |
8 | [](https://travis-ci.org/electron/devtron)
9 | [](https://ci.appveyor.com/project/electron-bot/devtron/branch/master)
10 | [](http://standardjs.com/)
11 | [](https://www.npmjs.com/packages/devtron)
12 |
13 | 
14 |
15 | ## Features
16 |
17 | * **Require graph** to help you visualize your app's internal and external
18 | library dependencies in both the main and renderer processes.
19 | * **IPC monitor** to track and inspect the messages sent and received
20 | between the renderer and main processes in your app.
21 | * **Event inspector** that shows the events and listeners that are registered
22 | in your app on the core Electron APIs such as the window, the app, and the
23 | processes.
24 | * **App Linter** that checks your apps for possible issues and missing
25 | functionality.
26 |
27 | ## Installing
28 |
29 | ```sh
30 | npm install --save-dev devtron
31 | ```
32 |
33 | Then execute the following from the Console tab of your running Electron app's
34 | developer tools:
35 |
36 | ```js
37 | require('devtron').install()
38 | ```
39 |
40 | You should then see a `Devtron` tab added.
41 |
42 | ## Disabled Node Integration
43 |
44 | If your application's `BrowserWindow` was created with `nodeIntegration` set
45 | to `false` then you will need to expose some globals via a [preload](http://electron.atom.io/docs/api/browser-window/#new-browserwindowoptions)
46 | script to allow Devtron access to Electron APIs:
47 |
48 | ```js
49 | window.__devtron = {require: require, process: process}
50 | ```
51 |
52 | Then restart your application and Devtron should successfully load. You may
53 | want to guard this assignment with a `if (process.env.NODE_ENV === 'development')`
54 | check to ensure these variables aren't exposed in production.
55 |
56 | ## Developing locally
57 |
58 | ```
59 | git clone https://github.com/electron/devtron
60 | cd devtron
61 | npm install
62 | npm start
63 | ```
64 |
65 | This will start a process that watches and compiles the extension as files
66 | are modified.
67 |
68 | Then open the Console tab of your Electron app and run the following with the
69 | path updated for the location that you've cloned devtron to:
70 |
71 | ```js
72 | require('/Users/me/code/devtron').install()
73 | ```
74 |
75 | Then a Devtron tab should appear and it will now be enabled for that
76 | application.
77 |
78 | You can reload the extension by closing and reopening the dev tools.
79 |
80 | ### Running in a browser
81 |
82 | To make developing and debugging the extension easier, you can run it in a
83 | Chrome tab that will talk remotely to a running Electron app over HTTP.
84 |
85 | - Set the `DEVTRON_DEBUG_PATH` environment variable to the path of the cloned
86 | devtron repository.
87 | - Start your Electron application.
88 | - Click the **Devtron** tab.
89 | - You should then see the following messages logged to the **Console** tab:
90 |
91 | > Devtron server listening on http://localhost:3948
92 | >
93 | > Open file:///Users/me/devtron/static/index.html to view
94 |
95 | - Then open `/Users/me/devtron/static/index.html` in Chrome
96 | - The page there will talk remotely to the running Electron app so you'll
97 | be able to fully interact with each pane with real data.
98 |
99 | ### Additional Notes
100 |
101 | - `require('devtron').install()` cannot be called before the `ready` event of the `app` module has been emitted.
102 |
103 | #### Webpack
104 |
105 | When using webpack, you may experience issues resolving `__dirname`. In accordance with the [docs](https://webpack.js.org/configuration/node/#node-__dirname), `__dirname` is resolved at runtime on the compiled file.
106 |
107 | You have to two solutions:
108 | 1. Remove `devtron` from Webpack bundle with `config.externals`
109 | 2. Copy `devtron` files to the same folder as your compiled main process file
110 |
111 | #### [Solution 1] Remove from webpack bundle
112 |
113 | ```js
114 | config.externals = [
115 | function(context, request, callback) {
116 | if (request.match(/devtron/)) {
117 | return callback(null, 'commonjs ' + request)
118 | }
119 |
120 | callback()
121 | }
122 | ]
123 | ```
124 |
125 | #### [Solution 2] Copy devtron files
126 | 1. Make sure that webpack does not replace `__dirname` by setting:
127 | ```js
128 | // in your webpack config for main process
129 | {
130 | target: 'electron-main',
131 | node: {
132 | __dirname: false,
133 | }
134 | }
135 | ```
136 | 2. Ensure that the copy target for `devtron/manifest.json` is the same folder as your compiled main process `js` file.
137 | 3. Ensure that the copy target for the `devtron/out/browser-globals.js` is `out/browser-globals.js` relative to your compiled main process `js` file.
138 |
139 | You can copy files with `copy-webpack-plugin`.
140 |
141 | ```js
142 | const path = require('path');
143 | const CopyWebpackPlugin = require('copy-webpack-plugin');
144 |
145 | const copyFiles = [
146 | {
147 | from: path.resolve(__dirname, 'node_modules/devtron/manifest.json')
148 | },
149 | {
150 | from: path.resolve(__dirname, 'node_modules/devtron/out/browser-globals.js'),
151 | to: path.resolve(__dirname, 'out'),
152 | }
153 | ];
154 |
155 | config.target = 'electron-main',
156 | config.plugins = [
157 | new CopyWebpackPlugin(copyFiles),
158 | ]
159 | ```
160 |
161 | ## Contributing
162 |
163 | Have an idea for something this extension could do to make debugging Electron
164 | apps easier? Please [open an issue](https://github.com/electron/devtron/issues/new).
165 |
166 | Pull requests are also welcome and appreciated. Run `npm test` to run the
167 | existing tests. This project uses the [standard JavaScript style](http://standardjs.com).
168 |
169 | ## License
170 |
171 | MIT
172 |
--------------------------------------------------------------------------------
/api.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron')
2 |
3 | exports.install = () => {
4 | if (process.type === 'renderer') {
5 | console.log(`Installing Devtron from ${__dirname}`)
6 | if (electron.remote.BrowserWindow.getDevToolsExtensions &&
7 | electron.remote.BrowserWindow.getDevToolsExtensions().devtron) return true
8 | return electron.remote.BrowserWindow.addDevToolsExtension(__dirname)
9 | } else if (process.type === 'browser') {
10 | console.log(`Installing Devtron from ${__dirname}`)
11 | if (electron.BrowserWindow.getDevToolsExtensions &&
12 | electron.BrowserWindow.getDevToolsExtensions().devtron) return true
13 | return electron.BrowserWindow.addDevToolsExtension(__dirname)
14 | } else {
15 | throw new Error('Devtron can only be installed from an Electron process.')
16 | }
17 | }
18 |
19 | exports.uninstall = () => {
20 | if (process.type === 'renderer') {
21 | console.log(`Uninstalling Devtron from ${__dirname}`)
22 | return electron.remote.BrowserWindow.removeDevToolsExtension('devtron')
23 | } else if (process.type === 'browser') {
24 | console.log(`Uninstalling Devtron from ${__dirname}`)
25 | return electron.BrowserWindow.removeDevToolsExtension('devtron')
26 | } else {
27 | throw new Error('Devtron can only be uninstalled from an Electron process.')
28 | }
29 | }
30 |
31 | exports.path = __dirname
32 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | build: off
2 |
3 | os: unstable
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | skip_tags: true
10 |
11 | environment:
12 | nodejs_version: "4.4.3"
13 |
14 | cache:
15 | - node_modules -> package.json
16 |
17 | install:
18 | - ps: Install-Product node $env:nodejs_version
19 | - npm install npm
20 | - .\node_modules\.bin\npm install
21 |
22 | test_script:
23 | - node --version
24 | - .\node_modules\.bin\npm --version
25 | - .\node_modules\.bin\npm test
26 |
--------------------------------------------------------------------------------
/lib/about-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Chrome = require('./chrome-helpers')
4 | const Eval = require('./eval')
5 | const View = require('./view')
6 | const metadata = require('../package')
7 |
8 | class AboutView extends View {
9 | constructor () {
10 | super('about-view')
11 | this.handleEvents()
12 | this.apis = Chrome.getChromeAPIs()
13 | this.render()
14 | }
15 |
16 | render () {
17 | this.versionLabel.textContent = metadata.version
18 |
19 | this.tabID.textContent = window.chrome.devtools.inspectedWindow.tabId
20 |
21 | if (window.chrome.runtime) {
22 | this.runtimeID.textContent = window.chrome.runtime.id
23 | }
24 |
25 | this.chromeAPIs.textContent = this.apis.sort().join('\n')
26 | }
27 |
28 | handleEvents () {
29 | this.issueButton.addEventListener('click', () => this.reportIssue())
30 | }
31 |
32 | reportIssue () {
33 | Eval.openExternal('https://github.com/electron/devtron/issues')
34 | }
35 | }
36 |
37 | module.exports = AboutView
38 |
--------------------------------------------------------------------------------
/lib/accessibility-element-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const SelectableView = require('./selectable-view')
4 | const Eval = require('./eval')
5 |
6 | class AccessibilityElementView extends SelectableView {
7 | constructor (element, parent) {
8 | super('element-row')
9 |
10 | this.path = element.selector
11 | this.pathId = element.id
12 | parent.appendChild(this.element)
13 | this.render()
14 | this.handleEvents(parent)
15 | }
16 |
17 | handleEvents (parent) {
18 | this.listenForSelection(parent)
19 | this.listenForSelectionKeys(parent.parentElement)
20 | }
21 |
22 | render () {
23 | this.selectorPath.textContent = this.path
24 |
25 | // Add a click-handler that will select the element.
26 | // Uses the `accessibilityAuditMap` defined in accessibility.js
27 | this.selectorPath.onclick = (evt) => {
28 | evt.stopPropagation()
29 | Eval.execute(`inspect(window.__devtron.accessibilityAuditMap.get(${this.pathId}))`)
30 | }
31 | }
32 |
33 | filter (searchText) {
34 | let matches = this.path.toLowerCase().includes(searchText)
35 | matches ? this.show() : this.hide()
36 | return matches
37 | }
38 | }
39 |
40 | module.exports = AccessibilityElementView
41 |
--------------------------------------------------------------------------------
/lib/accessibility-rule-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const ExpandableView = require('./expandable-view')
4 | const AccessibilityElementView = require('./accessibility-element-view')
5 |
6 | class AccessibilityRuleView extends ExpandableView {
7 | constructor (rule, table) {
8 | super('rule-row')
9 |
10 | this.rule = rule
11 | this.count = rule.elements.length
12 |
13 | table.appendChild(this.element)
14 | this.handleEvents(table)
15 | this.render()
16 |
17 | this.children = rule.elements.map((element) => {
18 | return new AccessibilityElementView(element, table)
19 | })
20 | this.collapse()
21 | }
22 |
23 | handleEvents (table) {
24 | this.listenForSelection(table)
25 | this.listenForSelectionKeys(table.parentElement)
26 | this.listenForExpanderKeys(table.parentElement)
27 | }
28 |
29 | render () {
30 | this.status.textContent = this.rule.status
31 | this.severity.textContent = this.rule.severity
32 | this.ruleName.textContent = this.rule.title
33 | this.elementCount.textContent = `(${this.count})`
34 | if (this.count === 0) {
35 | this.disclosure.style.visibility = 'hidden'
36 | }
37 | }
38 |
39 | filter (searchText) {
40 | this.collapse()
41 |
42 | let matches = this.rule.title.toLowerCase().includes(searchText) || this.rule.status.toLowerCase().includes(searchText)
43 |
44 | this.children.forEach((child) => {
45 | if (child.filter(searchText)) matches = true
46 | })
47 |
48 | if (matches) {
49 | this.markCollapsed()
50 | this.show()
51 | this.markExpanded()
52 | } else {
53 | this.hide()
54 | }
55 | return matches
56 | }
57 | }
58 |
59 | module.exports = AccessibilityRuleView
60 |
--------------------------------------------------------------------------------
/lib/accessibility-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Eval = require('./eval')
4 | const AccessibilityRuleView = require('./accessibility-rule-view')
5 | const View = require('./view')
6 | const accessibility = require('./accessibility')
7 |
8 | class AccessibilityView extends View {
9 | constructor () {
10 | super('accessibility-view')
11 | this.handleEvents()
12 | }
13 |
14 | handleEvents () {
15 | this.debounceInput(this.searchBox, () => this.filterAudits())
16 | this.docsButton.addEventListener('click', () => this.openDocs())
17 | this.accessibilityButton.addEventListener('click', () => this.audit())
18 | }
19 |
20 | audit () {
21 | accessibility.audit().then((results) => {
22 | return this.render(results)
23 | })
24 | }
25 |
26 | render (results) {
27 | this.tableDescription.classList.add('hidden')
28 | this.accessibilityResultsTable.innerHTML = ''
29 | this.children = results.map((result) => {
30 | return new AccessibilityRuleView(result, this.accessibilityResultsTable)
31 | })
32 | this.children[0].select()
33 | }
34 |
35 | openDocs () {
36 | Eval.openExternal('https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules')
37 | }
38 |
39 | filterAudits () {
40 | const searchText = this.getFilterText()
41 | if (searchText) {
42 | this.children.forEach((child) => child.filter(searchText))
43 | } else {
44 | this.children.forEach((child) => {
45 | child.show()
46 | child.collapse()
47 | })
48 | }
49 | }
50 |
51 | getFilterText () {
52 | return this.searchBox.value.toLowerCase()
53 | }
54 | }
55 |
56 | module.exports = AccessibilityView
57 |
--------------------------------------------------------------------------------
/lib/accessibility.js:
--------------------------------------------------------------------------------
1 | const Eval = require('./eval')
2 |
3 | exports.audit = () => {
4 | return Eval.execute(function () {
5 | const {axs} = window.__devtron // defined in browser-globals.js
6 | const config = new axs.AuditConfiguration({showUnsupportedRulesWarning: false})
7 | const results = axs.Audit.run(config)
8 |
9 | // Create a lookup map so users can click on an element to inspect it
10 | let idCounter = 0
11 | window.__devtron.accessibilityAuditMap = new Map()
12 | results.forEach(function (result) {
13 | const elements = result.elements || []
14 | elements.forEach(function (element) {
15 | const id = idCounter++
16 | element.__accessibilityAuditId = id
17 | window.__devtron.accessibilityAuditMap.set(id, element)
18 | })
19 | })
20 |
21 | return results.map(function (result) {
22 | const elements = result.elements || []
23 | let status = 'N/A'
24 | if (result.result === 'PASS') {
25 | status = 'Pass'
26 | } else if (result.result === 'FAIL') {
27 | status = 'Fail'
28 | }
29 | return {
30 | code: result.rule.code,
31 | severity: result.rule.severity,
32 | status: status,
33 | title: result.rule.heading,
34 | url: result.rule.url,
35 | elements: elements.map(function (element) {
36 | let selector = element.tagName.toLowerCase()
37 | if (element.className) {
38 | selector += '.' + element.className.split(' ').join('.')
39 | }
40 | return {
41 | selector: selector,
42 | id: element.__accessibilityAuditId
43 | }
44 | })
45 | }
46 | }).sort(function (resultA, resultB) {
47 | const statusA = resultA.status
48 | const statusB = resultB.status
49 | const severityA = resultA.severity
50 | const severityB = resultB.severity
51 |
52 | if (statusA === statusB) {
53 | if (severityA === severityB) {
54 | return resultB.elements.length - resultA.elements.length
55 | }
56 |
57 | if (severityA === 'Severe') return -1
58 | if (severityB === 'Severe') return 1
59 |
60 | if (severityA === 'Warning') return -1
61 | if (severityB === 'Warning') return 1
62 | } else {
63 | if (statusA === 'Fail') return -1
64 | if (statusB === 'Fail') return 1
65 |
66 | if (statusA === 'Pass') return -1
67 | if (statusB === 'Pass') return 1
68 | }
69 | })
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/lib/browser-globals.js:
--------------------------------------------------------------------------------
1 | // This defines globals that will be used in the browser context
2 | // (via the content_scripts definition in manifest.json)
3 | //
4 | // It is generated via `npm run-script prepublish`
5 | const axs = require('accessibility-developer-tools')
6 |
7 | window.__devtron = window.__devtron || {}
8 | window.__devtron.axs = axs
9 |
--------------------------------------------------------------------------------
/lib/chrome-helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const objectPrototype = Object.getPrototypeOf({})
4 |
5 | const isCustomClass = (object, prototype) => {
6 | if (typeof object !== 'object') return false
7 | if (Array.isArray(object)) return false
8 |
9 | return prototype && prototype !== objectPrototype
10 | }
11 |
12 | const checkAPI = (apis, parent, name, value) => {
13 | const api = parent + '.' + name
14 | if (typeof value === 'object') {
15 | findChromeAPIs(apis, api, value)
16 | } else {
17 | apis[api] = true
18 | }
19 | }
20 |
21 | const findChromeAPIs = (apis, parent, object) => {
22 | for (const name in object) {
23 | checkAPI(apis, parent, name, object[name])
24 | }
25 |
26 | const prototype = Object.getPrototypeOf(object)
27 | if (isCustomClass(object, prototype)) {
28 | Object.getOwnPropertyNames(prototype).filter((name) => {
29 | return name !== 'constructor'
30 | }).forEach((name) => {
31 | checkAPI(apis, parent, name, object[name])
32 | })
33 | }
34 |
35 | return Object.keys(apis)
36 | }
37 |
38 | exports.getChromeAPIs = () => findChromeAPIs({}, 'chrome', window.chrome)
39 |
--------------------------------------------------------------------------------
/lib/emitter-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const EventView = require('./event-view')
4 | const ExpandableView = require('./expandable-view')
5 |
6 | class EmitterView extends ExpandableView {
7 | constructor (name, listeners, table) {
8 | super('emitter-row')
9 |
10 | this.name = name
11 | this.count = Object.keys(listeners).reduce((count, name) => {
12 | return count + listeners[name].length
13 | }, 0)
14 |
15 | table.appendChild(this.element)
16 | this.render()
17 | this.handleEvents(table)
18 |
19 | this.children = Object.keys(listeners).map((name) => {
20 | return new EventView(name, listeners[name], this, table)
21 | })
22 | this.collapse()
23 | }
24 |
25 | handleEvents (table) {
26 | this.listenForSelection(table)
27 | this.listenForSelectionKeys(table.parentElement)
28 | this.listenForExpanderKeys(table.parentElement)
29 | }
30 |
31 | render () {
32 | this.emitterName.textContent = this.name
33 | this.listenerCount.textContent = `(${this.count})`
34 | }
35 |
36 | filter (searchText) {
37 | this.collapse()
38 |
39 | let matches = this.name.includes(searchText)
40 |
41 | this.children.forEach((child) => {
42 | if (child.filter(searchText)) matches = true
43 | })
44 |
45 | if (matches) {
46 | this.markCollapsed()
47 | this.show()
48 | this.markExpanded()
49 | } else {
50 | this.hide()
51 | }
52 | return matches
53 | }
54 | }
55 |
56 | module.exports = EmitterView
57 |
--------------------------------------------------------------------------------
/lib/eval.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | class Eval {
4 | static execute (expression) {
5 | if (typeof expression === 'function') {
6 | expression = `(${expression})`
7 | if (arguments.length > 1) {
8 | let expressionArgs = JSON.stringify(Array.prototype.slice.call(arguments, 1))
9 | expression += `.apply(this, ${expressionArgs})`
10 | } else {
11 | expression += '()'
12 | }
13 | }
14 |
15 | expression = `
16 | (function () {
17 | window.__devtron = window.__devtron || {}
18 | window.__devtron.evaling = true
19 |
20 | var require = window.__devtron.require || window.require
21 | var process = window.__devtron.process || window.process
22 |
23 | try {
24 | return ${expression}
25 | } finally {
26 | window.__devtron.evaling = false
27 | }
28 | })()
29 | `
30 |
31 | return new Promise((resolve, reject) => {
32 | window.chrome.devtools.inspectedWindow.eval(expression, (result, error) => {
33 | if (error) {
34 | if (error.isException && error.value) {
35 | let stack = error.value
36 | error = new Error(stack.split('\n')[0])
37 | error.stack = stack
38 | }
39 | reject(error)
40 | } else {
41 | resolve(result)
42 | }
43 | })
44 | })
45 | }
46 |
47 | static getFileSize (path) {
48 | return Eval.execute((path) => {
49 | try {
50 | return require('fs').statSync(path).size
51 | } catch (error) {
52 | return -1
53 | }
54 | }, path)
55 | }
56 |
57 | static openExternal (urlToOpen) {
58 | return Eval.execute((urlToOpen) => {
59 | return require('electron').shell.openExternal(urlToOpen)
60 | }, urlToOpen)
61 | }
62 |
63 | static getFileVersion (filePath) {
64 | return Eval.execute((filePath) => {
65 | if (/\/atom\.asar\/(browser|common|renderer)\//.test(filePath)) return process.versions.electron
66 |
67 | const fs = require('fs')
68 | const path = require('path')
69 | const appVersion = require('electron').remote.app.getVersion()
70 |
71 | let directory = path.dirname(filePath)
72 | while (path.basename(directory) !== 'node_modules') {
73 | try {
74 | let metadataPath = path.join(directory, 'package.json')
75 | let version = JSON.parse(fs.readFileSync(metadataPath)).version
76 | if (version) return version
77 | } catch (error) {
78 | // Ignore and continue
79 | }
80 |
81 | let nextDirectory = path.dirname(directory)
82 | if (nextDirectory === directory) break
83 | directory = nextDirectory
84 | }
85 | return appVersion
86 | }, filePath)
87 | }
88 |
89 | static isDebugMode () {
90 | return Eval.execute(() => {
91 | return process && !!process.env.DEVTRON_DEBUG_PATH
92 | })
93 | }
94 |
95 | static isApiAvailable () {
96 | return Eval.execute(() => {
97 | return typeof process === 'object' && typeof require === 'function'
98 | })
99 | }
100 |
101 | // Start a local http server in the currently running app that will
102 | // listen to requests sent by a browser
103 | static startServer () {
104 | return Eval.execute(() => {
105 | const path = require('path')
106 | const serverPath = path.join(process.env.DEVTRON_DEBUG_PATH, 'test', 'server.js')
107 | require(serverPath)
108 | })
109 | }
110 |
111 | // Implement the window.chrome.devtools.inspectedWindow.eval API via
112 | // window.fetch talking to a local http server running in an opened
113 | // Electron app
114 | static proxyToServer () {
115 | window.chrome.devtools = {
116 | inspectedWindow: {
117 | eval: function (expression, callback) {
118 | window.fetch('http://localhost:3948', {
119 | body: JSON.stringify({expression: expression}),
120 | headers: {
121 | 'Accept': 'application/json',
122 | 'Content-Type': 'application/json'
123 | },
124 | method: 'POST'
125 | }).then((response) => {
126 | return response.json()
127 | }).then((json) => {
128 | callback(json.result)
129 | }).catch((error) => {
130 | callback(null, error)
131 | })
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 | module.exports = Eval
139 |
--------------------------------------------------------------------------------
/lib/event-helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Eval = require('./eval')
4 |
5 | exports.getEvents = () => {
6 | return Eval.execute(() => {
7 | const formatCode = (listener) => {
8 | let lines = listener.split(/\r?\n/)
9 | if (lines.length === 1) return listener
10 |
11 | let lastLine = lines[lines.length - 1]
12 | let lastLineMatch = /^(\s+)}/.exec(lastLine)
13 | if (!lastLineMatch) return listener
14 |
15 | let whitespaceRegex = new RegExp('^' + lastLineMatch[1])
16 | return lines.map((line) => {
17 | return line.replace(whitespaceRegex, '')
18 | }).join('\n')
19 | }
20 |
21 | const getEvents = (emitter) => {
22 | const events = {}
23 | Object.keys(emitter._events).sort().forEach((name) => {
24 | let listeners = emitter.listeners(name)
25 | if (listeners.length > 0) {
26 | events[name] = listeners.map((listener) => {
27 | return formatCode(listener.toString())
28 | })
29 | }
30 | })
31 | return events
32 | }
33 |
34 | const electron = require('electron')
35 | const remote = electron.remote
36 | return {
37 | 'electron.remote.getCurrentWindow()': getEvents(remote.getCurrentWindow()),
38 | 'electron.remote.getCurrentWebContents()': getEvents(remote.getCurrentWebContents()),
39 | 'electron.remote.app': getEvents(remote.app),
40 | 'electron.remote.ipcMain': getEvents(remote.ipcMain),
41 | 'electron.ipcRenderer': getEvents(electron.ipcRenderer),
42 | 'electron.remote.process': getEvents(remote.process),
43 | 'global.process': getEvents(process)
44 | }
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/lib/event-listener-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const highlight = require('highlight.js')
4 | const SelectableView = require('./selectable-view')
5 |
6 | class EventListenerView extends SelectableView {
7 | constructor (listener, parent) {
8 | super('listener-code-row')
9 |
10 | this.listener = listener
11 | parent.appendChild(this.element)
12 | this.render()
13 | this.handleEvents(parent)
14 | }
15 |
16 | handleEvents (parent) {
17 | this.listenForSelection(parent)
18 | this.listenForSelectionKeys(parent.parentElement)
19 | }
20 |
21 | render () {
22 | this.listenerValue.textContent = this.listener
23 | highlight.highlightBlock(this.listenerValue)
24 | }
25 |
26 | filter (searchText) {
27 | let matches = this.listener.toLowerCase().includes(searchText)
28 | matches ? this.show() : this.hide()
29 | return matches
30 | }
31 | }
32 |
33 | module.exports = EventListenerView
34 |
--------------------------------------------------------------------------------
/lib/event-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const ExpandableView = require('./expandable-view')
4 | const EventListenerView = require('./event-listener-view')
5 |
6 | class EventView extends ExpandableView {
7 | constructor (name, listeners, parent, table) {
8 | super('event-type-row')
9 |
10 | this.name = name
11 | this.count = listeners.length
12 | this.parent = parent
13 |
14 | table.appendChild(this.element)
15 | this.handleEvents(table)
16 | this.render()
17 |
18 | this.children = listeners.map((listener) => {
19 | return new EventListenerView(listener, table)
20 | })
21 | this.collapse()
22 | }
23 |
24 | handleEvents (table) {
25 | this.listenForSelection(table)
26 | this.listenForSelectionKeys(table.parentElement)
27 | this.listenForExpanderKeys(table.parentElement)
28 | }
29 |
30 | render () {
31 | this.eventName.textContent = this.name
32 | this.listenerCount.textContent = `(${this.count})`
33 | }
34 |
35 | filter (searchText) {
36 | this.collapse()
37 |
38 | let matches = this.name.includes(searchText)
39 |
40 | this.children.forEach((child) => {
41 | if (child.filter(searchText)) matches = true
42 | })
43 |
44 | if (matches) {
45 | this.markCollapsed()
46 | this.show()
47 | this.markExpanded()
48 | } else {
49 | this.hide()
50 | }
51 | return matches
52 | }
53 | }
54 |
55 | module.exports = EventView
56 |
--------------------------------------------------------------------------------
/lib/events-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const events = require('./event-helpers')
4 | const EmitterView = require('./emitter-view')
5 | const View = require('./view')
6 |
7 | class EventsView extends View {
8 | constructor () {
9 | super('events-view')
10 | this.children = []
11 | this.handleEvents()
12 | }
13 |
14 | reload () {
15 | this.loadEvents()
16 | }
17 |
18 | focus () {
19 | this.listenersTable.focus()
20 | }
21 |
22 | handleEvents () {
23 | this.loadButton.addEventListener('click', () => this.loadEvents())
24 | this.debounceInput(this.searchBox, () => this.filterEvents())
25 | }
26 |
27 | filterEvents () {
28 | const searchText = this.searchBox.value.toLowerCase()
29 | if (searchText) {
30 | this.children.forEach((child) => {
31 | child.filter(searchText)
32 | })
33 | } else {
34 | this.children.forEach((child) => {
35 | child.show()
36 | child.collapse()
37 | })
38 | }
39 | }
40 |
41 | loadEvents () {
42 | events.getEvents().then((events) => {
43 | this.tableDescription.classList.add('hidden')
44 | this.listenersTable.innerHTML = ''
45 | this.destroyChildren()
46 | this.children = Object.keys(events).map((name) => {
47 | return new EmitterView(name, events[name], this.listenersTable)
48 | })
49 | this.children[0].select()
50 | }).catch((error) => {
51 | console.error('Getting event listeners failed')
52 | console.error(error.stack || error)
53 | })
54 | }
55 | }
56 |
57 | module.exports = EventsView
58 |
--------------------------------------------------------------------------------
/lib/expandable-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const SelectableView = require('./selectable-view')
4 |
5 | class ExpandableView extends SelectableView {
6 | constructor (viewId) {
7 | super(viewId)
8 | this.listenForArrowClicks()
9 | }
10 |
11 | toggleExpansion () {
12 | if (this.expanded) {
13 | this.collapse()
14 | } else {
15 | this.expand()
16 | }
17 | }
18 |
19 | markExpanded () {
20 | this.expanded = true
21 | this.disclosure.classList.add('disclosure-arrow-expanded')
22 | }
23 |
24 | expand () {
25 | this.markExpanded()
26 | this.children.forEach((child) => child.show())
27 | }
28 |
29 | markCollapsed () {
30 | this.expanded = false
31 | this.disclosure.classList.remove('disclosure-arrow-expanded')
32 | }
33 |
34 | collapse () {
35 | this.markCollapsed()
36 | this.children.forEach((child) => child.hide())
37 | }
38 |
39 | collapseAll () {
40 | this.collapse()
41 | this.children.forEach((child) => child.collapse())
42 | }
43 |
44 | hide () {
45 | super.hide()
46 | this.children.forEach((child) => child.hide())
47 | }
48 |
49 | show () {
50 | super.show()
51 | if (this.expanded) this.children.forEach((child) => child.show())
52 | }
53 |
54 | listenForArrowClicks () {
55 | this.disclosure.addEventListener('click', () => this.toggleExpansion())
56 | }
57 |
58 | listenForExpanderKeys (emitter) {
59 | this.bindListener(emitter, 'keydown', (event) => {
60 | if (!this.selected) return
61 | if (event.altKey || event.metaKey || event.ctrlKey) return
62 |
63 | switch (event.code) {
64 | case 'ArrowLeft':
65 | if (this.expanded) {
66 | this.collapse()
67 | } else if (this.parent && this.parent.expanded) {
68 | this.deselect()
69 | this.parent.collapse()
70 | this.parent.select()
71 | }
72 | event.stopImmediatePropagation()
73 | event.preventDefault()
74 | break
75 | case 'ArrowRight':
76 | this.expand()
77 | event.stopImmediatePropagation()
78 | event.preventDefault()
79 | break
80 | }
81 | })
82 | }
83 | }
84 |
85 | module.exports = ExpandableView
86 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const AboutView = require('./about-view')
4 | const Eval = require('./eval')
5 | const EventsView = require('./events-view')
6 | const IpcView = require('./ipc-view')
7 | const LintView = require('./lint-view')
8 | const AccessibilityView = require('./accessibility-view')
9 | const ModulesView = require('./modules-view')
10 | const NodeIntegrationView = require('./node-integration-view')
11 | const SidebarView = require('./sidebar-view')
12 |
13 | document.addEventListener('DOMContentLoaded', () => {
14 | Eval.isApiAvailable().then(function (apiAvailable) {
15 | const sidebarView = new SidebarView()
16 |
17 | if (apiAvailable) {
18 | sidebarView.addPane(new ModulesView())
19 | sidebarView.addPane(new EventsView())
20 | sidebarView.addPane(new IpcView())
21 | sidebarView.addPane(new LintView())
22 | sidebarView.addPane(new AccessibilityView())
23 | sidebarView.addPane(new AboutView())
24 |
25 | listenForLinkClicks()
26 | } else {
27 | sidebarView.addPane(new NodeIntegrationView())
28 | }
29 | })
30 | })
31 |
32 | if (!window.chrome.devtools) {
33 | Eval.proxyToServer()
34 | } else {
35 | Eval.isDebugMode().then(function (debugMode) {
36 | if (debugMode) Eval.startServer()
37 | })
38 | }
39 |
40 | const listenForLinkClicks = () => {
41 | document.body.addEventListener('click', (event) => {
42 | const href = event.target.href
43 | if (href) {
44 | Eval.openExternal(href)
45 | event.stopImmediatePropagation()
46 | event.preventDefault()
47 | }
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/lib/ipc-event-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const highlight = require('highlight.js')
4 | const SelectableView = require('./selectable-view')
5 |
6 | class IpcEventView extends SelectableView {
7 | constructor (event, table) {
8 | super('ipc-table-row')
9 |
10 | this.event = event
11 | this.internalEvent = event.channel.startsWith('ELECTRON_') || event.channel.startsWith('ATOM_')
12 | table.appendChild(this.element)
13 | this.listenForSelection(table)
14 | this.listenForSelectionKeys(table.parentElement)
15 | this.render()
16 | }
17 |
18 | render () {
19 | this.eventName.textContent = this.event.channel
20 | this.eventName.title = this.event.channel
21 |
22 | if (this.event.sent) {
23 | this.eventIcon.classList.add('ipc-icon-sent')
24 | this.eventIcon.title = 'Outgoing'
25 | } else {
26 | this.eventIcon.classList.add('ipc-icon-received')
27 | this.eventIcon.title = 'Incoming'
28 | }
29 |
30 | if (!this.event.sync) {
31 | this.syncIcon.style.display = 'none'
32 | }
33 |
34 | if (this.event.listenerCount > 0) {
35 | this.eventListenerCount.textContent = this.event.listenerCount
36 | }
37 |
38 | this.eventData.textContent = this.event.data
39 | highlight.highlightBlock(this.eventData)
40 | }
41 |
42 | filter (searchText) {
43 | let matches = this.event.channel.toLowerCase().includes(searchText)
44 | matches = matches || this.event.data.toLowerCase().includes(searchText)
45 | matches ? this.show() : this.hide()
46 | }
47 | }
48 |
49 | module.exports = IpcEventView
50 |
--------------------------------------------------------------------------------
/lib/ipc-helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Eval = require('./eval')
4 |
5 | exports.listenForEvents = () => {
6 | return Eval.execute(() => {
7 | // Return if events are already being listened to to prevent duplicates
8 | // when reloading the extension
9 | if (window.__devtron.events != null) {
10 | window.__devtron.events = []
11 | return
12 | }
13 |
14 | window.__devtron.events = []
15 |
16 | const ipcRenderer = require('electron').ipcRenderer
17 |
18 | const ignoredEvents = {
19 | 'ATOM_BROWSER_DEREFERENCE': true,
20 | 'ELECTRON_BROWSER_DEREFERENCE': true
21 | }
22 |
23 | const trackEvent = (channel, args, sent, sync) => {
24 | if (window.__devtron.evaling) return
25 | if (ignoredEvents.hasOwnProperty(channel)) return
26 |
27 | let data
28 | try {
29 | data = JSON.stringify(args)
30 | } catch (error) {
31 | data = `Failed to serialize args to JSON: ${error.message || error}`
32 | }
33 |
34 | window.__devtron.events.push({
35 | channel: channel,
36 | data: data,
37 | listenerCount: ipcRenderer.listenerCount(channel),
38 | sent: !!sent,
39 | sync: !!sync
40 | })
41 | }
42 |
43 | const originalEmit = ipcRenderer.emit
44 | ipcRenderer.emit = function (channel, event) {
45 | const args = Array.prototype.slice.call(arguments, 2)
46 | trackEvent(channel, args)
47 | return originalEmit.apply(ipcRenderer, arguments)
48 | }
49 |
50 | const originalSend = ipcRenderer.send
51 | ipcRenderer.send = function (channel) {
52 | const args = Array.prototype.slice.call(arguments, 1)
53 | trackEvent(channel, args, true)
54 | return originalSend.apply(ipcRenderer, arguments)
55 | }
56 |
57 | const originalSendSync = ipcRenderer.sendSync
58 | ipcRenderer.sendSync = function (channel) {
59 | const args = Array.prototype.slice.call(arguments, 1)
60 | trackEvent(channel, args, true, true)
61 | const returnValue = originalSendSync.apply(ipcRenderer, arguments)
62 | trackEvent(channel, [returnValue], false, true)
63 | return returnValue
64 | }
65 | })
66 | }
67 |
68 | exports.getEvents = () => {
69 | return Eval.execute(() => {
70 | const events = window.__devtron.events
71 | if (events) window.__devtron.events = []
72 | return events
73 | }).then((events) => {
74 | if (events) return events
75 |
76 | // Start listening for events if array is missing meaning
77 | // the window was reloaded
78 | return exports.listenForEvents().then(() => [])
79 | })
80 | }
81 |
--------------------------------------------------------------------------------
/lib/ipc-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Eval = require('./eval')
4 | const ipc = require('./ipc-helpers')
5 | const IpcEventView = require('./ipc-event-view')
6 | const View = require('./view')
7 |
8 | class IpcView extends View {
9 | constructor () {
10 | super('ipc-view')
11 | this.children = []
12 | this.recording = false
13 | this.handleEvents()
14 | }
15 |
16 | handleEvents () {
17 | this.debounceInput(this.searchBox, () => this.filterEvents())
18 | this.clearButton.addEventListener('click', () => this.clear())
19 | this.recordButton.addEventListener('click', () => this.toggleRecording())
20 | this.docsButton.addEventListener('click', () => this.openDocs())
21 | this.hideInternalButton.addEventListener('click', () => this.toggleHideInternal())
22 | }
23 |
24 | toggleHideInternal () {
25 | if (this.hideInternal) {
26 | this.hideInternalButton.classList.remove('active')
27 | this.hideInternal = false
28 | this.children.forEach((child) => {
29 | if (child.internalEvent) child.show()
30 | })
31 | } else {
32 | this.hideInternalButton.classList.add('active')
33 | this.hideInternal = true
34 | this.children.forEach((child) => {
35 | if (child.internalEvent) child.hide()
36 | })
37 | }
38 | }
39 |
40 | toggleRecording () {
41 | if (this.recording) {
42 | this.stopRecording()
43 | this.recordButton.classList.remove('active')
44 | } else {
45 | this.startRecording()
46 | this.recordButton.classList.add('active')
47 | }
48 | }
49 |
50 | startRecording () {
51 | ipc.listenForEvents().then(() => {
52 | this.recording = true
53 | this.addNewEvents()
54 | }).catch((error) => {
55 | console.error('Listening for IPC events failed')
56 | console.error(error.stack || error)
57 | })
58 | }
59 |
60 | stopRecording () {
61 | clearTimeout(this.timeoutId)
62 | this.recording = false
63 | }
64 |
65 | openDocs () {
66 | Eval.openExternal('http://electron.atom.io/docs/latest/api/ipc-main')
67 | }
68 |
69 | clear () {
70 | this.ipcTable.innerHTML = ''
71 | this.destroyChildren()
72 | }
73 |
74 | addNewEvents () {
75 | ipc.getEvents().then((events) => {
76 | if (!this.recording) return
77 | events.forEach((event) => this.addEvent(event))
78 | this.timeoutId = setTimeout(() => this.addNewEvents(), 333)
79 | }).catch((error) => {
80 | console.error('Getting IPC events failed')
81 | console.error(error.stack || error)
82 | })
83 | }
84 |
85 | addEvent (event) {
86 | this.tableDescription.classList.add('hidden')
87 | const eventView = new IpcEventView(event, this.ipcTable)
88 | this.children.push(eventView)
89 | this.filterIncomingEvent(eventView)
90 | }
91 |
92 | filterIncomingEvent (view) {
93 | if (this.hideInternal && view.internalEvent) {
94 | view.hide()
95 | } else {
96 | const searchText = this.getFilterText()
97 | if (searchText) view.filter(searchText)
98 | }
99 | }
100 |
101 | filterEvents () {
102 | const searchText = this.getFilterText()
103 | if (searchText) {
104 | this.children.forEach((child) => child.filter(searchText))
105 | } else {
106 | this.children.forEach((child) => child.show())
107 | }
108 | }
109 |
110 | getFilterText () {
111 | return this.searchBox.value.toLowerCase()
112 | }
113 | }
114 |
115 | module.exports = IpcView
116 |
--------------------------------------------------------------------------------
/lib/lint-helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Eval = require('./eval')
4 |
5 | exports.isUsingAsar = () => {
6 | return Eval.execute(() => {
7 | const mainPath = require('electron').remote.process.mainModule.filename
8 | return /[\\/]app\.asar[\\/]/.test(mainPath)
9 | })
10 | }
11 |
12 | exports.isListeningForCrashEvents = () => {
13 | return Eval.execute(() => {
14 | const webContents = require('electron').remote.getCurrentWebContents()
15 | // For versions less than 1.x.y
16 | // Electron has an crashed listener, so look for more than 1
17 | const crashedForwarding = /^0/.test(process.versions.electron)
18 | const minCount = crashedForwarding ? 1 : 0
19 | return webContents.listenerCount('crashed') > minCount
20 | })
21 | }
22 |
23 | exports.isListeningForUnresponsiveEvents = () => {
24 | return Eval.execute(() => {
25 | const browserWindow = require('electron').remote.getCurrentWindow()
26 | return browserWindow.listenerCount('unresponsive') > 0
27 | })
28 | }
29 |
30 | exports.isListeningForUncaughtExceptionEvents = () => {
31 | return Eval.execute(() => {
32 | const mainProcess = require('electron').remote.process
33 | // Electron has an uncaughtException listener, so look for more than 1
34 | return mainProcess.listenerCount('uncaughtException') > 1
35 | })
36 | }
37 |
38 | exports.getCurrentElectronVersion = () => {
39 | return Eval.execute(() => {
40 | return process.versions.electron
41 | })
42 | }
43 |
44 | exports.getLatestElectronVersion = () => {
45 | return Eval.execute(() => {
46 | return window.__devtron.latestElectronVersion
47 | })
48 | }
49 |
50 | exports.fetchLatestVersion = () => {
51 | return Eval.execute(() => {
52 | window.fetch('https://atom.io/download/atom-shell/index.json')
53 | .then((response) => {
54 | return response.json()
55 | }).then((versions) => {
56 | window.__devtron.latestElectronVersion = versions[0].version
57 | }).catch(() => {
58 | window.__devtron.latestElectronVersion = 'unknown'
59 | })
60 | })
61 | }
62 |
--------------------------------------------------------------------------------
/lib/lint-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const highlight = require('highlight.js')
4 | const Lint = require('./lint-helpers')
5 | const View = require('./view')
6 |
7 | class LintView extends View {
8 | constructor () {
9 | super('lint-view')
10 | this.handleEvents()
11 | this.highlightBlocks()
12 | }
13 |
14 | reload () {
15 | this.lint()
16 | }
17 |
18 | highlightBlocks () {
19 | highlight.highlightBlock(this.crashedExample)
20 | highlight.highlightBlock(this.unresponsiveExample)
21 | highlight.highlightBlock(this.uncaughtExample)
22 | }
23 |
24 | handleEvents () {
25 | this.lintButton.addEventListener('click', () => this.lint())
26 | }
27 |
28 | updateAlert (alertElement, descriptionElement, passing) {
29 | if (passing) {
30 | alertElement.classList.add('alert-lint-pass')
31 | descriptionElement.classList.add('hidden')
32 | } else {
33 | alertElement.classList.add('alert-lint-fail')
34 | descriptionElement.classList.remove('hidden')
35 | }
36 | alertElement.classList.remove('hidden')
37 | this.tableDescription.classList.add('hidden')
38 | }
39 |
40 | lint () {
41 | Lint.isUsingAsar().then((usingAsar) => {
42 | this.updateAlert(this.usingAsar, this.asarDescription, usingAsar)
43 | })
44 |
45 | Lint.isListeningForCrashEvents().then((listening) => {
46 | this.updateAlert(this.crashListener, this.crashDescription, listening)
47 | })
48 |
49 | Lint.isListeningForUnresponsiveEvents().then((listening) => {
50 | this.updateAlert(this.unresponsiveListener, this.unresponsiveDescription, listening)
51 | })
52 |
53 | Lint.isListeningForUncaughtExceptionEvents().then((listening) => {
54 | this.updateAlert(this.uncaughtListener, this.uncaughtDescription, listening)
55 | })
56 |
57 | this.checkVersion()
58 | }
59 |
60 | checkVersion () {
61 | Lint.getCurrentElectronVersion().then((version) => {
62 | this.currentVersion = version
63 | this.updateVersion()
64 | })
65 | Lint.fetchLatestVersion()
66 | this.checkLatestVersion()
67 | }
68 |
69 | checkLatestVersion () {
70 | Lint.getLatestElectronVersion().then((version) => {
71 | if (version) {
72 | this.latestVersion = version
73 | this.updateVersion()
74 | } else {
75 | setTimeout(() => this.checkLatestVersion(), 250)
76 | }
77 | })
78 | }
79 |
80 | updateVersion () {
81 | if (!this.latestVersion || !this.currentVersion) return
82 |
83 | const upToDate = this.latestVersion === this.currentVersion
84 | this.updateAlert(this.outdated, this.outdatedDescription, upToDate)
85 |
86 | this.latestLabel.textContent = this.latestVersion
87 | this.versionLabel.textContent = this.currentVersion
88 | }
89 | }
90 |
91 | module.exports = LintView
92 |
--------------------------------------------------------------------------------
/lib/module-helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Eval = require('./eval')
4 | const Module = require('./module')
5 |
6 | const loadSizes = (mainModule) => {
7 | let totalSize = 0
8 | return Promise.all(mainModule.toArray().map((module) => {
9 | return Eval.getFileSize(module.path).then((size) => {
10 | totalSize += size
11 | return module.setSize(size)
12 | })
13 | })).then(() => {
14 | mainModule.totalSize = totalSize
15 | return mainModule
16 | })
17 | }
18 |
19 | const loadVersions = (mainModule) => {
20 | return Promise.all(mainModule.toArray().map((module) => {
21 | return Eval.getFileVersion(module.path).then((version) => module.setVersion(version))
22 | })).then(() => mainModule)
23 | }
24 |
25 | const createModules = (mainModule) => {
26 | const resourcesPath = mainModule.resourcesPath
27 | const appName = mainModule.appName
28 | const processModule = (node) => {
29 | const module = new Module(node.path, resourcesPath, appName)
30 | node.children.forEach((childNode) => {
31 | module.addChild(processModule(childNode))
32 | })
33 | return module
34 | }
35 |
36 | const convertedMainModule = processModule(mainModule)
37 | convertedMainModule.count = mainModule.count
38 | return convertedMainModule
39 | }
40 |
41 | const getRenderRequireGraph = () => {
42 | return Eval.execute(() => {
43 | let count = 0
44 | const walkModule = (module) => {
45 | count++
46 | let modulePath = module.filename || module.id
47 | if (process.platform === 'win32') {
48 | modulePath = modulePath.replace(/\\/g, '/')
49 | }
50 | return {
51 | path: modulePath,
52 | children: module.children.map(walkModule)
53 | }
54 | }
55 | const mainModule = walkModule(process.mainModule)
56 | mainModule.resourcesPath = process.resourcesPath
57 | mainModule.appName = require('electron').remote.app.getName()
58 | mainModule.count = count
59 | return mainModule
60 | })
61 | }
62 |
63 | const getMainRequireGraph = () => {
64 | return Eval.execute(() => {
65 | let process = require('electron').remote.process
66 | let count = 0
67 | const walkModule = (module) => {
68 | count++
69 | let modulePath = module.filename || module.id
70 | if (process.platform === 'win32') {
71 | modulePath = modulePath.replace(/\\/g, '/')
72 | }
73 | return {
74 | path: modulePath,
75 | children: module.children.map(walkModule)
76 | }
77 | }
78 | const mainModule = walkModule(process.mainModule)
79 | mainModule.resourcesPath = process.resourcesPath
80 | mainModule.appName = require('electron').remote.app.getName()
81 | mainModule.count = count
82 | return mainModule
83 | })
84 | }
85 |
86 | exports.getRenderModules = () => {
87 | return getRenderRequireGraph().then(createModules).then(loadSizes).then(loadVersions)
88 | }
89 |
90 | exports.getMainModules = () => {
91 | return getMainRequireGraph().then(createModules).then(loadSizes).then(loadVersions)
92 | }
93 |
--------------------------------------------------------------------------------
/lib/module-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const ExpandableView = require('./expandable-view')
4 | const Humanize = require('humanize-plus')
5 |
6 | class ModuleView extends ExpandableView {
7 | constructor (module, table, parent) {
8 | super('requires-table-row')
9 |
10 | this.parent = parent
11 | this.module = module
12 | this.table = table
13 |
14 | table.appendChild(this.element)
15 | this.render()
16 | this.children = this.module.children.map((child) => {
17 | return new ModuleView(child, table, this)
18 | })
19 | this.module.getDepth() === 1 ? this.expand() : this.collapse()
20 |
21 | if (!this.module.hasChildren()) this.disclosure.style.display = 'none'
22 |
23 | this.handleEvents()
24 | }
25 |
26 | handleEvents () {
27 | this.listenForSelection(this.table)
28 | this.listenForSelectionKeys(this.table.parentElement)
29 | this.listenForExpanderKeys(this.table.parentElement)
30 | }
31 |
32 | getHumanizedSize () {
33 | const size = this.module.getSize()
34 | return Humanize.fileSize(size).replace('bytes', 'B')
35 | }
36 |
37 | render () {
38 | this.moduleName.textContent = this.module.getLibrary()
39 | this.moduleName.title = this.module.getLibrary()
40 | this.moduleVersion.textContent = this.module.getVersion()
41 | this.fileSize.textContent = this.getHumanizedSize()
42 | this.fileName.textContent = this.module.getName()
43 | this.fileName.title = this.module.path
44 | this.moduleDirectory.textContent = this.module.getDirectory()
45 | this.moduleDirectory.title = this.module.path
46 | this.pathSection.style['padding-left'] = `${(this.module.getDepth()) * 15}px`
47 | }
48 |
49 | filter (searchText) {
50 | this.collapse()
51 |
52 | let matches = this.module.getId().includes(searchText)
53 | matches = matches || this.module.getName().toLowerCase().includes(searchText)
54 |
55 | this.children.forEach((child) => {
56 | if (child.filter(searchText)) matches = true
57 | })
58 |
59 | if (matches) {
60 | this.markCollapsed()
61 | this.show()
62 | this.markExpanded()
63 | } else {
64 | this.hide()
65 | }
66 | return matches
67 | }
68 | }
69 |
70 | module.exports = ModuleView
71 |
--------------------------------------------------------------------------------
/lib/module.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | class Module {
4 | constructor (path, resourcesPath, appName) {
5 | this.path = path
6 | this.resourcesPath = resourcesPath
7 | this.appName = appName
8 | this.size = -1
9 | this.version = ''
10 | this.children = []
11 | }
12 |
13 | setVersion (version) {
14 | this.version = version
15 | return this
16 | }
17 |
18 | getVersion () {
19 | return this.version
20 | }
21 |
22 | setSize (size) {
23 | this.size = size
24 | return this
25 | }
26 |
27 | getSize () {
28 | return this.size
29 | }
30 |
31 | hasChildren () {
32 | return this.children.length > 0
33 | }
34 |
35 | addChild (child) {
36 | this.children.push(child)
37 | child.parent = this
38 | }
39 |
40 | getPath () {
41 | return this.path
42 | }
43 |
44 | getDepth () {
45 | let depth = 1
46 | let parent = this.parent
47 | while (parent != null) {
48 | depth++
49 | parent = parent.parent
50 | }
51 | return depth
52 | }
53 |
54 | getName () {
55 | if (!this.name) this.name = /\/([^\/]+)$/.exec(this.path)[1]
56 | return this.name
57 | }
58 |
59 | getDirectory () {
60 | let directoryPath = /(.+)\/[^\/]+$/.exec(this.path)[1]
61 | if (directoryPath.indexOf(this.resourcesPath) === 0) {
62 | directoryPath = directoryPath.substring(this.resourcesPath.length + 1)
63 | }
64 | return directoryPath
65 | }
66 |
67 | computeLibrary () {
68 | if (/\/atom\.asar\/(browser|common|renderer)\//.test(this.path)) return 'Electron'
69 |
70 | const libraryPattern = /\/node_modules\/([^\/]+)(?=\/)/g
71 | let match = libraryPattern.exec(this.path)
72 | while (match != null) {
73 | let library = match[1]
74 | match = libraryPattern.exec(this.path)
75 | if (match == null) return library
76 | }
77 |
78 | return this.appName
79 | }
80 |
81 | getLibrary () {
82 | if (!this.library) this.library = this.computeLibrary()
83 | return this.library
84 | }
85 |
86 | getId () {
87 | if (!this.id) this.id = this.getLibrary().toLowerCase()
88 | return this.id
89 | }
90 |
91 | visit (callback) {
92 | callback(this)
93 | this.children.forEach((child) => child.visit(callback))
94 | }
95 |
96 | toArray () {
97 | const modules = []
98 | this.visit((module) => modules.push(module))
99 | return modules
100 | }
101 | }
102 |
103 | module.exports = Module
104 |
--------------------------------------------------------------------------------
/lib/modules-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Humanize = require('humanize-plus')
4 | const modules = require('./module-helpers')
5 | const ModuleView = require('./module-view')
6 | const View = require('./view')
7 |
8 | class ModulesView extends View {
9 | constructor () {
10 | super('modules-view')
11 | this.handleEvents()
12 | }
13 |
14 | reload () {
15 | this.loadGraph()
16 | }
17 |
18 | focus () {
19 | if (this.mainProcessTable.classList.contains('hidden')) {
20 | this.renderRequireRows.focus()
21 | } else {
22 | this.mainRequireRows.focus()
23 | }
24 | }
25 |
26 | handleEvents () {
27 | this.loadButton.addEventListener('click', () => this.loadGraph())
28 | this.debounceInput(this.searchBox, () => this.filterGraph())
29 |
30 | this.mainProcessTab.addEventListener('click', () => {
31 | this.mainProcessTab.classList.add('active')
32 | this.renderProcessTab.classList.remove('active')
33 |
34 | this.mainProcessTable.classList.remove('hidden')
35 | this.renderProcessTable.classList.add('hidden')
36 |
37 | this.mainRequireRows.focus()
38 | })
39 |
40 | this.renderProcessTab.addEventListener('click', () => {
41 | this.mainProcessTab.classList.remove('active')
42 | this.renderProcessTab.classList.add('active')
43 |
44 | this.mainProcessTable.classList.add('hidden')
45 | this.renderProcessTable.classList.remove('hidden')
46 |
47 | this.renderRequireRows.focus()
48 | })
49 | }
50 |
51 | getTabLabelSuffix (mainModule) {
52 | const count = mainModule.count.toLocaleString()
53 | const size = Humanize.fileSize(mainModule.totalSize)
54 | return `- ${count} files, ${size}`
55 | }
56 |
57 | loadGraph () {
58 | modules.getRenderModules().then((mainModule) => {
59 | this.tableDescription.classList.add('hidden')
60 | const suffix = this.getTabLabelSuffix(mainModule)
61 | this.renderProcessTab.textContent = `Renderer Process ${suffix}`
62 | this.renderRequireRows.innerHTML = ''
63 | if (this.rootRenderView) this.rootRenderView.destroy()
64 | this.rootRenderView = new ModuleView(mainModule, this.renderRequireRows)
65 | this.rootRenderView.select()
66 | }).catch((error) => {
67 | console.error('Loading render modules failed')
68 | console.error(error.stack || error)
69 | })
70 |
71 | modules.getMainModules().then((mainModule) => {
72 | const suffix = this.getTabLabelSuffix(mainModule)
73 | this.mainProcessTab.textContent = `Main Process ${suffix}`
74 | this.mainRequireRows.innerHTML = ''
75 | if (this.rootMainView) this.rootMainView.destroy()
76 | this.rootMainView = new ModuleView(mainModule, this.mainRequireRows)
77 | this.rootMainView.select()
78 | }).catch((error) => {
79 | console.error('Loading main modules failed')
80 | console.error(error.stack || error)
81 | })
82 | }
83 |
84 | filterGraph () {
85 | const searchText = this.searchBox.value.toLowerCase()
86 | if (searchText) {
87 | this.rootRenderView.filter(searchText)
88 | this.rootMainView.filter(searchText)
89 | } else {
90 | this.rootRenderView.collapseAll()
91 | this.rootRenderView.expand()
92 | this.rootMainView.collapseAll()
93 | this.rootMainView.expand()
94 | }
95 | }
96 | }
97 |
98 | module.exports = ModulesView
99 |
--------------------------------------------------------------------------------
/lib/node-integration-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const highlight = require('highlight.js')
4 | const View = require('./view')
5 |
6 | class NodeIntegrationView extends View {
7 | constructor () {
8 | super('node-integration-view')
9 | this.highlightBlocks()
10 | }
11 |
12 | highlightBlocks () {
13 | highlight.highlightBlock(this.browserWindowExample)
14 | highlight.highlightBlock(this.devtronExample)
15 | highlight.highlightBlock(this.envCheckExample)
16 | }
17 | }
18 |
19 | module.exports = NodeIntegrationView
20 |
--------------------------------------------------------------------------------
/lib/selectable-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const View = require('./view')
4 |
5 | class SelectableView extends View {
6 | select () {
7 | this.selected = true
8 | this.element.classList.add('active')
9 | this.element.scrollIntoViewIfNeeded()
10 | }
11 |
12 | deselect () {
13 | this.selected = false
14 | this.element.classList.remove('active')
15 | }
16 |
17 | selectNext () {
18 | let next = this.element.nextElementSibling
19 | while (next && (next.view instanceof SelectableView)) {
20 | if (next.view.isHidden()) {
21 | next = next.nextElementSibling
22 | continue
23 | }
24 | this.deselect()
25 | next.view.select()
26 | break
27 | }
28 | }
29 |
30 | selectPrevious () {
31 | let previous = this.element.previousElementSibling
32 | while (previous && (previous.view instanceof SelectableView)) {
33 | if (previous.view.isHidden()) {
34 | previous = previous.previousElementSibling
35 | continue
36 | }
37 | this.deselect()
38 | previous.view.select()
39 | break
40 | }
41 | }
42 |
43 | listenForSelection (emitter) {
44 | this.bindListener(emitter, 'mousedown', (event) => {
45 | if (this.element.contains(event.target)) {
46 | this.select()
47 | } else {
48 | this.deselect()
49 | }
50 | })
51 | }
52 |
53 | listenForSelectionKeys (emitter) {
54 | this.bindListener(emitter, 'keydown', (event) => {
55 | if (!this.selected) return
56 | if (event.altKey || event.metaKey || event.ctrlKey) return
57 |
58 | switch (event.code) {
59 | case 'ArrowDown':
60 | this.selectNext()
61 | event.stopImmediatePropagation()
62 | event.preventDefault()
63 | break
64 | case 'ArrowUp':
65 | this.selectPrevious()
66 | event.stopImmediatePropagation()
67 | event.preventDefault()
68 | break
69 | }
70 | })
71 | }
72 | }
73 |
74 | module.exports = SelectableView
75 |
--------------------------------------------------------------------------------
/lib/sidebar-view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const View = require('./view')
4 |
5 | class SidebarView extends View {
6 | constructor () {
7 | super('sidebar-view')
8 | this.panes = []
9 | this.links = [this.requireLink, this.eventsLink, this.ipcLink, this.lintLink, this.accessibilityLink, this.aboutLink]
10 | this.panesElement = document.querySelector('#pane-group')
11 | this.panesElement.appendChild(this.element)
12 | this.handleEvents()
13 | }
14 |
15 | handleEvents () {
16 | document.body.addEventListener('keydown', (event) => {
17 | if (event.ctrlKey || event.metaKey) return
18 | if (!event.altKey) return
19 |
20 | switch (event.code) {
21 | case 'ArrowDown':
22 | this.selectNext()
23 | event.stopImmediatePropagation()
24 | event.preventDefault()
25 | break
26 | case 'ArrowUp':
27 | this.selectPrevious()
28 | event.stopImmediatePropagation()
29 | event.preventDefault()
30 | break
31 | }
32 | })
33 |
34 | document.body.addEventListener('keydown', (event) => {
35 | if ((event.ctrlKey || event.metaKey) && event.code === 'KeyE') {
36 | this.activePane.reload()
37 | this.activePane.focus()
38 | event.stopImmediatePropagation()
39 | event.preventDefault()
40 | }
41 | })
42 |
43 | this.element.addEventListener('mousedown', (event) => {
44 | let paneLink = event.target.dataset.paneLink
45 | if (paneLink) this.selectPane(paneLink)
46 | })
47 | }
48 |
49 | activateLink (name) {
50 | this.links.forEach((link) => {
51 | if (link.dataset.paneLink === name) {
52 | link.classList.add('active')
53 | } else {
54 | link.classList.remove('active')
55 | }
56 | })
57 | }
58 |
59 | addPane (view) {
60 | if (this.panes.length === 0) this.activePane = view
61 | this.panes.push(view)
62 | this.panesElement.appendChild(view.element)
63 | }
64 |
65 | findPane (name) {
66 | return this.panes.find((view) => view.element.dataset.pane === name)
67 | }
68 |
69 | selectPane (name) {
70 | const pane = this.findPane(name)
71 | if (!pane) return
72 |
73 | this.panes.forEach((view) => view.hide())
74 |
75 | pane.show()
76 | pane.focus()
77 | this.activePane = pane
78 |
79 | this.activateLink(name)
80 | }
81 |
82 | selectPrevious () {
83 | const selectedIndex = this.panes.indexOf(this.activePane)
84 | const previousIndex = Math.max(selectedIndex - 1, 0)
85 | this.selectPane(this.panes[previousIndex].element.dataset.pane)
86 | }
87 |
88 | selectNext () {
89 | const selectedIndex = this.panes.indexOf(this.activePane)
90 | const nextIndex = Math.min(selectedIndex + 1, this.panes.length - 1)
91 | this.selectPane(this.panes[nextIndex].element.dataset.pane)
92 | }
93 | }
94 |
95 | module.exports = SidebarView
96 |
--------------------------------------------------------------------------------
/lib/view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | class View {
4 | static queryForEach (element, selector, callback) {
5 | const elements = element.querySelectorAll(selector)
6 | Array.prototype.forEach.call(elements, callback)
7 | }
8 |
9 | constructor (viewId) {
10 | this.id = viewId
11 | this.listeners = []
12 | this.element = this.createElement()
13 | this.element.view = this
14 | this.bindFields()
15 | }
16 |
17 | destroy () {
18 | this.listeners.forEach((destroy) => destroy())
19 | this.listeners = []
20 | this.destroyChildren()
21 | }
22 |
23 | destroyChildren () {
24 | if (this.children) {
25 | this.children.forEach((child) => child.destroy())
26 | this.children = []
27 | }
28 | }
29 |
30 | bindFields () {
31 | View.queryForEach(this.element, '[data-field]', (propertyElement) => {
32 | this[propertyElement.dataset.field] = propertyElement
33 | })
34 | }
35 |
36 | bindListener (emitter, event, callback) {
37 | emitter.addEventListener(event, callback)
38 | this.listeners.push(function () {
39 | emitter.removeEventListener(event, callback)
40 | })
41 | }
42 |
43 | createElement () {
44 | const template = document.querySelector(`#${this.id}`).content
45 | return document.importNode(template, true).firstElementChild
46 | }
47 |
48 | isHidden () {
49 | return this.element.classList.contains('hidden')
50 | }
51 |
52 | hide () {
53 | this.element.classList.add('hidden')
54 | }
55 |
56 | show () {
57 | this.element.classList.remove('hidden')
58 | }
59 |
60 | focus () {
61 | this.element.focus()
62 | }
63 |
64 | debounceInput (emitter, callback) {
65 | this.debounceEvent(emitter, 'input', 250, callback)
66 | }
67 |
68 | debounceEvent (emitter, eventName, interval, callback) {
69 | let timeoutId
70 | this.bindListener(emitter, eventName, (event) => {
71 | window.clearTimeout(timeoutId)
72 | timeoutId = setTimeout(() => callback(event), interval)
73 | })
74 | }
75 |
76 | reload () {
77 | // Does nothing, subclasses should reimplement
78 | }
79 | }
80 |
81 | module.exports = View
82 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devtron",
3 | "version": "1.0",
4 | "devtools_page": "static/devtron.html",
5 | "content_scripts": [
6 | {
7 | "matches": ["*"],
8 | "js": ["out/browser-globals.js"]
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/out/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/out/.gitkeep
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devtron",
3 | "version": "1.4.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "anymatch": {
8 | "version": "1.3.0",
9 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
10 | "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=",
11 | "dev": true,
12 | "requires": {
13 | "arrify": "1.0.1",
14 | "micromatch": "2.3.11"
15 | }
16 | },
17 | "arr-diff": {
18 | "version": "2.0.0",
19 | "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
20 | "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
21 | "dev": true,
22 | "requires": {
23 | "arr-flatten": "1.1.0"
24 | }
25 | },
26 | "arr-flatten": {
27 | "version": "1.1.0",
28 | "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
29 | "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
30 | "dev": true
31 | },
32 | "array-unique": {
33 | "version": "0.2.1",
34 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
35 | "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
36 | "dev": true
37 | },
38 | "arrify": {
39 | "version": "1.0.1",
40 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
41 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
42 | "dev": true
43 | },
44 | "balanced-match": {
45 | "version": "1.0.0",
46 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
47 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
48 | "dev": true
49 | },
50 | "brace-expansion": {
51 | "version": "1.1.8",
52 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
53 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
54 | "dev": true,
55 | "requires": {
56 | "balanced-match": "1.0.0",
57 | "concat-map": "0.0.1"
58 | }
59 | },
60 | "braces": {
61 | "version": "1.8.5",
62 | "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
63 | "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
64 | "dev": true,
65 | "requires": {
66 | "expand-range": "1.8.2",
67 | "preserve": "0.2.0",
68 | "repeat-element": "1.1.2"
69 | }
70 | },
71 | "check-for-leaks": {
72 | "version": "1.2.0",
73 | "resolved": "https://registry.npmjs.org/check-for-leaks/-/check-for-leaks-1.2.0.tgz",
74 | "integrity": "sha512-bJ2Bzo6RtsYqamMnsjtVzowGvBNVrR5IPK8Bd+lx5W1TNgOKMsF+AyNHVkqFqO7cpDZNfny5SaqH6gEovpV5Gw==",
75 | "dev": true,
76 | "requires": {
77 | "anymatch": "1.3.0",
78 | "minimist": "1.2.0",
79 | "parse-gitignore": "0.4.0",
80 | "walk-sync": "0.3.2"
81 | }
82 | },
83 | "ci-info": {
84 | "version": "1.0.0",
85 | "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.0.0.tgz",
86 | "integrity": "sha1-3FKF8rTiUYIWg2gcOBwziPRuxTQ=",
87 | "dev": true
88 | },
89 | "concat-map": {
90 | "version": "0.0.1",
91 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
92 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
93 | "dev": true
94 | },
95 | "ensure-posix-path": {
96 | "version": "1.0.2",
97 | "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.0.2.tgz",
98 | "integrity": "sha1-pls+QtC3HPxYXrd0+ZQ8jZuRsMI=",
99 | "dev": true
100 | },
101 | "expand-brackets": {
102 | "version": "0.1.5",
103 | "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
104 | "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
105 | "dev": true,
106 | "requires": {
107 | "is-posix-bracket": "0.1.1"
108 | }
109 | },
110 | "expand-range": {
111 | "version": "1.8.2",
112 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
113 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
114 | "dev": true,
115 | "requires": {
116 | "fill-range": "2.2.3"
117 | }
118 | },
119 | "extglob": {
120 | "version": "0.3.2",
121 | "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
122 | "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
123 | "dev": true,
124 | "requires": {
125 | "is-extglob": "1.0.0"
126 | }
127 | },
128 | "filename-regex": {
129 | "version": "2.0.1",
130 | "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
131 | "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
132 | "dev": true
133 | },
134 | "fill-range": {
135 | "version": "2.2.3",
136 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
137 | "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
138 | "dev": true,
139 | "requires": {
140 | "is-number": "2.1.0",
141 | "isobject": "2.1.0",
142 | "randomatic": "1.1.7",
143 | "repeat-element": "1.1.2",
144 | "repeat-string": "1.6.1"
145 | }
146 | },
147 | "for-in": {
148 | "version": "1.0.2",
149 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
150 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
151 | "dev": true
152 | },
153 | "for-own": {
154 | "version": "0.1.5",
155 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
156 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
157 | "dev": true,
158 | "requires": {
159 | "for-in": "1.0.2"
160 | }
161 | },
162 | "glob-base": {
163 | "version": "0.3.0",
164 | "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
165 | "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
166 | "dev": true,
167 | "requires": {
168 | "glob-parent": "2.0.0",
169 | "is-glob": "2.0.1"
170 | }
171 | },
172 | "glob-parent": {
173 | "version": "2.0.0",
174 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
175 | "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
176 | "dev": true,
177 | "requires": {
178 | "is-glob": "2.0.1"
179 | }
180 | },
181 | "husky": {
182 | "version": "0.14.3",
183 | "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz",
184 | "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==",
185 | "dev": true,
186 | "requires": {
187 | "is-ci": "1.0.10",
188 | "normalize-path": "1.0.0",
189 | "strip-indent": "2.0.0"
190 | },
191 | "dependencies": {
192 | "normalize-path": {
193 | "version": "1.0.0",
194 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz",
195 | "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=",
196 | "dev": true
197 | }
198 | }
199 | },
200 | "is-buffer": {
201 | "version": "1.1.5",
202 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
203 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
204 | "dev": true
205 | },
206 | "is-ci": {
207 | "version": "1.0.10",
208 | "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
209 | "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=",
210 | "dev": true,
211 | "requires": {
212 | "ci-info": "1.0.0"
213 | }
214 | },
215 | "is-dotfile": {
216 | "version": "1.0.3",
217 | "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
218 | "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
219 | "dev": true
220 | },
221 | "is-equal-shallow": {
222 | "version": "0.1.3",
223 | "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
224 | "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
225 | "dev": true,
226 | "requires": {
227 | "is-primitive": "2.0.0"
228 | }
229 | },
230 | "is-extendable": {
231 | "version": "0.1.1",
232 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
233 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
234 | "dev": true
235 | },
236 | "is-extglob": {
237 | "version": "1.0.0",
238 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
239 | "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
240 | "dev": true
241 | },
242 | "is-glob": {
243 | "version": "2.0.1",
244 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
245 | "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
246 | "dev": true,
247 | "requires": {
248 | "is-extglob": "1.0.0"
249 | }
250 | },
251 | "is-number": {
252 | "version": "2.1.0",
253 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
254 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
255 | "dev": true,
256 | "requires": {
257 | "kind-of": "3.2.2"
258 | }
259 | },
260 | "is-posix-bracket": {
261 | "version": "0.1.1",
262 | "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
263 | "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
264 | "dev": true
265 | },
266 | "is-primitive": {
267 | "version": "2.0.0",
268 | "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
269 | "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
270 | "dev": true
271 | },
272 | "isarray": {
273 | "version": "1.0.0",
274 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
275 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
276 | "dev": true
277 | },
278 | "isobject": {
279 | "version": "2.1.0",
280 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
281 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
282 | "dev": true,
283 | "requires": {
284 | "isarray": "1.0.0"
285 | }
286 | },
287 | "kind-of": {
288 | "version": "3.2.2",
289 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
290 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
291 | "dev": true,
292 | "requires": {
293 | "is-buffer": "1.1.5"
294 | }
295 | },
296 | "matcher-collection": {
297 | "version": "1.0.4",
298 | "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-1.0.4.tgz",
299 | "integrity": "sha1-L2auCGmZbynkPQtiyD3R1D5YF1U=",
300 | "dev": true,
301 | "requires": {
302 | "minimatch": "3.0.4"
303 | }
304 | },
305 | "micromatch": {
306 | "version": "2.3.11",
307 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
308 | "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
309 | "dev": true,
310 | "requires": {
311 | "arr-diff": "2.0.0",
312 | "array-unique": "0.2.1",
313 | "braces": "1.8.5",
314 | "expand-brackets": "0.1.5",
315 | "extglob": "0.3.2",
316 | "filename-regex": "2.0.1",
317 | "is-extglob": "1.0.0",
318 | "is-glob": "2.0.1",
319 | "kind-of": "3.2.2",
320 | "normalize-path": "2.1.1",
321 | "object.omit": "2.0.1",
322 | "parse-glob": "3.0.4",
323 | "regex-cache": "0.4.3"
324 | }
325 | },
326 | "minimatch": {
327 | "version": "3.0.4",
328 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
329 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
330 | "dev": true,
331 | "requires": {
332 | "brace-expansion": "1.1.8"
333 | }
334 | },
335 | "minimist": {
336 | "version": "1.2.0",
337 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
338 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
339 | "dev": true
340 | },
341 | "normalize-path": {
342 | "version": "2.1.1",
343 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
344 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
345 | "dev": true,
346 | "requires": {
347 | "remove-trailing-separator": "1.0.2"
348 | }
349 | },
350 | "object.omit": {
351 | "version": "2.0.1",
352 | "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
353 | "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
354 | "dev": true,
355 | "requires": {
356 | "for-own": "0.1.5",
357 | "is-extendable": "0.1.1"
358 | }
359 | },
360 | "parse-gitignore": {
361 | "version": "0.4.0",
362 | "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-0.4.0.tgz",
363 | "integrity": "sha1-q/cC5LkAUk//eQK2g4YoV7Y/k/4=",
364 | "dev": true,
365 | "requires": {
366 | "array-unique": "0.3.2",
367 | "is-glob": "3.1.0"
368 | },
369 | "dependencies": {
370 | "array-unique": {
371 | "version": "0.3.2",
372 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
373 | "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
374 | "dev": true
375 | },
376 | "is-extglob": {
377 | "version": "2.1.1",
378 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
379 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
380 | "dev": true
381 | },
382 | "is-glob": {
383 | "version": "3.1.0",
384 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
385 | "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
386 | "dev": true,
387 | "requires": {
388 | "is-extglob": "2.1.1"
389 | }
390 | }
391 | }
392 | },
393 | "parse-glob": {
394 | "version": "3.0.4",
395 | "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
396 | "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
397 | "dev": true,
398 | "requires": {
399 | "glob-base": "0.3.0",
400 | "is-dotfile": "1.0.3",
401 | "is-extglob": "1.0.0",
402 | "is-glob": "2.0.1"
403 | }
404 | },
405 | "preserve": {
406 | "version": "0.2.0",
407 | "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
408 | "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
409 | "dev": true
410 | },
411 | "randomatic": {
412 | "version": "1.1.7",
413 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
414 | "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
415 | "dev": true,
416 | "requires": {
417 | "is-number": "3.0.0",
418 | "kind-of": "4.0.0"
419 | },
420 | "dependencies": {
421 | "is-number": {
422 | "version": "3.0.0",
423 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
424 | "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
425 | "dev": true,
426 | "requires": {
427 | "kind-of": "3.2.2"
428 | },
429 | "dependencies": {
430 | "kind-of": {
431 | "version": "3.2.2",
432 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
433 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
434 | "dev": true,
435 | "requires": {
436 | "is-buffer": "1.1.5"
437 | }
438 | }
439 | }
440 | },
441 | "kind-of": {
442 | "version": "4.0.0",
443 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
444 | "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
445 | "dev": true,
446 | "requires": {
447 | "is-buffer": "1.1.5"
448 | }
449 | }
450 | }
451 | },
452 | "regex-cache": {
453 | "version": "0.4.3",
454 | "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz",
455 | "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=",
456 | "dev": true,
457 | "requires": {
458 | "is-equal-shallow": "0.1.3",
459 | "is-primitive": "2.0.0"
460 | }
461 | },
462 | "remove-trailing-separator": {
463 | "version": "1.0.2",
464 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz",
465 | "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=",
466 | "dev": true
467 | },
468 | "repeat-element": {
469 | "version": "1.1.2",
470 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
471 | "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
472 | "dev": true
473 | },
474 | "repeat-string": {
475 | "version": "1.6.1",
476 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
477 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
478 | "dev": true
479 | },
480 | "strip-indent": {
481 | "version": "2.0.0",
482 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
483 | "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
484 | "dev": true
485 | },
486 | "walk-sync": {
487 | "version": "0.3.2",
488 | "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.3.2.tgz",
489 | "integrity": "sha512-FMB5VqpLqOCcqrzA9okZFc0wq0Qbmdm396qJxvQZhDpyu0W95G9JCmp74tx7iyYnyOcBtUuKJsgIKAqjozvmmQ==",
490 | "dev": true,
491 | "requires": {
492 | "ensure-posix-path": "1.0.2",
493 | "matcher-collection": "1.0.4"
494 | }
495 | }
496 | }
497 | }
498 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devtron",
3 | "version": "1.4.0",
4 | "description": "Electron DevTools Extension",
5 | "main": "./api.js",
6 | "scripts": {
7 | "prepublish": "browserify lib/*.js -o out/index.js --ignore-missing --entry lib/index.js && browserify lib/browser-globals.js -o out/browser-globals.js",
8 | "start": "browserify lib/browser-globals.js -o out/browser-globals.js && watchify lib/*.js -o out/index.js --ignore-missing --entry lib/index.js --verbose",
9 | "test": "mocha test/unit/*-test.js test/integration/*-test.js && standard",
10 | "prepack": "check-for-leaks",
11 | "prepush": "check-for-leaks"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/electron/devtron.git"
16 | },
17 | "author": "Kevin Sawicki",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/electron/devtron/issues"
21 | },
22 | "keywords": [
23 | "Electron",
24 | "Chrome",
25 | "Chromium",
26 | "devtools",
27 | "developer tools"
28 | ],
29 | "homepage": "https://github.com/electron/devtron#readme",
30 | "devDependencies": {
31 | "body-parser": "^1.15.0",
32 | "browserify": "^13.0.0",
33 | "chai": "^3.5.0",
34 | "chai-as-promised": "^5.3.0",
35 | "check-for-leaks": "^1.2.0",
36 | "cors": "^2.7.1",
37 | "electron-prebuilt": "~1.2.2",
38 | "express": "^4.13.4",
39 | "husky": "^0.14.3",
40 | "mocha": "^2.4.5",
41 | "spectron": "~3.2.3",
42 | "standard": "^6.0.8",
43 | "watchify": "^3.7.0"
44 | },
45 | "dependencies": {
46 | "accessibility-developer-tools": "^2.11.0",
47 | "highlight.js": "^9.3.0",
48 | "humanize-plus": "^1.8.1"
49 | },
50 | "standard": {
51 | "ignore": [
52 | "/out/index.js"
53 | ]
54 | }
55 | }
--------------------------------------------------------------------------------
/static/devtron.css:
--------------------------------------------------------------------------------
1 | .list-group-item:last-child,
2 | .list-group-item:first-child {
3 | border-radius: 0;
4 | }
5 |
6 | .list-group-item {
7 | background: transparent;
8 | border-bottom-width: 0;
9 | border-left-width: 0;
10 | border-right-width: 0;
11 | font-weight: bold;
12 | }
13 |
14 | .list-group-item.active:hover {
15 | background-color: #116cd6;
16 | }
17 |
18 | .row-file,
19 | .row-event-data,
20 | .row-listener {
21 | width: 100%;
22 | }
23 |
24 | .row-module {
25 | min-width: 150px;
26 | max-width: 150px;
27 | }
28 |
29 | .row-event-name {
30 | min-width: 300px;
31 | max-width: 300px;
32 | }
33 |
34 | .row-size,
35 | .row-event-listener-count {
36 | min-width: 70px;
37 | max-width: 70px;
38 | }
39 |
40 | .row-event-data pre {
41 | border-width: 0;
42 | }
43 |
44 | .row-listener-event-code code,
45 | .lint-code code {
46 | padding: 5px;
47 | }
48 |
49 | .disclosure-arrow {
50 | width: 16px;
51 | height: 16px;
52 | margin-right: 2px;
53 | background-color: #666;
54 | float: left;
55 | -webkit-mask-image: url(../vendor/entypo/triangle-right.svg);
56 | }
57 |
58 | .active .disclosure-arrow {
59 | border-left-color: #fff;
60 | }
61 |
62 | .disclosure-arrow-expanded {
63 | -webkit-mask-image: url(../vendor/entypo/triangle-down.svg);
64 | }
65 |
66 | .active .disclosure-arrow,
67 | .active .ipc-icon {
68 | background-color: white;
69 | }
70 |
71 | .table-striped tr:nth-child(even),
72 | .table-striped > tbody > tr:nth-of-type(odd) {
73 | background-color: #fff;
74 | }
75 |
76 | .table-striped > tbody > tr.active,
77 | .table-striped > tbody > tr.active .row-directory,
78 | .table-striped > tbody > tr.active .listener-count {
79 | background-color: #116cd6;
80 | color: #fff;
81 | }
82 |
83 | table:focus,
84 | tbody:focus,
85 | .toolbar-actions .btn:focus {
86 | outline: 0 none;
87 | }
88 |
89 | .table-scroller,
90 | .table-scroller-with-tabs {
91 | display: block;
92 | overflow: scroll;
93 | position: absolute;
94 | top: 35px;
95 | bottom: 0;
96 | left: 0;
97 | right: 0;
98 | }
99 |
100 | .table-scroller-with-tabs {
101 | top: 62px;
102 | }
103 |
104 | .row-directory,
105 | .listener-count {
106 | color: #bbb;
107 | display: inline-block;
108 | margin-left: 5px;
109 | }
110 |
111 | .row-directory:before {
112 | content: '-';
113 | padding-right: 5px;
114 | }
115 |
116 | .sidebar-icon {
117 | width: 16px;
118 | height: 16px;
119 | margin-right: 10px;
120 | background-color: black;
121 | float: left;
122 | }
123 |
124 | .row-listener-event-name {
125 | padding-left: 30px;
126 | }
127 |
128 | .row-listener-event-code,
129 | .row-element-path {
130 | padding-left: 45px;
131 | }
132 |
133 | .row-element-path a {
134 | cursor: pointer;
135 | }
136 | tr.active .row-element-path a {
137 | color: white;
138 | }
139 |
140 | .active .sidebar-icon {
141 | background-color: white;
142 | }
143 |
144 | .sidebar-icon-graph {
145 | -webkit-mask-image: url(../vendor/entypo/flow-tree.svg);
146 | }
147 |
148 | .sidebar-icon-events {
149 | -webkit-mask-image: url(../vendor/entypo/old-phone.svg);
150 | }
151 |
152 | .sidebar-icon-ipc {
153 | -webkit-mask-image: url(../vendor/entypo/swap.svg);
154 | }
155 |
156 | .sidebar-icon-lint {
157 | -webkit-mask-image: url(../vendor/entypo/warning.svg);
158 | }
159 |
160 | .sidebar-icon-about {
161 | -webkit-mask-image: url(../vendor/entypo/help-with-circle.svg);
162 | }
163 |
164 | .sidebar-icon-accessibility {
165 | -webkit-mask-image: url(../vendor/entypo/globe.svg);
166 | }
167 |
168 | .keybinding {
169 | padding-bottom: 5px;
170 | }
171 |
172 | .ipc-icon {
173 | width: 16px;
174 | height: 16px;
175 | margin-right: 10px;
176 | background-color: #666;
177 | float: left;
178 | }
179 |
180 | .ipc-icon-sent {
181 | -webkit-mask-image: url(../vendor/entypo/arrow-up.svg);
182 | }
183 |
184 | .ipc-icon-received {
185 | -webkit-mask-image: url(../vendor/entypo/arrow-down.svg);
186 | }
187 |
188 | .ipc-icon-sync {
189 | -webkit-mask-image: url(../vendor/entypo/controller-paus.svg);
190 | }
191 |
192 | .toolbar-icon {
193 | width: 16px;
194 | height: 16px;
195 | background-color: #666;
196 | margin-right: 5px;
197 | float: left;
198 | }
199 |
200 | .active .toolbar-icon {
201 | background-color: #fff;
202 | }
203 |
204 | .toolbar-icon-record {
205 | -webkit-mask-image: url(../vendor/entypo/controller-record.svg);
206 | }
207 |
208 | .toolbar-icon-stop {
209 | -webkit-mask-image: url(../vendor/entypo/controller-stop.svg);
210 | }
211 |
212 | .toolbar-icon-block {
213 | -webkit-mask-image: url(../vendor/entypo/block.svg);
214 | }
215 |
216 | .toolbar-icon-book {
217 | -webkit-mask-image: url(../vendor/entypo/open-book.svg);
218 | }
219 |
220 | .toolbar-icon-funnel {
221 | -webkit-mask-image: url(../vendor/entypo/funnel.svg);
222 | }
223 |
224 | .toolbar-icon-cycle {
225 | -webkit-mask-image: url(../vendor/entypo/cycle.svg);
226 | }
227 |
228 | .toolbar-icon-bug {
229 | -webkit-mask-image: url(../vendor/entypo/bug.svg);
230 | }
231 |
232 | .toolbar-actions .active,
233 | .toolbar-actions .active:focus,
234 | .toolbar-actions .active:hover {
235 | color: #fff;
236 | border: 1px solid transparent;
237 | background-color: #6d6c6d;
238 | background-image: none;
239 | outline: none;
240 | }
241 |
242 | .lint-icon {
243 | width: 18px;
244 | height: 18px;
245 | margin-right: 10px;
246 | float: left;
247 | }
248 |
249 | .alert-lint-pass .lint-icon {
250 | -webkit-mask-image: url(../vendor/entypo/check.svg);
251 | background-color: #3c763d;
252 | }
253 |
254 | .alert-lint-fail .lint-icon {
255 | -webkit-mask-image: url(../vendor/entypo/warning.svg);
256 | background-color: #a94442;
257 | }
258 |
259 | .alert-lint-pass .lint-title {
260 | color: #3c763d;
261 | }
262 |
263 | .alert-lint-fail .lint-title {
264 | color: #a94442;
265 | }
266 |
267 | td.alert {
268 | white-space: normal;
269 | }
270 |
271 | table code {
272 | padding: 0;
273 | -webkit-user-select: text;
274 | }
275 |
276 | table pre {
277 | margin: 0;
278 | padding: 0;
279 | -webkit-user-select: text;
280 | }
281 |
282 | pre code span {
283 | -webkit-user-select: text;
284 | }
285 |
286 | pre,
287 | code {
288 | color: inherit;
289 | background-color: inherit;
290 | -webkit-user-select: text;
291 | }
292 |
293 | .row-event-data pre code {
294 | white-space: nowrap;
295 | }
296 |
297 | .active pre,
298 | .active pre span,
299 | .active code,
300 | .active code span {
301 | color: white !important;
302 | }
303 |
304 | .hljs {
305 | padding: 0;
306 | background: inherit;
307 | }
308 |
309 | .search-box {
310 | width: 250px;
311 | min-height: 24px;
312 | max-height: 24px;
313 | }
314 |
315 | .search-box:focus {
316 | box-shadow: 1px 1px 0 #6db3fd, -1px -1px 0 #6db3fd, -1px 1px 0 #6db3fd, 1px -1px 0 #6db3fd;
317 | }
318 |
319 | .btn-group .btn + .btn.active {
320 | border-left: 1px solid transparent;
321 | }
322 |
323 | .btn-default:hover,
324 | .btn-default:focus,
325 | .btn-default:active,
326 | .btn-default:active:focus {
327 | border-top-color: #c2c0c2;
328 | border-right-color: #c2c0c2;
329 | border-bottom-color: #a19fa1;
330 | border-left-color: #c2c0c2;
331 | }
332 |
333 | .table-description {
334 | margin: auto;
335 | margin-top: 40px;
336 | text-align: center;
337 | font-size: 1.2em;
338 | font-weight: 300;
339 | color: #bbb;
340 | }
341 |
342 | .pane {
343 | overflow-y: hidden;
344 | }
345 |
346 | .lint-table .alert {
347 | border-radius: 0;
348 | background: #fff;
349 | color: #333;
350 | }
351 |
352 | .node-integration-view {
353 | display: block;
354 | overflow: scroll;
355 | position: absolute;
356 | top: 0;
357 | bottom: 0;
358 | left: 0;
359 | right: 0;
360 | }
361 |
362 | .node-integration-view .error-heading {
363 | color: #a94442;
364 | }
365 |
--------------------------------------------------------------------------------
/static/devtron.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
44 |
72 | Module | 123 |Version | 124 |Size | 125 |File | 126 | 127 | 128 | 129 |
---|
require
144 | starting from your application's main entry point.
145 |
468 | It appears that this BrowserWindow
was created with
469 | nodeIntegration
set to false
.
470 |
473 | Devtron accesses Electron APIs via require
so you will
474 | need to expose require
and process
via a
475 | preload script in order to use Devtron.
476 |
479 | You can specify a preload script to the BrowserWindow
constructor:
480 |
var window = new BrowserWindow({
483 | webPreferences: {
484 | preload: path.join(__dirname, 'preload.js')
485 | }
486 | })
487 |
488 |
489 | Then add the following to the preload.js
script:
490 |
window.__devtron = {require: require, process: process}
493 |
494 |
495 | You may want to guard the previous code block with a NODE_ENV
496 | check so that these variables are only exposed during development:
497 |
498 |
499 | if (process.env.NODE_ENV === 'development')
500 |
501 | 502 | Restart your application once you've configured this preload script 503 | and Devtron should then be enabled for your app. 504 |
505 |