devtron
Version:
Electron DevTools Extension
1,849 lines (1,535 loc) • 965 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict'
const Chrome = require('./chrome-helpers')
const Eval = require('./eval')
const View = require('./view')
const metadata = require('../package')
class AboutView extends View {
constructor () {
super('about-view')
this.handleEvents()
this.apis = Chrome.getChromeAPIs()
this.render()
}
render () {
this.versionLabel.textContent = metadata.version
this.tabID.textContent = window.chrome.devtools.inspectedWindow.tabId
if (window.chrome.runtime) {
this.runtimeID.textContent = window.chrome.runtime.id
}
this.chromeAPIs.textContent = this.apis.sort().join('\n')
}
handleEvents () {
this.issueButton.addEventListener('click', () => this.reportIssue())
}
reportIssue () {
Eval.openExternal('https://github.com/electron/devtron/issues')
}
}
module.exports = AboutView
},{"../package":189,"./chrome-helpers":7,"./eval":9,"./view":28}],2:[function(require,module,exports){
'use strict'
const SelectableView = require('./selectable-view')
const Eval = require('./eval')
class AccessibilityElementView extends SelectableView {
constructor (element, parent) {
super('element-row')
this.path = element.selector
this.pathId = element.id
parent.appendChild(this.element)
this.render()
this.handleEvents(parent)
}
handleEvents (parent) {
this.listenForSelection(parent)
this.listenForSelectionKeys(parent.parentElement)
}
render () {
this.selectorPath.textContent = this.path
// Add a click-handler that will select the element.
// Uses the `accessibilityAuditMap` defined in accessibility.js
this.selectorPath.onclick = (evt) => {
evt.stopPropagation()
Eval.execute(`inspect(window.__devtron.accessibilityAuditMap.get(${this.pathId}))`)
}
}
filter (searchText) {
let matches = this.path.toLowerCase().includes(searchText)
matches ? this.show() : this.hide()
return matches
}
}
module.exports = AccessibilityElementView
},{"./eval":9,"./selectable-view":26}],3:[function(require,module,exports){
'use strict'
const ExpandableView = require('./expandable-view')
const AccessibilityElementView = require('./accessibility-element-view')
class AccessibilityRuleView extends ExpandableView {
constructor (rule, table) {
super('rule-row')
this.rule = rule
this.count = rule.elements.length
table.appendChild(this.element)
this.handleEvents(table)
this.render()
this.children = rule.elements.map((element) => {
return new AccessibilityElementView(element, table)
})
this.collapse()
}
handleEvents (table) {
this.listenForSelection(table)
this.listenForSelectionKeys(table.parentElement)
this.listenForExpanderKeys(table.parentElement)
}
render () {
this.status.textContent = this.rule.status
this.severity.textContent = this.rule.severity
this.ruleName.textContent = this.rule.title
this.elementCount.textContent = `(${this.count})`
if (this.count === 0) {
this.disclosure.style.visibility = 'hidden'
}
}
filter (searchText) {
this.collapse()
let matches = this.rule.title.toLowerCase().includes(searchText) || this.rule.status.toLowerCase().includes(searchText)
this.children.forEach((child) => {
if (child.filter(searchText)) matches = true
})
if (matches) {
this.markCollapsed()
this.show()
this.markExpanded()
} else {
this.hide()
}
return matches
}
}
module.exports = AccessibilityRuleView
},{"./accessibility-element-view":2,"./expandable-view":14}],4:[function(require,module,exports){
'use strict'
const Eval = require('./eval')
const AccessibilityRuleView = require('./accessibility-rule-view')
const View = require('./view')
const accessibility = require('./accessibility')
class AccessibilityView extends View {
constructor () {
super('accessibility-view')
this.handleEvents()
}
handleEvents () {
this.debounceInput(this.searchBox, () => this.filterAudits())
this.docsButton.addEventListener('click', () => this.openDocs())
this.accessibilityButton.addEventListener('click', () => this.audit())
}
audit () {
accessibility.audit().then((results) => {
return this.render(results)
})
}
render (results) {
this.tableDescription.classList.add('hidden')
this.accessibilityResultsTable.innerHTML = ''
this.children = results.map((result) => {
return new AccessibilityRuleView(result, this.accessibilityResultsTable)
})
this.children[0].select()
}
openDocs () {
Eval.openExternal('https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules')
}
filterAudits () {
const searchText = this.getFilterText()
if (searchText) {
this.children.forEach((child) => child.filter(searchText))
} else {
this.children.forEach((child) => {
child.show()
child.collapse()
})
}
}
getFilterText () {
return this.searchBox.value.toLowerCase()
}
}
module.exports = AccessibilityView
},{"./accessibility":5,"./accessibility-rule-view":3,"./eval":9,"./view":28}],5:[function(require,module,exports){
const Eval = require('./eval')
exports.audit = () => {
return Eval.execute(function () {
const {axs} = window.__devtron // defined in browser-globals.js
const config = new axs.AuditConfiguration({showUnsupportedRulesWarning: false})
const results = axs.Audit.run(config)
// Create a lookup map so users can click on an element to inspect it
let idCounter = 0
window.__devtron.accessibilityAuditMap = new Map()
results.forEach(function (result) {
const elements = result.elements || []
elements.forEach(function (element) {
const id = idCounter++
element.__accessibilityAuditId = id
window.__devtron.accessibilityAuditMap.set(id, element)
})
})
return results.map(function (result) {
const elements = result.elements || []
let status = 'N/A'
if (result.result === 'PASS') {
status = 'Pass'
} else if (result.result === 'FAIL') {
status = 'Fail'
}
return {
code: result.rule.code,
severity: result.rule.severity,
status: status,
title: result.rule.heading,
url: result.rule.url,
elements: elements.map(function (element) {
let selector = element.tagName.toLowerCase()
if (element.className) {
selector += '.' + element.className.split(' ').join('.')
}
return {
selector: selector,
id: element.__accessibilityAuditId
}
})
}
}).sort(function (resultA, resultB) {
const statusA = resultA.status
const statusB = resultB.status
const severityA = resultA.severity
const severityB = resultB.severity
if (statusA === statusB) {
if (severityA === severityB) {
return resultB.elements.length - resultA.elements.length
}
if (severityA === 'Severe') return -1
if (severityB === 'Severe') return 1
if (severityA === 'Warning') return -1
if (severityB === 'Warning') return 1
} else {
if (statusA === 'Fail') return -1
if (statusB === 'Fail') return 1
if (statusA === 'Pass') return -1
if (statusB === 'Pass') return 1
}
})
})
}
},{"./eval":9}],6:[function(require,module,exports){
// This defines globals that will be used in the browser context
// (via the content_scripts definition in manifest.json)
//
// It is generated via `npm run-script prepublish`
const axs = require('accessibility-developer-tools')
window.__devtron = window.__devtron || {}
window.__devtron.axs = axs
},{"accessibility-developer-tools":30}],7:[function(require,module,exports){
'use strict'
const objectPrototype = Object.getPrototypeOf({})
const isCustomClass = (object, prototype) => {
if (typeof object !== 'object') return false
if (Array.isArray(object)) return false
return prototype && prototype !== objectPrototype
}
const checkAPI = (apis, parent, name, value) => {
const api = parent + '.' + name
if (typeof value === 'object') {
findChromeAPIs(apis, api, value)
} else {
apis[api] = true
}
}
const findChromeAPIs = (apis, parent, object) => {
for (const name in object) {
checkAPI(apis, parent, name, object[name])
}
const prototype = Object.getPrototypeOf(object)
if (isCustomClass(object, prototype)) {
Object.getOwnPropertyNames(prototype).filter((name) => {
return name !== 'constructor'
}).forEach((name) => {
checkAPI(apis, parent, name, object[name])
})
}
return Object.keys(apis)
}
exports.getChromeAPIs = () => findChromeAPIs({}, 'chrome', window.chrome)
},{}],8:[function(require,module,exports){
'use strict'
const EventView = require('./event-view')
const ExpandableView = require('./expandable-view')
class EmitterView extends ExpandableView {
constructor (name, listeners, table) {
super('emitter-row')
this.name = name
this.count = Object.keys(listeners).reduce((count, name) => {
return count + listeners[name].length
}, 0)
table.appendChild(this.element)
this.render()
this.handleEvents(table)
this.children = Object.keys(listeners).map((name) => {
return new EventView(name, listeners[name], this, table)
})
this.collapse()
}
handleEvents (table) {
this.listenForSelection(table)
this.listenForSelectionKeys(table.parentElement)
this.listenForExpanderKeys(table.parentElement)
}
render () {
this.emitterName.textContent = this.name
this.listenerCount.textContent = `(${this.count})`
}
filter (searchText) {
this.collapse()
let matches = this.name.includes(searchText)
this.children.forEach((child) => {
if (child.filter(searchText)) matches = true
})
if (matches) {
this.markCollapsed()
this.show()
this.markExpanded()
} else {
this.hide()
}
return matches
}
}
module.exports = EmitterView
},{"./event-view":12,"./expandable-view":14}],9:[function(require,module,exports){
(function (process){
'use strict'
class Eval {
static execute (expression) {
if (typeof expression === 'function') {
expression = `(${expression})`
if (arguments.length > 1) {
let expressionArgs = JSON.stringify(Array.prototype.slice.call(arguments, 1))
expression += `.apply(this, ${expressionArgs})`
} else {
expression += '()'
}
}
expression = `
(function () {
window.__devtron = window.__devtron || {}
window.__devtron.evaling = true
var require = window.__devtron.require || window.require
var process = window.__devtron.process || window.process
try {
return ${expression}
} finally {
window.__devtron.evaling = false
}
})()
`
return new Promise((resolve, reject) => {
window.chrome.devtools.inspectedWindow.eval(expression, (result, error) => {
if (error) {
if (error.isException && error.value) {
let stack = error.value
error = new Error(stack.split('\n')[0])
error.stack = stack
}
reject(error)
} else {
resolve(result)
}
})
})
}
static getFileSize (path) {
return Eval.execute((path) => {
try {
return require('fs').statSync(path).size
} catch (error) {
return -1
}
}, path)
}
static openExternal (urlToOpen) {
return Eval.execute((urlToOpen) => {
return require('electron').shell.openExternal(urlToOpen)
}, urlToOpen)
}
static getFileVersion (filePath) {
return Eval.execute((filePath) => {
if (/\/atom\.asar\/(browser|common|renderer)\//.test(filePath)) return process.versions.electron
const fs = require('fs')
const path = require('path')
const appVersion = require('electron').remote.app.getVersion()
let directory = path.dirname(filePath)
while (path.basename(directory) !== 'node_modules') {
try {
let metadataPath = path.join(directory, 'package.json')
let version = JSON.parse(fs.readFileSync(metadataPath)).version
if (version) return version
} catch (error) {
// Ignore and continue
}
let nextDirectory = path.dirname(directory)
if (nextDirectory === directory) break
directory = nextDirectory
}
return appVersion
}, filePath)
}
static isDebugMode () {
return Eval.execute(() => {
return process && !!process.env.DEVTRON_DEBUG_PATH
})
}
static isApiAvailable () {
return Eval.execute(() => {
return typeof process === 'object' && typeof require === 'function'
})
}
// Start a local http server in the currently running app that will
// listen to requests sent by a browser
static startServer () {
return Eval.execute(() => {
const path = require('path')
const serverPath = path.join(process.env.DEVTRON_DEBUG_PATH, 'test', 'server.js')
require(serverPath)
})
}
// Implement the window.chrome.devtools.inspectedWindow.eval API via
// window.fetch talking to a local http server running in an opened
// Electron app
static proxyToServer () {
window.chrome.devtools = {
inspectedWindow: {
eval: function (expression, callback) {
window.fetch('http://localhost:3948', {
body: JSON.stringify({expression: expression}),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: 'POST'
}).then((response) => {
return response.json()
}).then((json) => {
callback(json.result)
}).catch((error) => {
callback(null, error)
})
}
}
}
}
}
module.exports = Eval
}).call(this,require('_process'))
},{"_process":188,"electron":undefined,"fs":31,"path":187}],10:[function(require,module,exports){
(function (process){
'use strict'
const Eval = require('./eval')
exports.getEvents = () => {
return Eval.execute(() => {
const formatCode = (listener) => {
let lines = listener.split(/\r?\n/)
if (lines.length === 1) return listener
let lastLine = lines[lines.length - 1]
let lastLineMatch = /^(\s+)}/.exec(lastLine)
if (!lastLineMatch) return listener
let whitespaceRegex = new RegExp('^' + lastLineMatch[1])
return lines.map((line) => {
return line.replace(whitespaceRegex, '')
}).join('\n')
}
const getEvents = (emitter) => {
const events = {}
Object.keys(emitter._events).sort().forEach((name) => {
let listeners = emitter.listeners(name)
if (listeners.length > 0) {
events[name] = listeners.map((listener) => {
return formatCode(listener.toString())
})
}
})
return events
}
const electron = require('electron')
const remote = electron.remote
return {
'electron.remote.getCurrentWindow()': getEvents(remote.getCurrentWindow()),
'electron.remote.getCurrentWebContents()': getEvents(remote.getCurrentWebContents()),
'electron.remote.app': getEvents(remote.app),
'electron.remote.ipcMain': getEvents(remote.ipcMain),
'electron.ipcRenderer': getEvents(electron.ipcRenderer),
'electron.remote.process': getEvents(remote.process),
'global.process': getEvents(process)
}
})
}
}).call(this,require('_process'))
},{"./eval":9,"_process":188,"electron":undefined}],11:[function(require,module,exports){
'use strict'
const highlight = require('highlight.js')
const SelectableView = require('./selectable-view')
class EventListenerView extends SelectableView {
constructor (listener, parent) {
super('listener-code-row')
this.listener = listener
parent.appendChild(this.element)
this.render()
this.handleEvents(parent)
}
handleEvents (parent) {
this.listenForSelection(parent)
this.listenForSelectionKeys(parent.parentElement)
}
render () {
this.listenerValue.textContent = this.listener
highlight.highlightBlock(this.listenerValue)
}
filter (searchText) {
let matches = this.listener.toLowerCase().includes(searchText)
matches ? this.show() : this.hide()
return matches
}
}
module.exports = EventListenerView
},{"./selectable-view":26,"highlight.js":33}],12:[function(require,module,exports){
'use strict'
const ExpandableView = require('./expandable-view')
const EventListenerView = require('./event-listener-view')
class EventView extends ExpandableView {
constructor (name, listeners, parent, table) {
super('event-type-row')
this.name = name
this.count = listeners.length
this.parent = parent
table.appendChild(this.element)
this.handleEvents(table)
this.render()
this.children = listeners.map((listener) => {
return new EventListenerView(listener, table)
})
this.collapse()
}
handleEvents (table) {
this.listenForSelection(table)
this.listenForSelectionKeys(table.parentElement)
this.listenForExpanderKeys(table.parentElement)
}
render () {
this.eventName.textContent = this.name
this.listenerCount.textContent = `(${this.count})`
}
filter (searchText) {
this.collapse()
let matches = this.name.includes(searchText)
this.children.forEach((child) => {
if (child.filter(searchText)) matches = true
})
if (matches) {
this.markCollapsed()
this.show()
this.markExpanded()
} else {
this.hide()
}
return matches
}
}
module.exports = EventView
},{"./event-listener-view":11,"./expandable-view":14}],13:[function(require,module,exports){
'use strict'
const events = require('./event-helpers')
const EmitterView = require('./emitter-view')
const View = require('./view')
class EventsView extends View {
constructor () {
super('events-view')
this.children = []
this.handleEvents()
}
reload () {
this.loadEvents()
}
focus () {
this.listenersTable.focus()
}
handleEvents () {
this.loadButton.addEventListener('click', () => this.loadEvents())
this.debounceInput(this.searchBox, () => this.filterEvents())
}
filterEvents () {
const searchText = this.searchBox.value.toLowerCase()
if (searchText) {
this.children.forEach((child) => {
child.filter(searchText)
})
} else {
this.children.forEach((child) => {
child.show()
child.collapse()
})
}
}
loadEvents () {
events.getEvents().then((events) => {
this.tableDescription.classList.add('hidden')
this.listenersTable.innerHTML = ''
this.destroyChildren()
this.children = Object.keys(events).map((name) => {
return new EmitterView(name, events[name], this.listenersTable)
})
this.children[0].select()
}).catch((error) => {
console.error('Getting event listeners failed')
console.error(error.stack || error)
})
}
}
module.exports = EventsView
},{"./emitter-view":8,"./event-helpers":10,"./view":28}],14:[function(require,module,exports){
'use strict'
const SelectableView = require('./selectable-view')
class ExpandableView extends SelectableView {
constructor (viewId) {
super(viewId)
this.listenForArrowClicks()
}
toggleExpansion () {
if (this.expanded) {
this.collapse()
} else {
this.expand()
}
}
markExpanded () {
this.expanded = true
this.disclosure.classList.add('disclosure-arrow-expanded')
}
expand () {
this.markExpanded()
this.children.forEach((child) => child.show())
}
markCollapsed () {
this.expanded = false
this.disclosure.classList.remove('disclosure-arrow-expanded')
}
collapse () {
this.markCollapsed()
this.children.forEach((child) => child.hide())
}
collapseAll () {
this.collapse()
this.children.forEach((child) => child.collapse())
}
hide () {
super.hide()
this.children.forEach((child) => child.hide())
}
show () {
super.show()
if (this.expanded) this.children.forEach((child) => child.show())
}
listenForArrowClicks () {
this.disclosure.addEventListener('click', () => this.toggleExpansion())
}
listenForExpanderKeys (emitter) {
this.bindListener(emitter, 'keydown', (event) => {
if (!this.selected) return
if (event.altKey || event.metaKey || event.ctrlKey) return
switch (event.code) {
case 'ArrowLeft':
if (this.expanded) {
this.collapse()
} else if (this.parent && this.parent.expanded) {
this.deselect()
this.parent.collapse()
this.parent.select()
}
event.stopImmediatePropagation()
event.preventDefault()
break
case 'ArrowRight':
this.expand()
event.stopImmediatePropagation()
event.preventDefault()
break
}
})
}
}
module.exports = ExpandableView
},{"./selectable-view":26}],15:[function(require,module,exports){
'use strict'
const AboutView = require('./about-view')
const Eval = require('./eval')
const EventsView = require('./events-view')
const IpcView = require('./ipc-view')
const LintView = require('./lint-view')
const AccessibilityView = require('./accessibility-view')
const ModulesView = require('./modules-view')
const NodeIntegrationView = require('./node-integration-view')
const SidebarView = require('./sidebar-view')
document.addEventListener('DOMContentLoaded', () => {
Eval.isApiAvailable().then(function (apiAvailable) {
const sidebarView = new SidebarView()
if (apiAvailable) {
sidebarView.addPane(new ModulesView())
sidebarView.addPane(new EventsView())
sidebarView.addPane(new IpcView())
sidebarView.addPane(new LintView())
sidebarView.addPane(new AccessibilityView())
sidebarView.addPane(new AboutView())
listenForLinkClicks()
} else {
sidebarView.addPane(new NodeIntegrationView())
}
})
})
if (!window.chrome.devtools) {
Eval.proxyToServer()
} else {
Eval.isDebugMode().then(function (debugMode) {
if (debugMode) Eval.startServer()
})
}
const listenForLinkClicks = () => {
document.body.addEventListener('click', (event) => {
const href = event.target.href
if (href) {
Eval.openExternal(href)
event.stopImmediatePropagation()
event.preventDefault()
}
})
}
},{"./about-view":1,"./accessibility-view":4,"./eval":9,"./events-view":13,"./ipc-view":18,"./lint-view":20,"./modules-view":24,"./node-integration-view":25,"./sidebar-view":27}],16:[function(require,module,exports){
'use strict'
const highlight = require('highlight.js')
const SelectableView = require('./selectable-view')
class IpcEventView extends SelectableView {
constructor (event, table) {
super('ipc-table-row')
this.event = event
this.internalEvent = event.channel.startsWith('ELECTRON_') || event.channel.startsWith('ATOM_')
table.appendChild(this.element)
this.listenForSelection(table)
this.listenForSelectionKeys(table.parentElement)
this.render()
}
render () {
this.eventName.textContent = this.event.channel
this.eventName.title = this.event.channel
if (this.event.sent) {
this.eventIcon.classList.add('ipc-icon-sent')
this.eventIcon.title = 'Outgoing'
} else {
this.eventIcon.classList.add('ipc-icon-received')
this.eventIcon.title = 'Incoming'
}
if (!this.event.sync) {
this.syncIcon.style.display = 'none'
}
if (this.event.listenerCount > 0) {
this.eventListenerCount.textContent = this.event.listenerCount
}
this.eventData.textContent = this.event.data
highlight.highlightBlock(this.eventData)
}
filter (searchText) {
let matches = this.event.channel.toLowerCase().includes(searchText)
matches = matches || this.event.data.toLowerCase().includes(searchText)
matches ? this.show() : this.hide()
}
}
module.exports = IpcEventView
},{"./selectable-view":26,"highlight.js":33}],17:[function(require,module,exports){
'use strict'
const Eval = require('./eval')
exports.listenForEvents = () => {
return Eval.execute(() => {
// Return if events are already being listened to to prevent duplicates
// when reloading the extension
if (window.__devtron.events != null) {
window.__devtron.events = []
return
}
window.__devtron.events = []
const ipcRenderer = require('electron').ipcRenderer
const ignoredEvents = {
'ATOM_BROWSER_DEREFERENCE': true,
'ELECTRON_BROWSER_DEREFERENCE': true
}
const trackEvent = (channel, args, sent, sync) => {
if (window.__devtron.evaling) return
if (ignoredEvents.hasOwnProperty(channel)) return
let data
try {
data = JSON.stringify(args)
} catch (error) {
data = `Failed to serialize args to JSON: ${error.message || error}`
}
window.__devtron.events.push({
channel: channel,
data: data,
listenerCount: ipcRenderer.listenerCount(channel),
sent: !!sent,
sync: !!sync
})
}
const originalEmit = ipcRenderer.emit
ipcRenderer.emit = function (channel, event) {
const args = Array.prototype.slice.call(arguments, 2)
trackEvent(channel, args)
return originalEmit.apply(ipcRenderer, arguments)
}
const originalSend = ipcRenderer.send
ipcRenderer.send = function (channel) {
const args = Array.prototype.slice.call(arguments, 1)
trackEvent(channel, args, true)
return originalSend.apply(ipcRenderer, arguments)
}
const originalSendSync = ipcRenderer.sendSync
ipcRenderer.sendSync = function (channel) {
const args = Array.prototype.slice.call(arguments, 1)
trackEvent(channel, args, true, true)
const returnValue = originalSendSync.apply(ipcRenderer, arguments)
trackEvent(channel, [returnValue], false, true)
return returnValue
}
})
}
exports.getEvents = () => {
return Eval.execute(() => {
const events = window.__devtron.events
if (events) window.__devtron.events = []
return events
}).then((events) => {
if (events) return events
// Start listening for events if array is missing meaning
// the window was reloaded
return exports.listenForEvents().then(() => [])
})
}
},{"./eval":9,"electron":undefined}],18:[function(require,module,exports){
'use strict'
const Eval = require('./eval')
const ipc = require('./ipc-helpers')
const IpcEventView = require('./ipc-event-view')
const View = require('./view')
class IpcView extends View {
constructor () {
super('ipc-view')
this.children = []
this.recording = false
this.handleEvents()
}
handleEvents () {
this.debounceInput(this.searchBox, () => this.filterEvents())
this.clearButton.addEventListener('click', () => this.clear())
this.recordButton.addEventListener('click', () => this.toggleRecording())
this.docsButton.addEventListener('click', () => this.openDocs())
this.hideInternalButton.addEventListener('click', () => this.toggleHideInternal())
}
toggleHideInternal () {
if (this.hideInternal) {
this.hideInternalButton.classList.remove('active')
this.hideInternal = false
this.children.forEach((child) => {
if (child.internalEvent) child.show()
})
} else {
this.hideInternalButton.classList.add('active')
this.hideInternal = true
this.children.forEach((child) => {
if (child.internalEvent) child.hide()
})
}
}
toggleRecording () {
if (this.recording) {
this.stopRecording()
this.recordButton.classList.remove('active')
} else {
this.startRecording()
this.recordButton.classList.add('active')
}
}
startRecording () {
ipc.listenForEvents().then(() => {
this.recording = true
this.addNewEvents()
}).catch((error) => {
console.error('Listening for IPC events failed')
console.error(error.stack || error)
})
}
stopRecording () {
clearTimeout(this.timeoutId)
this.recording = false
}
openDocs () {
Eval.openExternal('http://electron.atom.io/docs/latest/api/ipc-main')
}
clear () {
this.ipcTable.innerHTML = ''
this.destroyChildren()
}
addNewEvents () {
ipc.getEvents().then((events) => {
if (!this.recording) return
events.forEach((event) => this.addEvent(event))
this.timeoutId = setTimeout(() => this.addNewEvents(), 333)
}).catch((error) => {
console.error('Getting IPC events failed')
console.error(error.stack || error)
})
}
addEvent (event) {
this.tableDescription.classList.add('hidden')
const eventView = new IpcEventView(event, this.ipcTable)
this.children.push(eventView)
this.filterIncomingEvent(eventView)
}
filterIncomingEvent (view) {
if (this.hideInternal && view.internalEvent) {
view.hide()
} else {
const searchText = this.getFilterText()
if (searchText) view.filter(searchText)
}
}
filterEvents () {
const searchText = this.getFilterText()
if (searchText) {
this.children.forEach((child) => child.filter(searchText))
} else {
this.children.forEach((child) => child.show())
}
}
getFilterText () {
return this.searchBox.value.toLowerCase()
}
}
module.exports = IpcView
},{"./eval":9,"./ipc-event-view":16,"./ipc-helpers":17,"./view":28}],19:[function(require,module,exports){
(function (process){
'use strict'
const Eval = require('./eval')
exports.isUsingAsar = () => {
return Eval.execute(() => {
const mainPath = require('electron').remote.process.mainModule.filename
return /[\\/]app\.asar[\\/]/.test(mainPath)
})
}
exports.isListeningForCrashEvents = () => {
return Eval.execute(() => {
const webContents = require('electron').remote.getCurrentWebContents()
// For versions less than 1.x.y
// Electron has an crashed listener, so look for more than 1
const crashedForwarding = /^0/.test(process.versions.electron)
const minCount = crashedForwarding ? 1 : 0
return webContents.listenerCount('crashed') > minCount
})
}
exports.isListeningForUnresponsiveEvents = () => {
return Eval.execute(() => {
const browserWindow = require('electron').remote.getCurrentWindow()
return browserWindow.listenerCount('unresponsive') > 0
})
}
exports.isListeningForUncaughtExceptionEvents = () => {
return Eval.execute(() => {
const mainProcess = require('electron').remote.process
// Electron has an uncaughtException listener, so look for more than 1
return mainProcess.listenerCount('uncaughtException') > 1
})
}
exports.getCurrentElectronVersion = () => {
return Eval.execute(() => {
return process.versions.electron
})
}
exports.getLatestElectronVersion = () => {
return Eval.execute(() => {
return window.__devtron.latestElectronVersion
})
}
exports.fetchLatestVersion = () => {
return Eval.execute(() => {
window.fetch('https://atom.io/download/atom-shell/index.json')
.then((response) => {
return response.json()
}).then((versions) => {
window.__devtron.latestElectronVersion = versions[0].version
}).catch(() => {
window.__devtron.latestElectronVersion = 'unknown'
})
})
}
}).call(this,require('_process'))
},{"./eval":9,"_process":188,"electron":undefined}],20:[function(require,module,exports){
'use strict'
const highlight = require('highlight.js')
const Lint = require('./lint-helpers')
const View = require('./view')
class LintView extends View {
constructor () {
super('lint-view')
this.handleEvents()
this.highlightBlocks()
}
reload () {
this.lint()
}
highlightBlocks () {
highlight.highlightBlock(this.crashedExample)
highlight.highlightBlock(this.unresponsiveExample)
highlight.highlightBlock(this.uncaughtExample)
}
handleEvents () {
this.lintButton.addEventListener('click', () => this.lint())
}
updateAlert (alertElement, descriptionElement, passing) {
if (passing) {
alertElement.classList.add('alert-lint-pass')
descriptionElement.classList.add('hidden')
} else {
alertElement.classList.add('alert-lint-fail')
descriptionElement.classList.remove('hidden')
}
alertElement.classList.remove('hidden')
this.tableDescription.classList.add('hidden')
}
lint () {
Lint.isUsingAsar().then((usingAsar) => {
this.updateAlert(this.usingAsar, this.asarDescription, usingAsar)
})
Lint.isListeningForCrashEvents().then((listening) => {
this.updateAlert(this.crashListener, this.crashDescription, listening)
})
Lint.isListeningForUnresponsiveEvents().then((listening) => {
this.updateAlert(this.unresponsiveListener, this.unresponsiveDescription, listening)
})
Lint.isListeningForUncaughtExceptionEvents().then((listening) => {
this.updateAlert(this.uncaughtListener, this.uncaughtDescription, listening)
})
this.checkVersion()
}
checkVersion () {
Lint.getCurrentElectronVersion().then((version) => {
this.currentVersion = version
this.updateVersion()
})
Lint.fetchLatestVersion()
this.checkLatestVersion()
}
checkLatestVersion () {
Lint.getLatestElectronVersion().then((version) => {
if (version) {
this.latestVersion = version
this.updateVersion()
} else {
setTimeout(() => this.checkLatestVersion(), 250)
}
})
}
updateVersion () {
if (!this.latestVersion || !this.currentVersion) return
const upToDate = this.latestVersion === this.currentVersion
this.updateAlert(this.outdated, this.outdatedDescription, upToDate)
this.latestLabel.textContent = this.latestVersion
this.versionLabel.textContent = this.currentVersion
}
}
module.exports = LintView
},{"./lint-helpers":19,"./view":28,"highlight.js":33}],21:[function(require,module,exports){
'use strict'
const Eval = require('./eval')
const Module = require('./module')
const loadSizes = (mainModule) => {
let totalSize = 0
return Promise.all(mainModule.toArray().map((module) => {
return Eval.getFileSize(module.path).then((size) => {
totalSize += size
return module.setSize(size)
})
})).then(() => {
mainModule.totalSize = totalSize
return mainModule
})
}
const loadVersions = (mainModule) => {
return Promise.all(mainModule.toArray().map((module) => {
return Eval.getFileVersion(module.path).then((version) => module.setVersion(version))
})).then(() => mainModule)
}
const createModules = (mainModule) => {
const resourcesPath = mainModule.resourcesPath
const appName = mainModule.appName
const processModule = (node) => {
const module = new Module(node.path, resourcesPath, appName)
node.children.forEach((childNode) => {
module.addChild(processModule(childNode))
})
return module
}
const convertedMainModule = processModule(mainModule)
convertedMainModule.count = mainModule.count
return convertedMainModule
}
const getRenderRequireGraph = () => {
return Eval.execute(() => {
let count = 0
const walkModule = (module) => {
count++
let modulePath = module.filename || module.id
if (process.platform === 'win32') {
modulePath = modulePath.replace(/\\/g, '/')
}
return {
path: modulePath,
children: module.children.map(walkModule)
}
}
const mainModule = walkModule(process.mainModule)
mainModule.resourcesPath = process.resourcesPath
mainModule.appName = require('electron').remote.app.getName()
mainModule.count = count
return mainModule
})
}
const getMainRequireGraph = () => {
return Eval.execute(() => {
let process = require('electron').remote.process
let count = 0
const walkModule = (module) => {
count++
let modulePath = module.filename || module.id
if (process.platform === 'win32') {
modulePath = modulePath.replace(/\\/g, '/')
}
return {
path: modulePath,
children: module.children.map(walkModule)
}
}
const mainModule = walkModule(process.mainModule)
mainModule.resourcesPath = process.resourcesPath
mainModule.appName = require('electron').remote.app.getName()
mainModule.count = count
return mainModule
})
}
exports.getRenderModules = () => {
return getRenderRequireGraph().then(createModules).then(loadSizes).then(loadVersions)
}
exports.getMainModules = () => {
return getMainRequireGraph().then(createModules).then(loadSizes).then(loadVersions)
}
},{"./eval":9,"./module":23,"electron":undefined}],22:[function(require,module,exports){
'use strict'
const ExpandableView = require('./expandable-view')
const Humanize = require('humanize-plus')
class ModuleView extends ExpandableView {
constructor (module, table, parent) {
super('requires-table-row')
this.parent = parent
this.module = module
this.table = table
table.appendChild(this.element)
this.render()
this.children = this.module.children.map((child) => {
return new ModuleView(child, table, this)
})
this.module.getDepth() === 1 ? this.expand() : this.collapse()
if (!this.module.hasChildren()) this.disclosure.style.display = 'none'
this.handleEvents()
}
handleEvents () {
this.listenForSelection(this.table)
this.listenForSelectionKeys(this.table.parentElement)
this.listenForExpanderKeys(this.table.parentElement)
}
getHumanizedSize () {
const size = this.module.getSize()
return Humanize.fileSize(size).replace('bytes', 'B')
}
render () {
this.moduleName.textContent = this.module.getLibrary()
this.moduleName.title = this.module.getLibrary()
this.moduleVersion.textContent = this.module.getVersion()
this.fileSize.textContent = this.getHumanizedSize()
this.fileName.textContent = this.module.getName()
this.fileName.title = this.module.path
this.moduleDirectory.textContent = this.module.getDirectory()
this.moduleDirectory.title = this.module.path
this.pathSection.style['padding-left'] = `${(this.module.getDepth()) * 15}px`
}
filter (searchText) {
this.collapse()
let matches = this.module.getId().includes(searchText)
matches = matches || this.module.getName().toLowerCase().includes(searchText)
this.children.forEach((child) => {
if (child.filter(searchText)) matches = true
})
if (matches) {
this.markCollapsed()
this.show()
this.markExpanded()
} else {
this.hide()
}
return matches
}
}
module.exports = ModuleView
},{"./expandable-view":14,"humanize-plus":186}],23:[function(require,module,exports){
'use strict'
class Module {
constructor (path, resourcesPath, appName) {
this.path = path
this.resourcesPath = resourcesPath
this.appName = appName
this.size = -1
this.version = ''
this.children = []
}
setVersion (version) {
this.version = version
return this
}
getVersion () {
return this.version
}
setSize (size) {
this.size = size
return this
}
getSize () {
return this.size
}
hasChildren () {
return this.children.length > 0
}
addChild (child) {
this.children.push(child)
child.parent = this
}
getPath () {
return this.path
}
getDepth () {
let depth = 1
let parent = this.parent
while (parent != null) {
depth++
parent = parent.parent
}
return depth
}
getName () {
if (!this.name) this.name = /\/([^\/]+)$/.exec(this.path)[1]
return this.name
}
getDirectory () {
let directoryPath = /(.+)\/[^\/]+$/.exec(this.path)[1]
if (directoryPath.indexOf(this.resourcesPath) === 0) {
directoryPath = directoryPath.substring(this.resourcesPath.length + 1)
}
return directoryPath
}
computeLibrary () {
if (/\/atom\.asar\/(browser|common|renderer)\//.test(this.path)) return 'Electron'
const libraryPattern = /\/node_modules\/([^\/]+)(?=\/)/g
let match = libraryPattern.exec(this.path)
while (match != null) {
let library = match[1]
match = libraryPattern.exec(this.path)
if (match == null) return library
}
return this.appName
}
getLibrary () {
if (!this.library) this.library = this.computeLibrary()
return this.library
}
getId () {
if (!this.id) this.id = this.getLibrary().toLowerCase()
return this.id
}
visit (callback) {
callback(this)
this.children.forEach((child) => child.visit(callback))
}
toArray () {
const modules = []
this.visit((module) => modules.push(module))
return modules
}
}
module.exports = Module
},{}],24:[function(require,module,exports){
'use strict'
const Humanize = require('humanize-plus')
const modules = require('./module-helpers')
const ModuleView = require('./module-view')
const View = require('./view')
class ModulesView extends View {
constructor () {
super('modules-view')
this.handleEvents()
}
reload () {
this.loadGraph()
}
focus () {
if (this.mainProcessTable.classList.contains('hidden')) {
this.renderRequireRows.focus()
} else {
this.mainRequireRows.focus()
}
}
handleEvents () {
this.loadButton.addEventListener('click', () => this.loadGraph())
this.debounceInput(this.searchBox, () => this.filterGraph())
this.mainProcessTab.addEventListener('click', () => {
this.mainProcessTab.classList.add('active')
this.renderProcessTab.classList.remove('active')
this.mainProcessTable.classList.remove('hidden')
this.renderProcessTable.classList.add('hidden')
this.mainRequireRows.focus()
})
this.renderProcessTab.addEventListener('click', () => {
this.mainProcessTab.classList.remove('active')
this.renderProcessTab.classList.add('active')
this.mainProcessTable.classList.add('hidden')
this.renderProcessTable.classList.remove('hidden')
this.renderRequireRows.focus()
})
}
getTabLabelSuffix (mainModule) {
const count = mainModule.count.toLocaleString()
const size = Humanize.fileSize(mainModule.totalSize)
return `- ${count} files, ${size}`
}
loadGraph () {
modules.getRenderModules().then((mainModule) => {
this.tableDescription.classList.add('hidden')
const suffix = this.getTabLabelSuffix(mainModule)
this.renderProcessTab.textContent = `Renderer Process ${suffix}`
this.renderRequireRows.innerHTML = ''
if (this.rootRenderView) this.rootRenderView.destroy()
this.rootRenderView = new ModuleView(mainModule, this.renderRequireRows)
this.rootRenderView.select()
}).catch((error) => {
console.error('Loading render modules failed')
console.error(error.stack || error)
})
modules.getMainModules().then((mainModule) => {
const suffix = this.getTabLabelSuffix(mainModule)
this.mainProcessTab.textContent = `Main Process ${suffix}`
this.mainRequireRows.innerHTML = ''
if (this.rootMainView) this.rootMainView.destroy()
this.rootMainView = new ModuleView(mainModule, this.mainRequireRows)
this.rootMainView.select()
}).catch((error) => {
console.error('Loading main modules failed')
console.error(error.stack || error)
})
}
filterGraph () {
const searchText = this.searchBox.value.toLowerCase()
if (searchText) {
this.rootRenderView.filter(searchText)
this.rootMainView.filter(searchText)
} else {
this.rootRenderView.collapseAll()
this.rootRenderView.expand()
this.rootMainView.collapseAll()
this.rootMainView.expand()
}
}
}
module.exports = ModulesView
},{"./module-helpers":21,"./module-view":22,"./view":28,"humanize-plus":186}],25:[function(require,module,exports){
'use strict'
const highlight = require('highlight.js')
const View = require('./view')
class NodeIntegrationView extends View {
constructor () {
super('node-integration-view')
this.highlightBlocks()
}
highlightBlocks () {
highlight.highlightBlock(this.browserWindowExample)
highlight.highlightBlock(this.devtronExample)
highlight.highlightBlock(this.envCheckExample)
}
}
module.exports = NodeIntegrationView
},{"./view":28,"highlight.js":33}],26:[function(require,module,exports){
'use strict'
const View = require('./view')
class SelectableView extends View {
select () {
this.selected = true
this.element.classList.add('active')
this.element.scrollIntoViewIfNeeded()
}
deselect () {
this.selected = false
this.element.classList.remove('active')
}
selectNext () {
let next = this.element.nextElementSibling
while (next && (next.view instanceof SelectableView)) {
if (next.view.isHidden()) {
next = next.nextElementSibling
continue
}
this.deselect()
next.view.select()
break
}
}
selectPrevious () {
let previous = this.element.previousElementSibling
while (previous && (previous.view instanceof SelectableView)) {
if (previous.view.isHidden()) {
previous = previous.previousElementSibling
continue
}
this.deselect()
previous.view.select()
break
}
}
listenForSelection (emitter) {
this.bindListener(emitter, 'mousedown', (event) => {
if (this.element.contains(event.target)) {
this.select()
} else {
this.deselect()
}
})
}
listenForSelectionKeys (emitter) {
this.bindListener(emitter, 'keydown', (event) => {
if (!this.selected) return
if (event.altKey || event.metaKey || event.ctrlKey) return
switch (event.code) {
case 'ArrowDown':
this.selectNext()
event.stopImmediatePropagation()
event.preventDefault()
break
case 'ArrowUp':
this.selectPrevious()
event.stopImmediatePropagation()
event.preventDefault()
break
}
})
}
}
module.exports = SelectableView
},{"./view":28}],27:[function(require,module,exports){
'use strict'
const View = require('./view')
class SidebarView extends View {
constructor () {
super('sidebar-view')
this.panes = []
this.links = [this.requireLink, this.eventsLink, this.ipcLink, this.lintLink, this.accessibilityLink, this.aboutLink]
this.panesElement = document.querySelector('#pane-group')
this.panesElement.appendChild(this.element)
this.handleEvents()
}
handleEvents () {
document.body.addEventListener('keydown', (event) => {
if (event.ctrlKey || event.metaKey) return
if (!event.altKey) return
switch (event.code) {
case 'ArrowDown':
this.selectNext()
event.stopImmediatePropagation()
event.preventDefault()
break
case 'ArrowUp':
this.selectPrevious()
event.stopImmediatePropagation()
event.preventDefault()
break
}
})
document.body.addEventListener('keydown', (event) => {
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyE') {
this.activePane.reload()
this.activePane.focus()
event.stopImmediatePropagation()
event.preventDefault()
}
})
this.element.addEventListener('mousedown', (event) => {
let paneLink = event.target.dataset.paneLink
if (paneLink) this.selectPane(paneLink)
})
}
activateLink (name) {
this.links.forEach((link) => {
if (link.dataset.paneLink === name) {
link.classList.add('active')
} else {
link.classList.remove('active')
}
})
}
addPane (view) {
if (this.panes.length === 0) this.activePane = view
this.panes.push(view)
this.panesElement.appendChild(view.element)
}
findPane (name) {
return this.panes.find((view) => view.element.dataset.pane === name)
}
selectPane (name) {
const pane = this.findPane(name)
if (!pane) return
this.panes.forEach((view) => view.hide())
pane.show()
pane.focus()
this.activePane = pane
this.activateLink(name)
}
selectPrevious () {
const selectedIndex = this.panes.indexOf(this.activePane)
const previousIndex = Math.max(selectedIndex - 1, 0)
this.selectPane(this.panes[previousIndex].element.dataset.pane)
}
selectNext () {
const selectedIndex = this.panes.indexOf(this.activePane)
const nextIndex = Math.min(selectedIndex + 1, this.panes.length - 1)
this.selectPane(this.panes[nextIndex].element.dataset.pane)
}
}
module.exports = SidebarView
},{"./view":28}],28:[function(require,module,exports){
'use strict'
class View {
static queryForEach (element, selector, callback) {
const elements = element.querySelectorAll(selector)
Array.prototype.forEach.call(elements, callback)
}
constructor (viewId) {
this.id = viewId
this.listeners = []
this.element = this.createElement()
this.element.v