├── .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 icon 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 | [![Travis Build Status](https://travis-ci.org/electron/devtron.svg?branch=master)](https://travis-ci.org/electron/devtron) 9 | [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/t9eqglwos7kyv6w3/branch/master?svg=true)](https://ci.appveyor.com/project/electron-bot/devtron/branch/master) 10 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) 11 | [![downloads:?](https://img.shields.io/npm/dm/devtron.svg)](https://www.npmjs.com/packages/devtron) 12 | 13 | ![screenshot](https://cloud.githubusercontent.com/assets/378023/15036521/e3e7cd06-12ca-11e6-8054-ed0455015f05.png) 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 | 4 | 5 | devtron 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Devtron 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 33 | 34 | 47 | 48 | 57 | 58 | 67 | 68 | 75 | 76 | 87 | 88 | 97 | 98 | 152 | 153 | 183 | 184 | 230 | 231 | 269 | 270 | 385 | 386 | 431 | 432 | 462 | 463 | 508 | 509 | 510 | 511 | -------------------------------------------------------------------------------- /test/fixtures/app/index.js: -------------------------------------------------------------------------------- 1 | const app = require('electron').app 2 | const BrowserWindow = require('electron').BrowserWindow 3 | const path = require('path') 4 | 5 | let window 6 | 7 | app.on('ready', function () { 8 | window = new BrowserWindow({ 9 | width: 800, 10 | height: 600, 11 | webPreferences: { 12 | preload: path.join(__dirname, 'preload') 13 | } 14 | }) 15 | 16 | const indexPage = path.join(__dirname, '..', '..', '..', 'static', 'index.html') 17 | window.loadURL(`file://${indexPage}`) 18 | }) 19 | -------------------------------------------------------------------------------- /test/fixtures/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devtron", 3 | "main": "index.js" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/app/preload.js: -------------------------------------------------------------------------------- 1 | const vm = require('vm') 2 | 3 | window.chrome = { 4 | devtools: { 5 | inspectedWindow: { 6 | eval: (expression, callback) => { 7 | expression = `'use strict';\n${expression}` 8 | try { 9 | const sandbox = { 10 | require: require, 11 | console: console, 12 | process: process, 13 | global: { 14 | process: process 15 | }, 16 | window: { 17 | require: require, 18 | process: process 19 | } 20 | } 21 | callback(vm.runInNewContext(expression, sandbox)) 22 | } catch (error) { 23 | callback(null, error) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/foo.txt: -------------------------------------------------------------------------------- 1 | this is a file 2 | -------------------------------------------------------------------------------- /test/fixtures/node-integration-instructions/index.js: -------------------------------------------------------------------------------- 1 | const app = require('electron').app 2 | const BrowserWindow = require('electron').BrowserWindow 3 | const path = require('path') 4 | 5 | let window 6 | 7 | app.on('ready', function () { 8 | window = new BrowserWindow({ 9 | width: 800, 10 | height: 600, 11 | webPreferences: { 12 | preload: path.join(__dirname, 'preload'), 13 | nodeIntegration: false 14 | } 15 | }) 16 | 17 | const indexPage = path.join(__dirname, '..', '..', '..', 'static', 'index.html') 18 | window.loadURL(`file://${indexPage}`) 19 | }) 20 | -------------------------------------------------------------------------------- /test/fixtures/node-integration-instructions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devtron", 3 | "main": "index.js" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/node-integration-instructions/preload.js: -------------------------------------------------------------------------------- 1 | const vm = require('vm') 2 | 3 | window.chrome = { 4 | devtools: { 5 | inspectedWindow: { 6 | eval: (expression, callback) => { 7 | expression = `'use strict';\n${expression}` 8 | try { 9 | let sandbox = { 10 | window: {} 11 | } 12 | callback(vm.runInNewContext(expression, sandbox)) 13 | } catch (error) { 14 | callback(null, error) 15 | } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/foo/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/test/fixtures/node_modules/foo/index.js -------------------------------------------------------------------------------- /test/fixtures/node_modules/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foo", 3 | "version": "1.2.3", 4 | "main": "./index.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/preload/index.js: -------------------------------------------------------------------------------- 1 | const app = require('electron').app 2 | const BrowserWindow = require('electron').BrowserWindow 3 | const path = require('path') 4 | 5 | let window 6 | 7 | app.on('ready', function () { 8 | window = new BrowserWindow({ 9 | width: 800, 10 | height: 600, 11 | webPreferences: { 12 | preload: path.join(__dirname, 'preload'), 13 | nodeIntegration: false 14 | } 15 | }) 16 | 17 | const indexPage = path.join(__dirname, '..', '..', '..', 'static', 'index.html') 18 | window.loadURL(`file://${indexPage}`) 19 | }) 20 | -------------------------------------------------------------------------------- /test/fixtures/preload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devtron", 3 | "main": "index.js" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/preload/preload.js: -------------------------------------------------------------------------------- 1 | const vm = require('vm') 2 | 3 | const devtron = { 4 | require: require, 5 | process: process 6 | } 7 | 8 | window.chrome = { 9 | devtools: { 10 | inspectedWindow: { 11 | eval: (expression, callback) => { 12 | expression = `'use strict';\n${expression}` 13 | try { 14 | let sandbox = { 15 | window: { 16 | __devtron: devtron 17 | } 18 | } 19 | callback(vm.runInNewContext(expression, sandbox)) 20 | } catch (error) { 21 | callback(null, error) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/integration/app-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const Application = require('spectron').Application 5 | const chai = require('chai') 6 | const chaiAsPromised = require('chai-as-promised') 7 | 8 | const describe = global.describe 9 | const it = global.it 10 | const beforeEach = global.beforeEach 11 | const afterEach = global.afterEach 12 | 13 | const timeout = process.env.CI ? 60000 : 30000 14 | 15 | chai.should() 16 | chai.use(chaiAsPromised) 17 | 18 | describe('when opened in an app', function () { 19 | this.timeout(timeout) 20 | 21 | let app 22 | 23 | beforeEach(function () { 24 | let electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron') 25 | if (process.platform === 'win32') electronPath += '.cmd' 26 | 27 | app = new Application({ 28 | path: electronPath, 29 | args: [path.join(__dirname, '..', 'fixtures', 'app')] 30 | }) 31 | 32 | return app.start().then(function () { 33 | chaiAsPromised.transferPromiseness = app.transferPromiseness 34 | return app.client.waitUntilWindowLoaded() 35 | }) 36 | }) 37 | 38 | afterEach(function () { 39 | if (app.isRunning()) return app.stop() 40 | }) 41 | 42 | describe('when a sidebar item is clicked', function () { 43 | it('displays the pane for the selected item', function () { 44 | return app.client 45 | .isVisible('.pane[data-pane=listeners]').should.eventually.be.false 46 | .click('.list-group-item[data-pane-link=listeners]') 47 | .isVisible('.pane[data-pane=graph]').should.eventually.be.false 48 | .isVisible('.pane[data-pane=listeners]').should.eventually.be.true 49 | .click('.list-group-item[data-pane-link=ipc]') 50 | .isVisible('.pane[data-pane=listeners]').should.eventually.be.false 51 | .isVisible('.pane[data-pane=ipc]').should.eventually.be.true 52 | .click('.list-group-item[data-pane-link=lint]') 53 | .isVisible('.pane[data-pane=ipc]').should.eventually.be.false 54 | .isVisible('.pane[data-pane=lint]').should.eventually.be.true 55 | .click('.list-group-item[data-pane-link=about]') 56 | .isVisible('.pane[data-pane=lint]').should.eventually.be.false 57 | .isVisible('.pane[data-pane=about]').should.eventually.be.true 58 | }) 59 | }) 60 | 61 | describe('Require Graph', function () { 62 | it('displays it initially', function () { 63 | return app.client 64 | .getText('.sidebar .active').should.eventually.equal('Require Graph') 65 | .isVisible('.pane[data-pane=graph]').should.eventually.be.true 66 | .getText('.pane[data-pane=graph] .tab-item.active').should.eventually.equal('Renderer Process') 67 | }) 68 | 69 | describe('when the Load Graph button is clicked', function () { 70 | it('displays a table of required files', function () { 71 | return app.client 72 | .click('.pane[data-pane=graph] button') 73 | .waitForVisible('.pane[data-pane=graph] .table-description', timeout, true) 74 | .isVisible('.pane[data-pane=graph] .row-module').should.eventually.have.length.above(0) 75 | }) 76 | }) 77 | }) 78 | 79 | describe('Event Listeners', function () { 80 | describe('when the Load Listeners button is clicked', function () { 81 | it('displays a table of emitters, events, and listeners', function () { 82 | return app.client 83 | .click('.list-group-item[data-pane-link=listeners]') 84 | .click('.pane[data-pane=listeners] button') 85 | .waitForVisible('.pane[data-pane=listeners] .table-description', timeout, true) 86 | .isVisible('.pane[data-pane=listeners] .row-emitter').should.eventually.have.length.above(0) 87 | }) 88 | }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /test/integration/node-integration-instructions-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const Application = require('spectron').Application 5 | const chai = require('chai') 6 | const chaiAsPromised = require('chai-as-promised') 7 | 8 | const describe = global.describe 9 | const it = global.it 10 | const before = global.before 11 | const after = global.after 12 | 13 | const timeout = process.env.CI ? 60000 : 30000 14 | 15 | chai.should() 16 | chai.use(chaiAsPromised) 17 | 18 | describe('when nodeIntegration is disabled in the app', function () { 19 | this.timeout(timeout) 20 | 21 | let app 22 | 23 | before(function () { 24 | let electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron') 25 | if (process.platform === 'win32') electronPath += '.cmd' 26 | 27 | app = new Application({ 28 | path: electronPath, 29 | args: [path.join(__dirname, '..', 'fixtures', 'node-integration-instructions')] 30 | }) 31 | 32 | return app.start().then(function () { 33 | chaiAsPromised.transferPromiseness = app.transferPromiseness 34 | return app.client.waitUntilWindowLoaded() 35 | }) 36 | }) 37 | 38 | after(function () { 39 | if (app.isRunning()) return app.stop() 40 | }) 41 | 42 | describe('when require and process are not set via a preload script', function () { 43 | it('displays instructions for exposing them', function () { 44 | return app.client 45 | .waitForVisible('.node-integration-view', timeout) 46 | .getText('.error-heading').should.eventually.equal('Node Integration Disabled') 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /test/integration/preload-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const Application = require('spectron').Application 5 | const chai = require('chai') 6 | const chaiAsPromised = require('chai-as-promised') 7 | 8 | const describe = global.describe 9 | const it = global.it 10 | const before = global.before 11 | const after = global.after 12 | 13 | const timeout = process.env.CI ? 60000 : 30000 14 | 15 | chai.should() 16 | chai.use(chaiAsPromised) 17 | 18 | describe('when nodeIntegration is disabled in the app', function () { 19 | this.timeout(timeout) 20 | 21 | let app 22 | 23 | before(function () { 24 | let electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron') 25 | if (process.platform === 'win32') electronPath += '.cmd' 26 | 27 | app = new Application({ 28 | path: electronPath, 29 | args: [path.join(__dirname, '..', 'fixtures', 'preload')] 30 | }) 31 | 32 | return app.start().then(function () { 33 | chaiAsPromised.transferPromiseness = app.transferPromiseness 34 | return app.client.waitUntilWindowLoaded() 35 | }) 36 | }) 37 | 38 | after(function () { 39 | if (app.isRunning()) return app.stop() 40 | }) 41 | 42 | describe('when require and process are set via a preload script', function () { 43 | it('displays a table of required files', function () { 44 | return app.client 45 | .click('.pane[data-pane=graph] button') 46 | .waitForVisible('.pane[data-pane=graph] .table-description', timeout, true) 47 | .isVisible('.pane[data-pane=graph] .row-module').should.eventually.have.length.above(0) 48 | }) 49 | 50 | it('displays a table of emitters, events, and listeners', function () { 51 | return app.client 52 | .click('.list-group-item[data-pane-link=listeners]') 53 | .click('.pane[data-pane=listeners] button') 54 | .waitForVisible('.pane[data-pane=listeners] .table-description', timeout, true) 55 | .isVisible('.pane[data-pane=listeners] .row-emitter').should.eventually.have.length.above(0) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | // This is the server run by devtron in debug mode. 2 | // It allows you to run devtron in a browser and have it forward requests 3 | // to the running Electron app. 4 | 5 | const express = require('express') 6 | const bodyParser = require('body-parser') 7 | const cors = require('cors') 8 | const path = require('path') 9 | const vm = require('vm') 10 | 11 | console.log('Starting Devtron server') 12 | 13 | const app = express() 14 | app.use(bodyParser.json()) 15 | app.use(cors()) 16 | app.post('/', function (request, response) { 17 | try { 18 | response.json({result: vm.runInThisContext(request.body.expression)}) 19 | } catch (error) { 20 | response.json(error) 21 | } 22 | }) 23 | app.listen(3948, 'localhost', function () { 24 | console.log('Devtron server listening on http://localhost:3948') 25 | console.log(`Open file://${path.join(__dirname, '..', 'static', 'index.html')} to view`) 26 | }) 27 | -------------------------------------------------------------------------------- /test/unit/chrome-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Chrome = require('../../lib/chrome-helpers') 4 | const devtools = require('./devtools') 5 | const expect = require('chai').expect 6 | 7 | const describe = global.describe 8 | const it = global.it 9 | const beforeEach = global.beforeEach 10 | const afterEach = global.afterEach 11 | 12 | describe('Chrome', function () { 13 | this.timeout(process.env.CI ? 60000 : 30000) 14 | 15 | beforeEach(() => { 16 | global.window = devtools.create() 17 | }) 18 | 19 | afterEach(() => { 20 | delete global.window 21 | }) 22 | 23 | describe('getChromeAPIs()', () => { 24 | it('returns an array of string API signatures ', () => { 25 | const apis = Chrome.getChromeAPIs() 26 | expect(apis).to.deep.equal([ 27 | 'chrome.devtools.inspectedWindow.eval', 28 | 'chrome.devtools.tabId', 29 | 'chrome.extension.getURL' 30 | ]) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/unit/devtools.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const vm = require('vm') 4 | 5 | class Extension { 6 | getURL () { 7 | return 'file:///foo/bar.html' 8 | } 9 | } 10 | 11 | exports.create = () => { 12 | const devtron = {} 13 | return { 14 | chrome: { 15 | devtools: { 16 | inspectedWindow: { 17 | eval: (expression, callback) => { 18 | expression = `'use strict';\n${expression}` 19 | try { 20 | const sandbox = { 21 | require: require, 22 | console: console, 23 | process: process, 24 | global: { 25 | process: process, 26 | __devtron: devtron 27 | }, 28 | window: { 29 | process: process, 30 | require: require, 31 | __devtron: devtron 32 | } 33 | } 34 | callback(vm.runInNewContext(expression, sandbox)) 35 | } catch (error) { 36 | callback(null, error) 37 | } 38 | } 39 | }, 40 | tabId: 1 41 | }, 42 | extension: new Extension() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/unit/eval-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const devtools = require('./devtools') 4 | const Eval = require('../../lib/eval') 5 | const path = require('path') 6 | 7 | const describe = global.describe 8 | const it = global.it 9 | const beforeEach = global.beforeEach 10 | const afterEach = global.afterEach 11 | 12 | describe('Eval', function () { 13 | this.timeout(process.env.CI ? 60000 : 30000) 14 | 15 | beforeEach(() => { 16 | global.window = devtools.create() 17 | }) 18 | 19 | afterEach(() => { 20 | delete global.window 21 | }) 22 | 23 | describe('execute(expression, ...args)', () => { 24 | it('returns a promise that resolves to the value of the specified expression', () => { 25 | return Eval.execute('1+1').should.eventually.equal(2) 26 | }) 27 | 28 | it('returns a rejected promise on errors', () => { 29 | return Eval.execute('+-').should.be.rejected 30 | }) 31 | 32 | it('accepts a function with no arguments', () => { 33 | return Eval.execute(() => 3).should.eventually.equal(3) 34 | }) 35 | 36 | it('accepts a function with arguments', () => { 37 | return Eval.execute((x, y) => x + y, 1, 2).should.eventually.equal(3) 38 | }) 39 | }) 40 | 41 | describe('getFileSize(filePath)', () => { 42 | it('returns the size of the file', () => { 43 | return Eval.getFileSize(path.join(__dirname, '..', 'fixtures', 'foo.txt')).should.eventually.equal(15) 44 | }) 45 | 46 | it('returns -1 for files that do not exist', () => { 47 | return Eval.getFileSize(path.join(__dirname, '..', 'fixtures', 'does-not-exist.txt')).should.eventually.equal(-1) 48 | }) 49 | }) 50 | 51 | describe('getFileVersion(filePath)', () => { 52 | it('returns the version from the parent package.json', () => { 53 | return Eval.getFileVersion(path.join(__dirname, '..', 'fixtures', 'node_modules', 'foo', 'index.js')).should.eventually.equal('1.2.3') 54 | }) 55 | 56 | it('returns the electron version for paths inside the api asar file', () => { 57 | return Eval.getFileVersion('/Electron.app/Contents/Resources/atom.asar/renderer/init.js').should.eventually.equal('1.0.0') 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/unit/event-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const devtools = require('./devtools') 4 | const Event = require('../../lib/event-helpers') 5 | const expect = require('chai').expect 6 | 7 | const describe = global.describe 8 | const it = global.it 9 | const beforeEach = global.beforeEach 10 | const afterEach = global.afterEach 11 | 12 | describe('Event Helpers', function () { 13 | this.timeout(process.env.CI ? 60000 : 30000) 14 | 15 | beforeEach(() => { 16 | global.window = devtools.create() 17 | }) 18 | 19 | afterEach(() => { 20 | delete global.window 21 | }) 22 | 23 | describe('getEvents()', () => { 24 | it('returns the listeners', () => { 25 | return Event.getEvents().then((events) => { 26 | expect(Object.keys(events).length).to.equal(7) 27 | 28 | const browserWindowEvents = events['electron.remote.getCurrentWindow()'] 29 | expect(Object.keys(browserWindowEvents).length).to.equal(1) 30 | expect(browserWindowEvents['browser-window'].length).to.equal(1) 31 | expect(browserWindowEvents['browser-window'][0]).to.equal('function () { return 3 }') 32 | }) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/unit/ipc-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const devtools = require('./devtools') 4 | const ipc = require('../../lib/ipc-helpers') 5 | const expect = require('chai').expect 6 | 7 | const describe = global.describe 8 | const it = global.it 9 | const beforeEach = global.beforeEach 10 | const afterEach = global.afterEach 11 | 12 | describe('IPC Helpers', function () { 13 | this.timeout(process.env.CI ? 60000 : 30000) 14 | 15 | beforeEach(() => { 16 | global.window = devtools.create() 17 | }) 18 | 19 | afterEach(() => { 20 | delete global.window 21 | }) 22 | 23 | describe('getEvents()', () => { 24 | it('returns an empty events array when not listening', () => { 25 | return ipc.getEvents().then((events) => { 26 | expect(events.length).to.equal(0) 27 | }) 28 | }) 29 | 30 | it('returns the emitted IPC events', () => { 31 | return ipc.listenForEvents().then(() => { 32 | require('electron').ipcRenderer.emit('foo', {}, 'bar', 'baz') 33 | require('electron').ipcRenderer.send('bar', 'hey', 3) 34 | require('electron').ipcRenderer.sendSync('baz', 'hi', false) 35 | 36 | return ipc.getEvents() 37 | }).then((events) => { 38 | expect(events.length).to.equal(4) 39 | 40 | expect(events[0].channel).to.equal('foo') 41 | expect(events[0].data).to.equal('["bar","baz"]') 42 | expect(events[0].sync).to.be.false 43 | expect(events[0].sent).to.be.false 44 | 45 | expect(events[1].channel).to.equal('bar') 46 | expect(events[1].data).to.equal('["hey",3]') 47 | expect(events[1].sync).to.be.false 48 | expect(events[1].sent).to.be.true 49 | 50 | expect(events[2].channel).to.equal('baz') 51 | expect(events[2].data).to.equal('["hi",false]') 52 | expect(events[2].sync).to.be.true 53 | expect(events[2].sent).to.be.true 54 | 55 | expect(events[3].channel).to.equal('baz') 56 | expect(events[3].data).to.equal('[null]') 57 | expect(events[3].sync).to.be.true 58 | expect(events[3].sent).to.be.false 59 | }).then(ipc.getEvents).then((events) => { 60 | expect(events.length).to.equal(0) 61 | }) 62 | }) 63 | 64 | it('ignores certain internal events', () => { 65 | return ipc.listenForEvents().then(() => { 66 | require('electron').ipcRenderer.emit('ELECTRON_BROWSER_DEREFERENCE', {}, 'bar') 67 | return ipc.getEvents() 68 | }).then((events) => { 69 | expect(events.length).to.equal(0) 70 | }) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/unit/lint-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const devtools = require('./devtools') 4 | const expect = require('chai').expect 5 | const Lint = require('../../lib/lint-helpers') 6 | 7 | const afterEach = global.afterEach 8 | const beforeEach = global.beforeEach 9 | const describe = global.describe 10 | const it = global.it 11 | 12 | describe('Lint Helpers', function () { 13 | this.timeout(process.env.CI ? 60000 : 30000) 14 | 15 | beforeEach(() => { 16 | global.window = devtools.create() 17 | }) 18 | 19 | afterEach(() => { 20 | delete global.window 21 | }) 22 | 23 | describe('isUsingAsar()', () => { 24 | it('returns false if the application is not in an asar archive', () => { 25 | return Lint.isUsingAsar().then((usingAsar) => { 26 | expect(usingAsar).to.equal(false) 27 | }) 28 | }) 29 | }) 30 | 31 | describe('isListeningForCrashEvents()', () => { 32 | it('returns false if not listening for crash events', () => { 33 | return Lint.isListeningForCrashEvents().then((listening) => { 34 | expect(listening).to.equal(false) 35 | 36 | require('electron').remote.getCurrentWebContents().on('crashed', function () {}) 37 | 38 | return Lint.isListeningForCrashEvents().then((listening) => { 39 | expect(listening).to.equal(true) 40 | }) 41 | }) 42 | }) 43 | }) 44 | 45 | describe('isListeningForUnresponsiveEvents()', () => { 46 | it('returns false if not listening for unresponsive events', () => { 47 | return Lint.isListeningForUnresponsiveEvents().then((listening) => { 48 | expect(listening).to.equal(false) 49 | 50 | require('electron').remote.getCurrentWindow().on('unresponsive', function () {}) 51 | 52 | return Lint.isListeningForUnresponsiveEvents().then((listening) => { 53 | expect(listening).to.equal(true) 54 | }) 55 | }) 56 | }) 57 | }) 58 | 59 | describe('isListeningForUncaughtExceptionEvents()', () => { 60 | it('returns false if not listening for uncaught exception events', () => { 61 | return Lint.isListeningForUncaughtExceptionEvents().then((listening) => { 62 | expect(listening).to.equal(false) 63 | 64 | require('electron').remote.process.on('uncaughtException', function () {}) 65 | 66 | return Lint.isListeningForUncaughtExceptionEvents().then((listening) => { 67 | expect(listening).to.equal(true) 68 | }) 69 | }) 70 | }) 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /test/unit/module-test.js: -------------------------------------------------------------------------------- 1 | const devtools = require('./devtools') 2 | const expect = require('chai').expect 3 | const Module = require('../../lib/module') 4 | const Modules = require('../../lib/module-helpers') 5 | 6 | const afterEach = global.afterEach 7 | const beforeEach = global.beforeEach 8 | const describe = global.describe 9 | const it = global.it 10 | 11 | describe('Module', function () { 12 | this.timeout(process.env.CI ? 60000 : 30000) 13 | 14 | beforeEach(() => { 15 | global.window = devtools.create() 16 | }) 17 | 18 | afterEach(() => { 19 | delete global.window 20 | }) 21 | 22 | describe('main module', () => { 23 | it('provides details about the module', () => { 24 | const root = new Module('/apps/my-app/lib/bar', '/apps/my-app', 'My App') 25 | expect(root.hasChildren()).to.be.false 26 | expect(root.getName()).to.equal('bar') 27 | expect(root.getDepth()).to.equal(1) 28 | expect(root.getDirectory()).to.equal('lib') 29 | expect(root.getLibrary()).to.equal('My App') 30 | expect(root.getId()).to.equal('my app') 31 | }) 32 | }) 33 | 34 | describe('getMainModules()', () => { 35 | it('returns the main module and child modules', () => { 36 | return Modules.getMainModules().then((mainModule) => { 37 | expect(mainModule.appName).to.equal('Devtron') 38 | expect(mainModule.totalSize).to.be.above(0) 39 | expect(mainModule.getLibrary()).to.equal('mocha') 40 | expect(mainModule.getId()).to.equal('mocha') 41 | expect(mainModule.getName()).to.equal('_mocha') 42 | expect(mainModule.hasChildren()).to.be.true 43 | expect(mainModule.getPath()).to.equal(process.mainModule.filename.replace(/\\/g, '/')) 44 | expect(mainModule.getSize()).to.be.above(0) 45 | expect(mainModule.getDepth()).to.equal(1) 46 | expect(mainModule.getVersion()).not.to.be.empty 47 | }) 48 | }) 49 | }) 50 | 51 | describe('getRenderModules()', () => { 52 | it('returns the main module and child modules', () => { 53 | return Modules.getRenderModules().then((mainModule) => { 54 | expect(mainModule.appName).to.equal('Devtron') 55 | expect(mainModule.totalSize).to.be.above(0) 56 | expect(mainModule.getLibrary()).to.equal('mocha') 57 | expect(mainModule.getId()).to.equal('mocha') 58 | expect(mainModule.getName()).to.equal('_mocha') 59 | expect(mainModule.hasChildren()).to.be.true 60 | expect(mainModule.getPath()).to.equal(process.mainModule.filename.replace(/\\/g, '/')) 61 | expect(mainModule.getSize()).to.be.above(0) 62 | expect(mainModule.getDepth()).to.equal(1) 63 | expect(mainModule.getVersion()).not.to.be.empty 64 | }) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/unit/node_modules/electron/index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter 2 | 3 | const app = new EventEmitter() 4 | app.getVersion = () => '1.0.0' 5 | app.getName = () => 'Devtron' 6 | 7 | const browserWindow = new EventEmitter() 8 | browserWindow.on('browser-window', function () { return 3 }) 9 | 10 | const ipcMain = new EventEmitter() 11 | const ipcRenderer = new EventEmitter() 12 | const webContents = new EventEmitter() 13 | 14 | ipcRenderer.send = function () {} 15 | ipcRenderer.sendSync = function () {} 16 | 17 | // Mock Electron's APIs for running the specs in a pure node environment 18 | module.exports = { 19 | remote: { 20 | app: app, 21 | getCurrentWindow: () => browserWindow, 22 | getCurrentWebContents: () => webContents, 23 | ipcMain: ipcMain, 24 | process: global.process 25 | }, 26 | ipcRenderer: ipcRenderer, 27 | setup: () => { 28 | process.versions.electron = app.getVersion() 29 | process.resourcesPath = __dirname 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/unit/node_modules/electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron", 3 | "version": "1.0.0", 4 | "main": "./index.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/unit/setup-test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | const chaiAsPromised = require('chai-as-promised') 3 | 4 | chai.should() 5 | chai.use(chaiAsPromised) 6 | 7 | require('electron').setup() 8 | -------------------------------------------------------------------------------- /vendor/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /vendor/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /vendor/entypo/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/entypo/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/entypo/block.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /vendor/entypo/bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 19 | 20 | -------------------------------------------------------------------------------- /vendor/entypo/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /vendor/entypo/controller-paus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /vendor/entypo/controller-record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/entypo/controller-stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /vendor/entypo/cycle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /vendor/entypo/flow-tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 18 | 19 | -------------------------------------------------------------------------------- /vendor/entypo/funnel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /vendor/entypo/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 17 | 18 | -------------------------------------------------------------------------------- /vendor/entypo/help-with-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /vendor/entypo/old-phone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /vendor/entypo/open-book.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 24 | 25 | -------------------------------------------------------------------------------- /vendor/entypo/sound.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 14 | 15 | -------------------------------------------------------------------------------- /vendor/entypo/swap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/entypo/triangle-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/entypo/triangle-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/entypo/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /vendor/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /vendor/photon/css/photon.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * ===================================================== 3 | * Photon v0.1.1 4 | * Copyright 2015 Connor Sears 5 | * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE) 6 | * 7 | * v0.1.1 designed by @connors. 8 | * ===================================================== 9 | */ 10 | 11 | @charset "UTF-8"; 12 | audio, 13 | canvas, 14 | progress, 15 | video { 16 | vertical-align: baseline; 17 | } 18 | 19 | audio:not([controls]) { 20 | display: none; 21 | } 22 | 23 | a:active, 24 | a:hover { 25 | outline: 0; 26 | } 27 | 28 | abbr[title] { 29 | border-bottom: 1px dotted; 30 | } 31 | 32 | b, 33 | strong { 34 | font-weight: bold; 35 | } 36 | 37 | dfn { 38 | font-style: italic; 39 | } 40 | 41 | h1 { 42 | font-size: 2em; 43 | margin: 0.67em 0; 44 | } 45 | 46 | small { 47 | font-size: 80%; 48 | } 49 | 50 | sub, 51 | sup { 52 | font-size: 75%; 53 | line-height: 0; 54 | position: relative; 55 | vertical-align: baseline; 56 | } 57 | 58 | sup { 59 | top: -0.5em; 60 | } 61 | 62 | sub { 63 | bottom: -0.25em; 64 | } 65 | 66 | pre { 67 | overflow: auto; 68 | } 69 | 70 | code, 71 | kbd, 72 | pre, 73 | samp { 74 | font-family: monospace, monospace; 75 | font-size: 1em; 76 | } 77 | 78 | button, 79 | input, 80 | optgroup, 81 | select, 82 | textarea { 83 | color: inherit; 84 | font: inherit; 85 | margin: 0; 86 | } 87 | 88 | input[type="number"]::-webkit-inner-spin-button, 89 | input[type="number"]::-webkit-outer-spin-button { 90 | height: auto; 91 | } 92 | 93 | input[type="search"] { 94 | -webkit-appearance: textfield; 95 | box-sizing: content-box; 96 | } 97 | 98 | input[type="search"]::-webkit-search-cancel-button, 99 | input[type="search"]::-webkit-search-decoration { 100 | -webkit-appearance: none; 101 | } 102 | 103 | fieldset { 104 | border: 1px solid #c0c0c0; 105 | margin: 0 2px; 106 | padding: 0.35em 0.625em 0.75em; 107 | } 108 | 109 | legend { 110 | border: 0; 111 | padding: 0; 112 | } 113 | 114 | table { 115 | border-collapse: collapse; 116 | border-spacing: 0; 117 | } 118 | 119 | td, 120 | th { 121 | padding: 0; 122 | } 123 | 124 | * { 125 | cursor: default; 126 | -webkit-user-drag: text; 127 | -webkit-user-select: none; 128 | -webkit-box-sizing: border-box; 129 | box-sizing: border-box; 130 | } 131 | 132 | html { 133 | height: 100%; 134 | width: 100%; 135 | overflow: hidden; 136 | } 137 | 138 | body { 139 | height: 100%; 140 | padding: 0; 141 | margin: 0; 142 | font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "Segoe UI", sans-serif; 143 | font-size: 13px; 144 | line-height: 1.6; 145 | color: #333; 146 | background-color: transparent; 147 | } 148 | 149 | hr { 150 | margin: 15px 0; 151 | overflow: hidden; 152 | background: transparent; 153 | border: 0; 154 | border-bottom: 1px solid #ddd; 155 | } 156 | 157 | h1, h2, h3, h4, h5, h6 { 158 | margin-top: 20px; 159 | margin-bottom: 10px; 160 | font-weight: 500; 161 | white-space: nowrap; 162 | overflow: hidden; 163 | text-overflow: ellipsis; 164 | } 165 | 166 | h1 { 167 | font-size: 36px; 168 | } 169 | 170 | h2 { 171 | font-size: 30px; 172 | } 173 | 174 | h3 { 175 | font-size: 24px; 176 | } 177 | 178 | h4 { 179 | font-size: 18px; 180 | } 181 | 182 | h5 { 183 | font-size: 14px; 184 | } 185 | 186 | h6 { 187 | font-size: 12px; 188 | } 189 | 190 | .window { 191 | position: absolute; 192 | top: 0; 193 | right: 0; 194 | bottom: 0; 195 | left: 0; 196 | display: flex; 197 | flex-direction: column; 198 | background-color: #fff; 199 | } 200 | 201 | .window-content { 202 | position: relative; 203 | overflow-y: auto; 204 | display: flex; 205 | flex: 1; 206 | } 207 | 208 | .selectable-text { 209 | cursor: text; 210 | -webkit-user-select: text; 211 | } 212 | 213 | .text-center { 214 | text-align: center; 215 | } 216 | 217 | .text-right { 218 | text-align: right; 219 | } 220 | 221 | .text-left { 222 | text-align: left; 223 | } 224 | 225 | .pull-left { 226 | float: left; 227 | } 228 | 229 | .pull-right { 230 | float: right; 231 | } 232 | 233 | .padded { 234 | padding: 10px; 235 | } 236 | 237 | .padded-less { 238 | padding: 5px; 239 | } 240 | 241 | .padded-more { 242 | padding: 20px; 243 | } 244 | 245 | .padded-vertically { 246 | padding-top: 10px; 247 | padding-bottom: 10px; 248 | } 249 | 250 | .padded-vertically-less { 251 | padding-top: 5px; 252 | padding-bottom: 5px; 253 | } 254 | 255 | .padded-vertically-more { 256 | padding-top: 20px; 257 | padding-bottom: 20px; 258 | } 259 | 260 | .padded-horizontally { 261 | padding-right: 10px; 262 | padding-left: 10px; 263 | } 264 | 265 | .padded-horizontally-less { 266 | padding-right: 5px; 267 | padding-left: 5px; 268 | } 269 | 270 | .padded-horizontally-more { 271 | padding-right: 20px; 272 | padding-left: 20px; 273 | } 274 | 275 | .padded-top { 276 | padding-top: 10px; 277 | } 278 | 279 | .padded-top-less { 280 | padding-top: 5px; 281 | } 282 | 283 | .padded-top-more { 284 | padding-top: 20px; 285 | } 286 | 287 | .padded-bottom { 288 | padding-bottom: 10px; 289 | } 290 | 291 | .padded-bottom-less { 292 | padding-bottom: 5px; 293 | } 294 | 295 | .padded-bottom-more { 296 | padding-bottom: 20px; 297 | } 298 | 299 | .sidebar { 300 | background-color: #f5f5f4; 301 | } 302 | 303 | .draggable { 304 | -webkit-app-region: drag; 305 | } 306 | 307 | .clearfix:before, .clearfix:after { 308 | display: table; 309 | content: " "; 310 | } 311 | .clearfix:after { 312 | clear: both; 313 | } 314 | 315 | .btn { 316 | display: inline-block; 317 | padding: 3px 8px; 318 | margin-bottom: 0; 319 | font-size: 12px; 320 | line-height: 1.4; 321 | text-align: center; 322 | white-space: nowrap; 323 | vertical-align: middle; 324 | cursor: default; 325 | background-image: none; 326 | border: 1px solid transparent; 327 | border-radius: 4px; 328 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.06); 329 | -webkit-app-region: no-drag; 330 | } 331 | .btn:focus { 332 | outline: none; 333 | box-shadow: none; 334 | } 335 | 336 | .btn-mini { 337 | padding: 2px 6px; 338 | } 339 | 340 | .btn-large { 341 | padding: 6px 12px; 342 | } 343 | 344 | .btn-form { 345 | padding-right: 20px; 346 | padding-left: 20px; 347 | } 348 | 349 | .btn-default { 350 | color: #333; 351 | border-top-color: #c2c0c2; 352 | border-right-color: #c2c0c2; 353 | border-bottom-color: #a19fa1; 354 | border-left-color: #c2c0c2; 355 | background-color: #fcfcfc; 356 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fcfcfc), color-stop(100%, #f1f1f1)); 357 | background-image: -webkit-linear-gradient(top, #fcfcfc 0%, #f1f1f1 100%); 358 | background-image: linear-gradient(to bottom, #fcfcfc 0%, #f1f1f1 100%); 359 | } 360 | .btn-default:active { 361 | background-color: #ddd; 362 | background-image: none; 363 | } 364 | 365 | .btn-primary, 366 | .btn-positive, 367 | .btn-negative, 368 | .btn-warning { 369 | color: #fff; 370 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); 371 | } 372 | 373 | .btn-primary { 374 | border-color: #388df8; 375 | border-bottom-color: #0866dc; 376 | background-color: #6eb4f7; 377 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #6eb4f7), color-stop(100%, #1a82fb)); 378 | background-image: -webkit-linear-gradient(top, #6eb4f7 0%, #1a82fb 100%); 379 | background-image: linear-gradient(to bottom, #6eb4f7 0%, #1a82fb 100%); 380 | } 381 | .btn-primary:active { 382 | background-color: #3e9bf4; 383 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3e9bf4), color-stop(100%, #0469de)); 384 | background-image: -webkit-linear-gradient(top, #3e9bf4 0%, #0469de 100%); 385 | background-image: linear-gradient(to bottom, #3e9bf4 0%, #0469de 100%); 386 | } 387 | 388 | .btn-positive { 389 | border-color: #29a03b; 390 | border-bottom-color: #248b34; 391 | background-color: #5bd46d; 392 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bd46d), color-stop(100%, #29a03b)); 393 | background-image: -webkit-linear-gradient(top, #5bd46d 0%, #29a03b 100%); 394 | background-image: linear-gradient(to bottom, #5bd46d 0%, #29a03b 100%); 395 | } 396 | .btn-positive:active { 397 | background-color: #34c84a; 398 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #34c84a), color-stop(100%, #248b34)); 399 | background-image: -webkit-linear-gradient(top, #34c84a 0%, #248b34 100%); 400 | background-image: linear-gradient(to bottom, #34c84a 0%, #248b34 100%); 401 | } 402 | 403 | .btn-negative { 404 | border-color: #fb2f29; 405 | border-bottom-color: #fb1710; 406 | background-color: #fd918d; 407 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fd918d), color-stop(100%, #fb2f29)); 408 | background-image: -webkit-linear-gradient(top, #fd918d 0%, #fb2f29 100%); 409 | background-image: linear-gradient(to bottom, #fd918d 0%, #fb2f29 100%); 410 | } 411 | .btn-negative:active { 412 | background-color: #fc605b; 413 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fc605b), color-stop(100%, #fb1710)); 414 | background-image: -webkit-linear-gradient(top, #fc605b 0%, #fb1710 100%); 415 | background-image: linear-gradient(to bottom, #fc605b 0%, #fb1710 100%); 416 | } 417 | 418 | .btn-warning { 419 | border-color: #fcaa0e; 420 | border-bottom-color: #ee9d02; 421 | background-color: #fece72; 422 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fece72), color-stop(100%, #fcaa0e)); 423 | background-image: -webkit-linear-gradient(top, #fece72 0%, #fcaa0e 100%); 424 | background-image: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%); 425 | } 426 | .btn-warning:active { 427 | background-color: #fdbc40; 428 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdbc40), color-stop(100%, #ee9d02)); 429 | background-image: -webkit-linear-gradient(top, #fdbc40 0%, #ee9d02 100%); 430 | background-image: linear-gradient(to bottom, #fdbc40 0%, #ee9d02 100%); 431 | } 432 | 433 | .btn .icon { 434 | float: left; 435 | width: 14px; 436 | height: 14px; 437 | margin-top: 1px; 438 | margin-bottom: 1px; 439 | color: #737475; 440 | font-size: 14px; 441 | line-height: 1; 442 | } 443 | 444 | .btn .icon-text { 445 | margin-right: 5px; 446 | } 447 | 448 | .btn-dropdown:after { 449 | font-family: "photon-entypo"; 450 | margin-left: 5px; 451 | content: ""; 452 | } 453 | 454 | .btn-group { 455 | position: relative; 456 | display: inline-block; 457 | vertical-align: middle; 458 | -webkit-app-region: no-drag; 459 | } 460 | .btn-group .btn { 461 | position: relative; 462 | float: left; 463 | } 464 | .btn-group .btn:focus, .btn-group .btn:active { 465 | z-index: 2; 466 | } 467 | .btn-group .btn.active { 468 | z-index: 3; 469 | } 470 | 471 | .btn-group .btn + .btn, 472 | .btn-group .btn + .btn-group, 473 | .btn-group .btn-group + .btn, 474 | .btn-group .btn-group + .btn-group { 475 | margin-left: -1px; 476 | } 477 | .btn-group > .btn:first-child { 478 | border-top-right-radius: 0; 479 | border-bottom-right-radius: 0; 480 | } 481 | .btn-group > .btn:last-child { 482 | border-top-left-radius: 0; 483 | border-bottom-left-radius: 0; 484 | } 485 | .btn-group > .btn:not(:first-child):not(:last-child) { 486 | border-radius: 0; 487 | } 488 | .btn-group .btn + .btn { 489 | border-left: 1px solid #c2c0c2; 490 | } 491 | .btn-group .btn + .btn.active { 492 | border-left: 0; 493 | } 494 | .btn-group .active { 495 | color: #fff; 496 | border: 1px solid transparent; 497 | background-color: #6d6c6d; 498 | background-image: none; 499 | } 500 | .btn-group .active .icon { 501 | color: #fff; 502 | } 503 | 504 | .toolbar { 505 | min-height: 22px; 506 | box-shadow: inset 0 1px 0 #f5f4f5; 507 | background-color: #e8e6e8; 508 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e8e6e8), color-stop(100%, #d1cfd1)); 509 | background-image: -webkit-linear-gradient(top, #e8e6e8 0%, #d1cfd1 100%); 510 | background-image: linear-gradient(to bottom, #e8e6e8 0%, #d1cfd1 100%); 511 | } 512 | .toolbar:before, .toolbar:after { 513 | display: table; 514 | content: " "; 515 | } 516 | .toolbar:after { 517 | clear: both; 518 | } 519 | 520 | .toolbar-header { 521 | border-bottom: 1px solid #c2c0c2; 522 | } 523 | .toolbar-header .title { 524 | margin-top: 1px; 525 | } 526 | 527 | .toolbar-footer { 528 | border-top: 1px solid #c2c0c2; 529 | -webkit-app-region: drag; 530 | } 531 | 532 | .title { 533 | margin: 0; 534 | font-size: 12px; 535 | font-weight: 400; 536 | text-align: center; 537 | color: #555; 538 | cursor: default; 539 | } 540 | 541 | .toolbar-borderless { 542 | border-top: 0; 543 | border-bottom: 0; 544 | } 545 | 546 | .toolbar-actions { 547 | margin-top: 4px; 548 | margin-bottom: 3px; 549 | padding-right: 3px; 550 | padding-left: 3px; 551 | padding-bottom: 3px; 552 | -webkit-app-region: drag; 553 | } 554 | .toolbar-actions:before, .toolbar-actions:after { 555 | display: table; 556 | content: " "; 557 | } 558 | .toolbar-actions:after { 559 | clear: both; 560 | } 561 | .toolbar-actions > .btn, 562 | .toolbar-actions > .btn-group { 563 | margin-left: 4px; 564 | margin-right: 4px; 565 | } 566 | 567 | label { 568 | display: inline-block; 569 | font-size: 13px; 570 | margin-bottom: 5px; 571 | white-space: nowrap; 572 | overflow: hidden; 573 | text-overflow: ellipsis; 574 | } 575 | 576 | input[type="search"] { 577 | box-sizing: border-box; 578 | } 579 | 580 | input[type="radio"], 581 | input[type="checkbox"] { 582 | margin: 4px 0 0; 583 | line-height: normal; 584 | } 585 | 586 | .form-control { 587 | display: inline-block; 588 | width: 100%; 589 | min-height: 25px; 590 | padding: 5px 10px; 591 | font-size: 13px; 592 | line-height: 1.6; 593 | background-color: #fff; 594 | border: 1px solid #ddd; 595 | border-radius: 4px; 596 | outline: none; 597 | } 598 | .form-control:focus { 599 | border-color: #6db3fd; 600 | box-shadow: 3px 3px 0 #6db3fd, -3px -3px 0 #6db3fd, -3px 3px 0 #6db3fd, 3px -3px 0 #6db3fd; 601 | } 602 | 603 | textarea { 604 | height: auto; 605 | } 606 | 607 | .form-group { 608 | margin-bottom: 10px; 609 | } 610 | 611 | .radio, 612 | .checkbox { 613 | position: relative; 614 | display: block; 615 | margin-top: 10px; 616 | margin-bottom: 10px; 617 | } 618 | .radio label, 619 | .checkbox label { 620 | padding-left: 20px; 621 | margin-bottom: 0; 622 | font-weight: normal; 623 | } 624 | 625 | .radio input[type="radio"], 626 | .radio-inline input[type="radio"], 627 | .checkbox input[type="checkbox"], 628 | .checkbox-inline input[type="checkbox"] { 629 | position: absolute; 630 | margin-left: -20px; 631 | margin-top: 4px; 632 | } 633 | 634 | .form-actions .btn { 635 | margin-right: 10px; 636 | } 637 | .form-actions .btn:last-child { 638 | margin-right: 0; 639 | } 640 | 641 | .pane-group { 642 | position: absolute; 643 | top: 0; 644 | right: 0; 645 | bottom: 0; 646 | left: 0; 647 | display: flex; 648 | } 649 | 650 | .pane { 651 | position: relative; 652 | overflow-y: auto; 653 | flex: 1; 654 | border-left: 1px solid #ddd; 655 | } 656 | .pane:first-child { 657 | border-left: 0; 658 | } 659 | 660 | .pane-sm { 661 | max-width: 220px; 662 | min-width: 150px; 663 | } 664 | 665 | .pane-mini { 666 | width: 80px; 667 | flex: none; 668 | } 669 | 670 | .pane-one-fourth { 671 | width: 25%; 672 | flex: none; 673 | } 674 | 675 | .pane-one-third { 676 | width: 33.3%; 677 | } 678 | 679 | img { 680 | -webkit-user-drag: text; 681 | } 682 | 683 | .img-circle { 684 | border-radius: 50%; 685 | } 686 | 687 | .img-rounded { 688 | border-radius: 4px; 689 | } 690 | 691 | .list-group { 692 | width: 100%; 693 | list-style: none; 694 | margin: 0; 695 | padding: 0; 696 | } 697 | .list-group * { 698 | margin: 0; 699 | white-space: nowrap; 700 | overflow: hidden; 701 | text-overflow: ellipsis; 702 | } 703 | 704 | .list-group-item { 705 | padding: 10px; 706 | font-size: 12px; 707 | color: #414142; 708 | border-top: 1px solid #ddd; 709 | } 710 | .list-group-item:first-child { 711 | border-top: 0; 712 | } 713 | .list-group-item.active, .list-group-item.selected { 714 | color: #fff; 715 | background-color: #116cd6; 716 | } 717 | 718 | .list-group-header { 719 | padding: 10px; 720 | } 721 | 722 | .media-object { 723 | margin-top: 3px; 724 | } 725 | 726 | .media-object.pull-left { 727 | margin-right: 10px; 728 | } 729 | 730 | .media-object.pull-right { 731 | margin-left: 10px; 732 | } 733 | 734 | .media-body { 735 | overflow: hidden; 736 | } 737 | 738 | .nav-group { 739 | font-size: 14px; 740 | } 741 | 742 | .nav-group-item { 743 | padding: 2px 10px 2px 25px; 744 | display: block; 745 | color: #333; 746 | text-decoration: none; 747 | white-space: nowrap; 748 | overflow: hidden; 749 | text-overflow: ellipsis; 750 | } 751 | .nav-group-item:active, .nav-group-item.active { 752 | background-color: #dcdfe1; 753 | } 754 | .nav-group-item .icon { 755 | width: 19px; 756 | height: 18px; 757 | float: left; 758 | color: #737475; 759 | margin-top: -3px; 760 | margin-right: 7px; 761 | font-size: 18px; 762 | text-align: center; 763 | } 764 | 765 | .nav-group-title { 766 | margin: 0; 767 | padding: 10px 10px 2px; 768 | font-size: 12px; 769 | font-weight: 500; 770 | color: #666666; 771 | } 772 | 773 | @font-face { 774 | font-family: "photon-entypo"; 775 | src: url("../fonts/photon-entypo.eot"); 776 | src: url("../fonts/photon-entypo.eot?#iefix") format("eot"), url("../fonts/photon-entypo.woff") format("woff"), url("../fonts/photon-entypo.ttf") format("truetype"); 777 | font-weight: normal; 778 | font-style: normal; 779 | } 780 | .icon:before { 781 | position: relative; 782 | display: inline-block; 783 | font-family: "photon-entypo"; 784 | speak: none; 785 | font-size: 100%; 786 | font-style: normal; 787 | font-weight: normal; 788 | font-variant: normal; 789 | text-transform: none; 790 | line-height: 1; 791 | -webkit-font-smoothing: antialiased; 792 | -moz-osx-font-smoothing: grayscale; 793 | } 794 | 795 | .icon-note:before { 796 | content: '\e800'; 797 | } 798 | 799 | /* '' */ 800 | .icon-note-beamed:before { 801 | content: '\e801'; 802 | } 803 | 804 | /* '' */ 805 | .icon-music:before { 806 | content: '\e802'; 807 | } 808 | 809 | /* '' */ 810 | .icon-search:before { 811 | content: '\e803'; 812 | } 813 | 814 | /* '' */ 815 | .icon-flashlight:before { 816 | content: '\e804'; 817 | } 818 | 819 | /* '' */ 820 | .icon-mail:before { 821 | content: '\e805'; 822 | } 823 | 824 | /* '' */ 825 | .icon-heart:before { 826 | content: '\e806'; 827 | } 828 | 829 | /* '' */ 830 | .icon-heart-empty:before { 831 | content: '\e807'; 832 | } 833 | 834 | /* '' */ 835 | .icon-star:before { 836 | content: '\e808'; 837 | } 838 | 839 | /* '' */ 840 | .icon-star-empty:before { 841 | content: '\e809'; 842 | } 843 | 844 | /* '' */ 845 | .icon-user:before { 846 | content: '\e80a'; 847 | } 848 | 849 | /* '' */ 850 | .icon-users:before { 851 | content: '\e80b'; 852 | } 853 | 854 | /* '' */ 855 | .icon-user-add:before { 856 | content: '\e80c'; 857 | } 858 | 859 | /* '' */ 860 | .icon-video:before { 861 | content: '\e80d'; 862 | } 863 | 864 | /* '' */ 865 | .icon-picture:before { 866 | content: '\e80e'; 867 | } 868 | 869 | /* '' */ 870 | .icon-camera:before { 871 | content: '\e80f'; 872 | } 873 | 874 | /* '' */ 875 | .icon-layout:before { 876 | content: '\e810'; 877 | } 878 | 879 | /* '' */ 880 | .icon-menu:before { 881 | content: '\e811'; 882 | } 883 | 884 | /* '' */ 885 | .icon-check:before { 886 | content: '\e812'; 887 | } 888 | 889 | /* '' */ 890 | .icon-cancel:before { 891 | content: '\e813'; 892 | } 893 | 894 | /* '' */ 895 | .icon-cancel-circled:before { 896 | content: '\e814'; 897 | } 898 | 899 | /* '' */ 900 | .icon-cancel-squared:before { 901 | content: '\e815'; 902 | } 903 | 904 | /* '' */ 905 | .icon-plus:before { 906 | content: '\e816'; 907 | } 908 | 909 | /* '' */ 910 | .icon-plus-circled:before { 911 | content: '\e817'; 912 | } 913 | 914 | /* '' */ 915 | .icon-plus-squared:before { 916 | content: '\e818'; 917 | } 918 | 919 | /* '' */ 920 | .icon-minus:before { 921 | content: '\e819'; 922 | } 923 | 924 | /* '' */ 925 | .icon-minus-circled:before { 926 | content: '\e81a'; 927 | } 928 | 929 | /* '' */ 930 | .icon-minus-squared:before { 931 | content: '\e81b'; 932 | } 933 | 934 | /* '' */ 935 | .icon-help:before { 936 | content: '\e81c'; 937 | } 938 | 939 | /* '' */ 940 | .icon-help-circled:before { 941 | content: '\e81d'; 942 | } 943 | 944 | /* '' */ 945 | .icon-info:before { 946 | content: '\e81e'; 947 | } 948 | 949 | /* '' */ 950 | .icon-info-circled:before { 951 | content: '\e81f'; 952 | } 953 | 954 | /* '' */ 955 | .icon-back:before { 956 | content: '\e820'; 957 | } 958 | 959 | /* '' */ 960 | .icon-home:before { 961 | content: '\e821'; 962 | } 963 | 964 | /* '' */ 965 | .icon-link:before { 966 | content: '\e822'; 967 | } 968 | 969 | /* '' */ 970 | .icon-attach:before { 971 | content: '\e823'; 972 | } 973 | 974 | /* '' */ 975 | .icon-lock:before { 976 | content: '\e824'; 977 | } 978 | 979 | /* '' */ 980 | .icon-lock-open:before { 981 | content: '\e825'; 982 | } 983 | 984 | /* '' */ 985 | .icon-eye:before { 986 | content: '\e826'; 987 | } 988 | 989 | /* '' */ 990 | .icon-tag:before { 991 | content: '\e827'; 992 | } 993 | 994 | /* '' */ 995 | .icon-bookmark:before { 996 | content: '\e828'; 997 | } 998 | 999 | /* '' */ 1000 | .icon-bookmarks:before { 1001 | content: '\e829'; 1002 | } 1003 | 1004 | /* '' */ 1005 | .icon-flag:before { 1006 | content: '\e82a'; 1007 | } 1008 | 1009 | /* '' */ 1010 | .icon-thumbs-up:before { 1011 | content: '\e82b'; 1012 | } 1013 | 1014 | /* '' */ 1015 | .icon-thumbs-down:before { 1016 | content: '\e82c'; 1017 | } 1018 | 1019 | /* '' */ 1020 | .icon-download:before { 1021 | content: '\e82d'; 1022 | } 1023 | 1024 | /* '' */ 1025 | .icon-upload:before { 1026 | content: '\e82e'; 1027 | } 1028 | 1029 | /* '' */ 1030 | .icon-upload-cloud:before { 1031 | content: '\e82f'; 1032 | } 1033 | 1034 | /* '' */ 1035 | .icon-reply:before { 1036 | content: '\e830'; 1037 | } 1038 | 1039 | /* '' */ 1040 | .icon-reply-all:before { 1041 | content: '\e831'; 1042 | } 1043 | 1044 | /* '' */ 1045 | .icon-forward:before { 1046 | content: '\e832'; 1047 | } 1048 | 1049 | /* '' */ 1050 | .icon-quote:before { 1051 | content: '\e833'; 1052 | } 1053 | 1054 | /* '' */ 1055 | .icon-code:before { 1056 | content: '\e834'; 1057 | } 1058 | 1059 | /* '' */ 1060 | .icon-export:before { 1061 | content: '\e835'; 1062 | } 1063 | 1064 | /* '' */ 1065 | .icon-pencil:before { 1066 | content: '\e836'; 1067 | } 1068 | 1069 | /* '' */ 1070 | .icon-feather:before { 1071 | content: '\e837'; 1072 | } 1073 | 1074 | /* '' */ 1075 | .icon-print:before { 1076 | content: '\e838'; 1077 | } 1078 | 1079 | /* '' */ 1080 | .icon-retweet:before { 1081 | content: '\e839'; 1082 | } 1083 | 1084 | /* '' */ 1085 | .icon-keyboard:before { 1086 | content: '\e83a'; 1087 | } 1088 | 1089 | /* '' */ 1090 | .icon-comment:before { 1091 | content: '\e83b'; 1092 | } 1093 | 1094 | /* '' */ 1095 | .icon-chat:before { 1096 | content: '\e83c'; 1097 | } 1098 | 1099 | /* '' */ 1100 | .icon-bell:before { 1101 | content: '\e83d'; 1102 | } 1103 | 1104 | /* '' */ 1105 | .icon-attention:before { 1106 | content: '\e83e'; 1107 | } 1108 | 1109 | /* '' */ 1110 | .icon-alert:before { 1111 | content: '\e83f'; 1112 | } 1113 | 1114 | /* '' */ 1115 | .icon-vcard:before { 1116 | content: '\e840'; 1117 | } 1118 | 1119 | /* '' */ 1120 | .icon-address:before { 1121 | content: '\e841'; 1122 | } 1123 | 1124 | /* '' */ 1125 | .icon-location:before { 1126 | content: '\e842'; 1127 | } 1128 | 1129 | /* '' */ 1130 | .icon-map:before { 1131 | content: '\e843'; 1132 | } 1133 | 1134 | /* '' */ 1135 | .icon-direction:before { 1136 | content: '\e844'; 1137 | } 1138 | 1139 | /* '' */ 1140 | .icon-compass:before { 1141 | content: '\e845'; 1142 | } 1143 | 1144 | /* '' */ 1145 | .icon-cup:before { 1146 | content: '\e846'; 1147 | } 1148 | 1149 | /* '' */ 1150 | .icon-trash:before { 1151 | content: '\e847'; 1152 | } 1153 | 1154 | /* '' */ 1155 | .icon-doc:before { 1156 | content: '\e848'; 1157 | } 1158 | 1159 | /* '' */ 1160 | .icon-docs:before { 1161 | content: '\e849'; 1162 | } 1163 | 1164 | /* '' */ 1165 | .icon-doc-landscape:before { 1166 | content: '\e84a'; 1167 | } 1168 | 1169 | /* '' */ 1170 | .icon-doc-text:before { 1171 | content: '\e84b'; 1172 | } 1173 | 1174 | /* '' */ 1175 | .icon-doc-text-inv:before { 1176 | content: '\e84c'; 1177 | } 1178 | 1179 | /* '' */ 1180 | .icon-newspaper:before { 1181 | content: '\e84d'; 1182 | } 1183 | 1184 | /* '' */ 1185 | .icon-book-open:before { 1186 | content: '\e84e'; 1187 | } 1188 | 1189 | /* '' */ 1190 | .icon-book:before { 1191 | content: '\e84f'; 1192 | } 1193 | 1194 | /* '' */ 1195 | .icon-folder:before { 1196 | content: '\e850'; 1197 | } 1198 | 1199 | /* '' */ 1200 | .icon-archive:before { 1201 | content: '\e851'; 1202 | } 1203 | 1204 | /* '' */ 1205 | .icon-box:before { 1206 | content: '\e852'; 1207 | } 1208 | 1209 | /* '' */ 1210 | .icon-rss:before { 1211 | content: '\e853'; 1212 | } 1213 | 1214 | /* '' */ 1215 | .icon-phone:before { 1216 | content: '\e854'; 1217 | } 1218 | 1219 | /* '' */ 1220 | .icon-cog:before { 1221 | content: '\e855'; 1222 | } 1223 | 1224 | /* '' */ 1225 | .icon-tools:before { 1226 | content: '\e856'; 1227 | } 1228 | 1229 | /* '' */ 1230 | .icon-share:before { 1231 | content: '\e857'; 1232 | } 1233 | 1234 | /* '' */ 1235 | .icon-shareable:before { 1236 | content: '\e858'; 1237 | } 1238 | 1239 | /* '' */ 1240 | .icon-basket:before { 1241 | content: '\e859'; 1242 | } 1243 | 1244 | /* '' */ 1245 | .icon-bag:before { 1246 | content: '\e85a'; 1247 | } 1248 | 1249 | /* '' */ 1250 | .icon-calendar:before { 1251 | content: '\e85b'; 1252 | } 1253 | 1254 | /* '' */ 1255 | .icon-login:before { 1256 | content: '\e85c'; 1257 | } 1258 | 1259 | /* '' */ 1260 | .icon-logout:before { 1261 | content: '\e85d'; 1262 | } 1263 | 1264 | /* '' */ 1265 | .icon-mic:before { 1266 | content: '\e85e'; 1267 | } 1268 | 1269 | /* '' */ 1270 | .icon-mute:before { 1271 | content: '\e85f'; 1272 | } 1273 | 1274 | /* '' */ 1275 | .icon-sound:before { 1276 | content: '\e860'; 1277 | } 1278 | 1279 | /* '' */ 1280 | .icon-volume:before { 1281 | content: '\e861'; 1282 | } 1283 | 1284 | /* '' */ 1285 | .icon-clock:before { 1286 | content: '\e862'; 1287 | } 1288 | 1289 | /* '' */ 1290 | .icon-hourglass:before { 1291 | content: '\e863'; 1292 | } 1293 | 1294 | /* '' */ 1295 | .icon-lamp:before { 1296 | content: '\e864'; 1297 | } 1298 | 1299 | /* '' */ 1300 | .icon-light-down:before { 1301 | content: '\e865'; 1302 | } 1303 | 1304 | /* '' */ 1305 | .icon-light-up:before { 1306 | content: '\e866'; 1307 | } 1308 | 1309 | /* '' */ 1310 | .icon-adjust:before { 1311 | content: '\e867'; 1312 | } 1313 | 1314 | /* '' */ 1315 | .icon-block:before { 1316 | content: '\e868'; 1317 | } 1318 | 1319 | /* '' */ 1320 | .icon-resize-full:before { 1321 | content: '\e869'; 1322 | } 1323 | 1324 | /* '' */ 1325 | .icon-resize-small:before { 1326 | content: '\e86a'; 1327 | } 1328 | 1329 | /* '' */ 1330 | .icon-popup:before { 1331 | content: '\e86b'; 1332 | } 1333 | 1334 | /* '' */ 1335 | .icon-publish:before { 1336 | content: '\e86c'; 1337 | } 1338 | 1339 | /* '' */ 1340 | .icon-window:before { 1341 | content: '\e86d'; 1342 | } 1343 | 1344 | /* '' */ 1345 | .icon-arrow-combo:before { 1346 | content: '\e86e'; 1347 | } 1348 | 1349 | /* '' */ 1350 | .icon-down-circled:before { 1351 | content: '\e86f'; 1352 | } 1353 | 1354 | /* '' */ 1355 | .icon-left-circled:before { 1356 | content: '\e870'; 1357 | } 1358 | 1359 | /* '' */ 1360 | .icon-right-circled:before { 1361 | content: '\e871'; 1362 | } 1363 | 1364 | /* '' */ 1365 | .icon-up-circled:before { 1366 | content: '\e872'; 1367 | } 1368 | 1369 | /* '' */ 1370 | .icon-down-open:before { 1371 | content: '\e873'; 1372 | } 1373 | 1374 | /* '' */ 1375 | .icon-left-open:before { 1376 | content: '\e874'; 1377 | } 1378 | 1379 | /* '' */ 1380 | .icon-right-open:before { 1381 | content: '\e875'; 1382 | } 1383 | 1384 | /* '' */ 1385 | .icon-up-open:before { 1386 | content: '\e876'; 1387 | } 1388 | 1389 | /* '' */ 1390 | .icon-down-open-mini:before { 1391 | content: '\e877'; 1392 | } 1393 | 1394 | /* '' */ 1395 | .icon-left-open-mini:before { 1396 | content: '\e878'; 1397 | } 1398 | 1399 | /* '' */ 1400 | .icon-right-open-mini:before { 1401 | content: '\e879'; 1402 | } 1403 | 1404 | /* '' */ 1405 | .icon-up-open-mini:before { 1406 | content: '\e87a'; 1407 | } 1408 | 1409 | /* '' */ 1410 | .icon-down-open-big:before { 1411 | content: '\e87b'; 1412 | } 1413 | 1414 | /* '' */ 1415 | .icon-left-open-big:before { 1416 | content: '\e87c'; 1417 | } 1418 | 1419 | /* '' */ 1420 | .icon-right-open-big:before { 1421 | content: '\e87d'; 1422 | } 1423 | 1424 | /* '' */ 1425 | .icon-up-open-big:before { 1426 | content: '\e87e'; 1427 | } 1428 | 1429 | /* '' */ 1430 | .icon-down:before { 1431 | content: '\e87f'; 1432 | } 1433 | 1434 | /* '' */ 1435 | .icon-left:before { 1436 | content: '\e880'; 1437 | } 1438 | 1439 | /* '' */ 1440 | .icon-right:before { 1441 | content: '\e881'; 1442 | } 1443 | 1444 | /* '' */ 1445 | .icon-up:before { 1446 | content: '\e882'; 1447 | } 1448 | 1449 | /* '' */ 1450 | .icon-down-dir:before { 1451 | content: '\e883'; 1452 | } 1453 | 1454 | /* '' */ 1455 | .icon-left-dir:before { 1456 | content: '\e884'; 1457 | } 1458 | 1459 | /* '' */ 1460 | .icon-right-dir:before { 1461 | content: '\e885'; 1462 | } 1463 | 1464 | /* '' */ 1465 | .icon-up-dir:before { 1466 | content: '\e886'; 1467 | } 1468 | 1469 | /* '' */ 1470 | .icon-down-bold:before { 1471 | content: '\e887'; 1472 | } 1473 | 1474 | /* '' */ 1475 | .icon-left-bold:before { 1476 | content: '\e888'; 1477 | } 1478 | 1479 | /* '' */ 1480 | .icon-right-bold:before { 1481 | content: '\e889'; 1482 | } 1483 | 1484 | /* '' */ 1485 | .icon-up-bold:before { 1486 | content: '\e88a'; 1487 | } 1488 | 1489 | /* '' */ 1490 | .icon-down-thin:before { 1491 | content: '\e88b'; 1492 | } 1493 | 1494 | /* '' */ 1495 | .icon-left-thin:before { 1496 | content: '\e88c'; 1497 | } 1498 | 1499 | /* '' */ 1500 | .icon-right-thin:before { 1501 | content: '\e88d'; 1502 | } 1503 | 1504 | /* '' */ 1505 | .icon-up-thin:before { 1506 | content: '\e88e'; 1507 | } 1508 | 1509 | /* '' */ 1510 | .icon-ccw:before { 1511 | content: '\e88f'; 1512 | } 1513 | 1514 | /* '' */ 1515 | .icon-cw:before { 1516 | content: '\e890'; 1517 | } 1518 | 1519 | /* '' */ 1520 | .icon-arrows-ccw:before { 1521 | content: '\e891'; 1522 | } 1523 | 1524 | /* '' */ 1525 | .icon-level-down:before { 1526 | content: '\e892'; 1527 | } 1528 | 1529 | /* '' */ 1530 | .icon-level-up:before { 1531 | content: '\e893'; 1532 | } 1533 | 1534 | /* '' */ 1535 | .icon-shuffle:before { 1536 | content: '\e894'; 1537 | } 1538 | 1539 | /* '' */ 1540 | .icon-loop:before { 1541 | content: '\e895'; 1542 | } 1543 | 1544 | /* '' */ 1545 | .icon-switch:before { 1546 | content: '\e896'; 1547 | } 1548 | 1549 | /* '' */ 1550 | .icon-play:before { 1551 | content: '\e897'; 1552 | } 1553 | 1554 | /* '' */ 1555 | .icon-stop:before { 1556 | content: '\e898'; 1557 | } 1558 | 1559 | /* '' */ 1560 | .icon-pause:before { 1561 | content: '\e899'; 1562 | } 1563 | 1564 | /* '' */ 1565 | .icon-record:before { 1566 | content: '\e89a'; 1567 | } 1568 | 1569 | /* '' */ 1570 | .icon-to-end:before { 1571 | content: '\e89b'; 1572 | } 1573 | 1574 | /* '' */ 1575 | .icon-to-start:before { 1576 | content: '\e89c'; 1577 | } 1578 | 1579 | /* '' */ 1580 | .icon-fast-forward:before { 1581 | content: '\e89d'; 1582 | } 1583 | 1584 | /* '' */ 1585 | .icon-fast-backward:before { 1586 | content: '\e89e'; 1587 | } 1588 | 1589 | /* '' */ 1590 | .icon-progress-0:before { 1591 | content: '\e89f'; 1592 | } 1593 | 1594 | /* '' */ 1595 | .icon-progress-1:before { 1596 | content: '\e8a0'; 1597 | } 1598 | 1599 | /* '' */ 1600 | .icon-progress-2:before { 1601 | content: '\e8a1'; 1602 | } 1603 | 1604 | /* '' */ 1605 | .icon-progress-3:before { 1606 | content: '\e8a2'; 1607 | } 1608 | 1609 | /* '' */ 1610 | .icon-target:before { 1611 | content: '\e8a3'; 1612 | } 1613 | 1614 | /* '' */ 1615 | .icon-palette:before { 1616 | content: '\e8a4'; 1617 | } 1618 | 1619 | /* '' */ 1620 | .icon-list:before { 1621 | content: '\e8a5'; 1622 | } 1623 | 1624 | /* '' */ 1625 | .icon-list-add:before { 1626 | content: '\e8a6'; 1627 | } 1628 | 1629 | /* '' */ 1630 | .icon-signal:before { 1631 | content: '\e8a7'; 1632 | } 1633 | 1634 | /* '' */ 1635 | .icon-trophy:before { 1636 | content: '\e8a8'; 1637 | } 1638 | 1639 | /* '' */ 1640 | .icon-battery:before { 1641 | content: '\e8a9'; 1642 | } 1643 | 1644 | /* '' */ 1645 | .icon-back-in-time:before { 1646 | content: '\e8aa'; 1647 | } 1648 | 1649 | /* '' */ 1650 | .icon-monitor:before { 1651 | content: '\e8ab'; 1652 | } 1653 | 1654 | /* '' */ 1655 | .icon-mobile:before { 1656 | content: '\e8ac'; 1657 | } 1658 | 1659 | /* '' */ 1660 | .icon-network:before { 1661 | content: '\e8ad'; 1662 | } 1663 | 1664 | /* '' */ 1665 | .icon-cd:before { 1666 | content: '\e8ae'; 1667 | } 1668 | 1669 | /* '' */ 1670 | .icon-inbox:before { 1671 | content: '\e8af'; 1672 | } 1673 | 1674 | /* '' */ 1675 | .icon-install:before { 1676 | content: '\e8b0'; 1677 | } 1678 | 1679 | /* '' */ 1680 | .icon-globe:before { 1681 | content: '\e8b1'; 1682 | } 1683 | 1684 | /* '' */ 1685 | .icon-cloud:before { 1686 | content: '\e8b2'; 1687 | } 1688 | 1689 | /* '' */ 1690 | .icon-cloud-thunder:before { 1691 | content: '\e8b3'; 1692 | } 1693 | 1694 | /* '' */ 1695 | .icon-flash:before { 1696 | content: '\e8b4'; 1697 | } 1698 | 1699 | /* '' */ 1700 | .icon-moon:before { 1701 | content: '\e8b5'; 1702 | } 1703 | 1704 | /* '' */ 1705 | .icon-flight:before { 1706 | content: '\e8b6'; 1707 | } 1708 | 1709 | /* '' */ 1710 | .icon-paper-plane:before { 1711 | content: '\e8b7'; 1712 | } 1713 | 1714 | /* '' */ 1715 | .icon-leaf:before { 1716 | content: '\e8b8'; 1717 | } 1718 | 1719 | /* '' */ 1720 | .icon-lifebuoy:before { 1721 | content: '\e8b9'; 1722 | } 1723 | 1724 | /* '' */ 1725 | .icon-mouse:before { 1726 | content: '\e8ba'; 1727 | } 1728 | 1729 | /* '' */ 1730 | .icon-briefcase:before { 1731 | content: '\e8bb'; 1732 | } 1733 | 1734 | /* '' */ 1735 | .icon-suitcase:before { 1736 | content: '\e8bc'; 1737 | } 1738 | 1739 | /* '' */ 1740 | .icon-dot:before { 1741 | content: '\e8bd'; 1742 | } 1743 | 1744 | /* '' */ 1745 | .icon-dot-2:before { 1746 | content: '\e8be'; 1747 | } 1748 | 1749 | /* '' */ 1750 | .icon-dot-3:before { 1751 | content: '\e8bf'; 1752 | } 1753 | 1754 | /* '' */ 1755 | .icon-brush:before { 1756 | content: '\e8c0'; 1757 | } 1758 | 1759 | /* '' */ 1760 | .icon-magnet:before { 1761 | content: '\e8c1'; 1762 | } 1763 | 1764 | /* '' */ 1765 | .icon-infinity:before { 1766 | content: '\e8c2'; 1767 | } 1768 | 1769 | /* '' */ 1770 | .icon-erase:before { 1771 | content: '\e8c3'; 1772 | } 1773 | 1774 | /* '' */ 1775 | .icon-chart-pie:before { 1776 | content: '\e8c4'; 1777 | } 1778 | 1779 | /* '' */ 1780 | .icon-chart-line:before { 1781 | content: '\e8c5'; 1782 | } 1783 | 1784 | /* '' */ 1785 | .icon-chart-bar:before { 1786 | content: '\e8c6'; 1787 | } 1788 | 1789 | /* '' */ 1790 | .icon-chart-area:before { 1791 | content: '\e8c7'; 1792 | } 1793 | 1794 | /* '' */ 1795 | .icon-tape:before { 1796 | content: '\e8c8'; 1797 | } 1798 | 1799 | /* '' */ 1800 | .icon-graduation-cap:before { 1801 | content: '\e8c9'; 1802 | } 1803 | 1804 | /* '' */ 1805 | .icon-language:before { 1806 | content: '\e8ca'; 1807 | } 1808 | 1809 | /* '' */ 1810 | .icon-ticket:before { 1811 | content: '\e8cb'; 1812 | } 1813 | 1814 | /* '' */ 1815 | .icon-water:before { 1816 | content: '\e8cc'; 1817 | } 1818 | 1819 | /* '' */ 1820 | .icon-droplet:before { 1821 | content: '\e8cd'; 1822 | } 1823 | 1824 | /* '' */ 1825 | .icon-air:before { 1826 | content: '\e8ce'; 1827 | } 1828 | 1829 | /* '' */ 1830 | .icon-credit-card:before { 1831 | content: '\e8cf'; 1832 | } 1833 | 1834 | /* '' */ 1835 | .icon-floppy:before { 1836 | content: '\e8d0'; 1837 | } 1838 | 1839 | /* '' */ 1840 | .icon-clipboard:before { 1841 | content: '\e8d1'; 1842 | } 1843 | 1844 | /* '' */ 1845 | .icon-megaphone:before { 1846 | content: '\e8d2'; 1847 | } 1848 | 1849 | /* '' */ 1850 | .icon-database:before { 1851 | content: '\e8d3'; 1852 | } 1853 | 1854 | /* '' */ 1855 | .icon-drive:before { 1856 | content: '\e8d4'; 1857 | } 1858 | 1859 | /* '' */ 1860 | .icon-bucket:before { 1861 | content: '\e8d5'; 1862 | } 1863 | 1864 | /* '' */ 1865 | .icon-thermometer:before { 1866 | content: '\e8d6'; 1867 | } 1868 | 1869 | /* '' */ 1870 | .icon-key:before { 1871 | content: '\e8d7'; 1872 | } 1873 | 1874 | /* '' */ 1875 | .icon-flow-cascade:before { 1876 | content: '\e8d8'; 1877 | } 1878 | 1879 | /* '' */ 1880 | .icon-flow-branch:before { 1881 | content: '\e8d9'; 1882 | } 1883 | 1884 | /* '' */ 1885 | .icon-flow-tree:before { 1886 | content: '\e8da'; 1887 | } 1888 | 1889 | /* '' */ 1890 | .icon-flow-line:before { 1891 | content: '\e8db'; 1892 | } 1893 | 1894 | /* '' */ 1895 | .icon-flow-parallel:before { 1896 | content: '\e8dc'; 1897 | } 1898 | 1899 | /* '' */ 1900 | .icon-rocket:before { 1901 | content: '\e8dd'; 1902 | } 1903 | 1904 | /* '' */ 1905 | .icon-gauge:before { 1906 | content: '\e8de'; 1907 | } 1908 | 1909 | /* '' */ 1910 | .icon-traffic-cone:before { 1911 | content: '\e8df'; 1912 | } 1913 | 1914 | /* '' */ 1915 | .icon-cc:before { 1916 | content: '\e8e0'; 1917 | } 1918 | 1919 | /* '' */ 1920 | .icon-cc-by:before { 1921 | content: '\e8e1'; 1922 | } 1923 | 1924 | /* '' */ 1925 | .icon-cc-nc:before { 1926 | content: '\e8e2'; 1927 | } 1928 | 1929 | /* '' */ 1930 | .icon-cc-nc-eu:before { 1931 | content: '\e8e3'; 1932 | } 1933 | 1934 | /* '' */ 1935 | .icon-cc-nc-jp:before { 1936 | content: '\e8e4'; 1937 | } 1938 | 1939 | /* '' */ 1940 | .icon-cc-sa:before { 1941 | content: '\e8e5'; 1942 | } 1943 | 1944 | /* '' */ 1945 | .icon-cc-nd:before { 1946 | content: '\e8e6'; 1947 | } 1948 | 1949 | /* '' */ 1950 | .icon-cc-pd:before { 1951 | content: '\e8e7'; 1952 | } 1953 | 1954 | /* '' */ 1955 | .icon-cc-zero:before { 1956 | content: '\e8e8'; 1957 | } 1958 | 1959 | /* '' */ 1960 | .icon-cc-share:before { 1961 | content: '\e8e9'; 1962 | } 1963 | 1964 | /* '' */ 1965 | .icon-cc-remix:before { 1966 | content: '\e8ea'; 1967 | } 1968 | 1969 | /* '' */ 1970 | .icon-github:before { 1971 | content: '\e8eb'; 1972 | } 1973 | 1974 | /* '' */ 1975 | .icon-github-circled:before { 1976 | content: '\e8ec'; 1977 | } 1978 | 1979 | /* '' */ 1980 | .icon-flickr:before { 1981 | content: '\e8ed'; 1982 | } 1983 | 1984 | /* '' */ 1985 | .icon-flickr-circled:before { 1986 | content: '\e8ee'; 1987 | } 1988 | 1989 | /* '' */ 1990 | .icon-vimeo:before { 1991 | content: '\e8ef'; 1992 | } 1993 | 1994 | /* '' */ 1995 | .icon-vimeo-circled:before { 1996 | content: '\e8f0'; 1997 | } 1998 | 1999 | /* '' */ 2000 | .icon-twitter:before { 2001 | content: '\e8f1'; 2002 | } 2003 | 2004 | /* '' */ 2005 | .icon-twitter-circled:before { 2006 | content: '\e8f2'; 2007 | } 2008 | 2009 | /* '' */ 2010 | .icon-facebook:before { 2011 | content: '\e8f3'; 2012 | } 2013 | 2014 | /* '' */ 2015 | .icon-facebook-circled:before { 2016 | content: '\e8f4'; 2017 | } 2018 | 2019 | /* '' */ 2020 | .icon-facebook-squared:before { 2021 | content: '\e8f5'; 2022 | } 2023 | 2024 | /* '' */ 2025 | .icon-gplus:before { 2026 | content: '\e8f6'; 2027 | } 2028 | 2029 | /* '' */ 2030 | .icon-gplus-circled:before { 2031 | content: '\e8f7'; 2032 | } 2033 | 2034 | /* '' */ 2035 | .icon-pinterest:before { 2036 | content: '\e8f8'; 2037 | } 2038 | 2039 | /* '' */ 2040 | .icon-pinterest-circled:before { 2041 | content: '\e8f9'; 2042 | } 2043 | 2044 | /* '' */ 2045 | .icon-tumblr:before { 2046 | content: '\e8fa'; 2047 | } 2048 | 2049 | /* '' */ 2050 | .icon-tumblr-circled:before { 2051 | content: '\e8fb'; 2052 | } 2053 | 2054 | /* '' */ 2055 | .icon-linkedin:before { 2056 | content: '\e8fc'; 2057 | } 2058 | 2059 | /* '' */ 2060 | .icon-linkedin-circled:before { 2061 | content: '\e8fd'; 2062 | } 2063 | 2064 | /* '' */ 2065 | .icon-dribbble:before { 2066 | content: '\e8fe'; 2067 | } 2068 | 2069 | /* '' */ 2070 | .icon-dribbble-circled:before { 2071 | content: '\e8ff'; 2072 | } 2073 | 2074 | /* '' */ 2075 | .icon-stumbleupon:before { 2076 | content: '\e900'; 2077 | } 2078 | 2079 | /* '' */ 2080 | .icon-stumbleupon-circled:before { 2081 | content: '\e901'; 2082 | } 2083 | 2084 | /* '' */ 2085 | .icon-lastfm:before { 2086 | content: '\e902'; 2087 | } 2088 | 2089 | /* '' */ 2090 | .icon-lastfm-circled:before { 2091 | content: '\e903'; 2092 | } 2093 | 2094 | /* '' */ 2095 | .icon-rdio:before { 2096 | content: '\e904'; 2097 | } 2098 | 2099 | /* '' */ 2100 | .icon-rdio-circled:before { 2101 | content: '\e905'; 2102 | } 2103 | 2104 | /* '' */ 2105 | .icon-spotify:before { 2106 | content: '\e906'; 2107 | } 2108 | 2109 | /* '' */ 2110 | .icon-spotify-circled:before { 2111 | content: '\e907'; 2112 | } 2113 | 2114 | /* '' */ 2115 | .icon-qq:before { 2116 | content: '\e908'; 2117 | } 2118 | 2119 | /* '' */ 2120 | .icon-instagram:before { 2121 | content: '\e909'; 2122 | } 2123 | 2124 | /* '' */ 2125 | .icon-dropbox:before { 2126 | content: '\e90a'; 2127 | } 2128 | 2129 | /* '' */ 2130 | .icon-evernote:before { 2131 | content: '\e90b'; 2132 | } 2133 | 2134 | /* '' */ 2135 | .icon-flattr:before { 2136 | content: '\e90c'; 2137 | } 2138 | 2139 | /* '' */ 2140 | .icon-skype:before { 2141 | content: '\e90d'; 2142 | } 2143 | 2144 | /* '' */ 2145 | .icon-skype-circled:before { 2146 | content: '\e90e'; 2147 | } 2148 | 2149 | /* '' */ 2150 | .icon-renren:before { 2151 | content: '\e90f'; 2152 | } 2153 | 2154 | /* '' */ 2155 | .icon-sina-weibo:before { 2156 | content: '\e910'; 2157 | } 2158 | 2159 | /* '' */ 2160 | .icon-paypal:before { 2161 | content: '\e911'; 2162 | } 2163 | 2164 | /* '' */ 2165 | .icon-picasa:before { 2166 | content: '\e912'; 2167 | } 2168 | 2169 | /* '' */ 2170 | .icon-soundcloud:before { 2171 | content: '\e913'; 2172 | } 2173 | 2174 | /* '' */ 2175 | .icon-mixi:before { 2176 | content: '\e914'; 2177 | } 2178 | 2179 | /* '' */ 2180 | .icon-behance:before { 2181 | content: '\e915'; 2182 | } 2183 | 2184 | /* '' */ 2185 | .icon-google-circles:before { 2186 | content: '\e916'; 2187 | } 2188 | 2189 | /* '' */ 2190 | .icon-vkontakte:before { 2191 | content: '\e917'; 2192 | } 2193 | 2194 | /* '' */ 2195 | .icon-smashing:before { 2196 | content: '\e918'; 2197 | } 2198 | 2199 | /* '' */ 2200 | .icon-sweden:before { 2201 | content: '\e919'; 2202 | } 2203 | 2204 | /* '' */ 2205 | .icon-db-shape:before { 2206 | content: '\e91a'; 2207 | } 2208 | 2209 | /* '' */ 2210 | .icon-logo-db:before { 2211 | content: '\e91b'; 2212 | } 2213 | 2214 | /* '' */ 2215 | table { 2216 | width: 100%; 2217 | border: 0; 2218 | border-collapse: separate; 2219 | font-size: 12px; 2220 | text-align: left; 2221 | } 2222 | 2223 | thead { 2224 | background-color: #f5f5f4; 2225 | } 2226 | 2227 | tbody { 2228 | background-color: #fff; 2229 | } 2230 | 2231 | .table-striped tr:nth-child(even) { 2232 | background-color: #f5f5f4; 2233 | } 2234 | 2235 | tr:active, 2236 | .table-striped tr:active:nth-child(even) { 2237 | color: #fff; 2238 | background-color: #116cd6; 2239 | } 2240 | 2241 | thead tr:active { 2242 | color: #333; 2243 | background-color: #f5f5f4; 2244 | } 2245 | 2246 | th { 2247 | font-weight: normal; 2248 | border-right: 1px solid #ddd; 2249 | border-bottom: 1px solid #ddd; 2250 | } 2251 | 2252 | th, 2253 | td { 2254 | padding: 2px 15px; 2255 | white-space: nowrap; 2256 | overflow: hidden; 2257 | text-overflow: ellipsis; 2258 | } 2259 | th:last-child, 2260 | td:last-child { 2261 | border-right: 0; 2262 | } 2263 | 2264 | .tab-group { 2265 | margin-top: -1px; 2266 | display: flex; 2267 | border-top: 1px solid #989698; 2268 | border-bottom: 1px solid #989698; 2269 | } 2270 | 2271 | .tab-item { 2272 | position: relative; 2273 | flex: 1; 2274 | padding: 3px; 2275 | font-size: 12px; 2276 | text-align: center; 2277 | border-left: 1px solid #989698; 2278 | background-color: #b8b6b8; 2279 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b8b6b8), color-stop(100%, #b0aeb0)); 2280 | background-image: -webkit-linear-gradient(top, #b8b6b8 0%, #b0aeb0 100%); 2281 | background-image: linear-gradient(to bottom, #b8b6b8 0%, #b0aeb0 100%); 2282 | } 2283 | .tab-item:first-child { 2284 | border-left: 0; 2285 | } 2286 | .tab-item.active { 2287 | background-color: #d4d2d4; 2288 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d4d2d4), color-stop(100%, #cccacc)); 2289 | background-image: -webkit-linear-gradient(top, #d4d2d4 0%, #cccacc 100%); 2290 | background-image: linear-gradient(to bottom, #d4d2d4 0%, #cccacc 100%); 2291 | } 2292 | .tab-item .icon-close-tab { 2293 | position: absolute; 2294 | top: 50%; 2295 | left: 5px; 2296 | width: 15px; 2297 | height: 15px; 2298 | font-size: 15px; 2299 | line-height: 15px; 2300 | text-align: center; 2301 | color: #666; 2302 | opacity: 0; 2303 | transition: opacity .1s linear, background-color .1s linear; 2304 | border-radius: 3px; 2305 | transform: translateY(-50%); 2306 | z-index: 10; 2307 | } 2308 | .tab-item:after { 2309 | position: absolute; 2310 | top: 0; 2311 | right: 0; 2312 | bottom: 0; 2313 | left: 0; 2314 | content: ""; 2315 | background-color: rgba(0, 0, 0, 0.08); 2316 | opacity: 0; 2317 | transition: opacity .1s linear; 2318 | z-index: 1; 2319 | } 2320 | .tab-item:hover:not(.active):after { 2321 | opacity: 1; 2322 | } 2323 | .tab-item:hover .icon-close-tab { 2324 | opacity: 1; 2325 | } 2326 | .tab-item .icon-close-tab:hover { 2327 | background-color: rgba(0, 0, 0, 0.08); 2328 | } 2329 | 2330 | .tab-item-fixed { 2331 | flex: none; 2332 | padding: 3px 10px; 2333 | } 2334 | -------------------------------------------------------------------------------- /vendor/photon/fonts/photon-entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/photon/fonts/photon-entypo.eot -------------------------------------------------------------------------------- /vendor/photon/fonts/photon-entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/photon/fonts/photon-entypo.ttf -------------------------------------------------------------------------------- /vendor/photon/fonts/photon-entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-userland/devtron/c2d06028bb75c5cc5567b272a866e6d6e75223df/vendor/photon/fonts/photon-entypo.woff --------------------------------------------------------------------------------