Files
eruda/src/Console/Console.js
2019-09-27 16:18:03 +08:00

327 lines
8.4 KiB
JavaScript

import Logger from './Logger'
import Tool from '../DevTools/Tool'
import { noop, evalCss, $, Emitter } from '../lib/util'
import emitter from '../lib/emitter'
import Settings from '../Settings/Settings'
import stringify from './stringify'
export default class Console extends Tool {
constructor() {
super()
Emitter.mixin(this)
this.name = 'console'
this._scale = 1
this._registerListener()
}
init($el, container) {
super.init($el)
this._container = container
this._appendTpl()
this._initLogger()
this._exposeLogger()
this._rejectionHandler = e => this._logger.error(e.reason)
this._initCfg()
this._bindEvent()
}
overrideConsole() {
const origConsole = (this._origConsole = {})
const winConsole = window.console
CONSOLE_METHOD.forEach(name => {
let origin = (origConsole[name] = noop)
if (winConsole[name]) {
origin = origConsole[name] = winConsole[name].bind(winConsole)
}
winConsole[name] = (...args) => {
this[name](...args)
origin(...args)
}
})
return this
}
restoreConsole() {
if (!this._origConsole) return this
CONSOLE_METHOD.forEach(
name => (window.console[name] = this._origConsole[name])
)
delete this._origConsole
return this
}
catchGlobalErr() {
this._origOnerror = window.onerror
window.onerror = (errMsg, url, lineNum, column, errObj) => {
this._logger.error(errObj ? errObj : errMsg)
}
window.addEventListener('unhandledrejection', this._rejectionHandler)
return this
}
ignoreGlobalErr() {
if (this._origOnerror) {
window.onerror = this._origOnerror
delete this._origOnerror
}
window.removeEventListener('unhandledrejection', this._rejectionHandler)
return this
}
destroy() {
this._logger.destroy()
super.destroy()
evalCss.remove(this._style)
this.ignoreGlobalErr()
this.restoreConsole()
this._unregisterListener()
this._rmCfg()
}
_registerListener() {
this._scaleListener = scale => (this._scale = scale)
emitter.on(emitter.SCALE, this._scaleListener)
}
_unregisterListener() {
emitter.off(emitter.SCALE, this._scaleListener)
}
_appendTpl() {
const $el = this._$el
this._style = evalCss(require('./Console.scss'))
$el.append(require('./Console.hbs')())
const _$inputContainer = $el.find('.eruda-js-input'),
_$input = _$inputContainer.find('textarea'),
_$inputBtns = _$inputContainer.find('.eruda-buttons')
Object.assign(this, {
_$control: $el.find('.eruda-control'),
_$logs: $el.find('.eruda-logs'),
_$inputContainer,
_$input,
_$inputBtns
})
}
_initLogger() {
const $filter = this._$control.find('.eruda-filter')
const logger = (this._logger = new Logger(this._$logs))
logger.on('filter', filter =>
$filter.each(function() {
const $this = $(this),
isMatch = $this.data('filter') === filter
$this[isMatch ? 'addClass' : 'rmClass']('eruda-active')
})
)
}
_exposeLogger() {
const logger = this._logger,
methods = ['filter', 'html'].concat(CONSOLE_METHOD)
methods.forEach(
name =>
(this[name] = (...args) => {
logger[name](...args)
this.emit(name, ...args)
return this
})
)
}
_bindEvent() {
const container = this._container
const $input = this._$input,
$inputBtns = this._$inputBtns,
$control = this._$control,
logger = this._logger,
config = this.config
$control
.on('click', '.eruda-clear-console', () => logger.silentClear())
.on('click', '.eruda-filter', function() {
logger.filter($(this).data('filter'))
})
.on('click', '.eruda-help', () => logger.help())
$inputBtns
.on('click', '.eruda-cancel', () => this._hideInput())
.on('click', '.eruda-execute', () => {
const jsInput = $input.val().trim()
if (jsInput === '') return
logger.input(jsInput)
$input
.val('')
.get(0)
.blur()
this._hideInput()
})
$input.on('focusin', () => this._showInput())
logger
.on('viewJson', data => {
const sources = container.get('sources')
if (!sources) return
sources.set('json', data)
container.showTool('sources')
})
.on('insert', log => {
const autoShow = log.type === 'error' && config.get('displayIfErr')
if (autoShow) container.showTool('console').show()
})
}
_hideInput() {
this._$inputContainer.css({
paddingTop: 0,
height: 40 * this._scale
})
this._$inputBtns.hide()
}
_showInput() {
this._$inputContainer.css({
paddingTop: 40 * this._scale,
height: '100%'
})
this._$inputBtns.show()
}
_rmCfg() {
const cfg = this.config
const settings = this._container.get('settings')
if (!settings) return
settings
.remove(cfg, 'catchGlobalErr')
.remove(cfg, 'overrideConsole')
.remove(cfg, 'displayExtraInfo')
.remove(cfg, 'displayUnenumerable')
.remove(cfg, 'displayGetterVal')
.remove(cfg, 'lazyEvaluation')
.remove(cfg, 'viewLogInSources')
.remove(cfg, 'displayIfErr')
.remove(cfg, 'useWorker')
.remove(cfg, 'maxLogNum')
.remove('Console')
}
_initCfg() {
const container = this._container
const sources = container.get('sources')
const logger = this._logger
const cfg = (this.config = Settings.createCfg('console', {
catchGlobalErr: true,
overrideConsole: true,
displayExtraInfo: false,
displayUnenumerable: true,
displayGetterVal: false,
lazyEvaluation: true,
viewLogInSources: false,
displayIfErr: false,
useWorker: false,
maxLogNum: 'infinite'
}))
const isWorkerSupported = !!window.Worker
let maxLogNum = cfg.get('maxLogNum')
maxLogNum = maxLogNum === 'infinite' ? maxLogNum : +maxLogNum
if (cfg.get('catchGlobalErr')) this.catchGlobalErr()
if (cfg.get('overrideConsole')) this.overrideConsole()
if (cfg.get('useWorker') && isWorkerSupported) stringify.useWorker = true
logger.displayHeader(cfg.get('displayExtraInfo'))
logger.displayUnenumerable(cfg.get('displayUnenumerable'))
logger.displayGetterVal(cfg.get('displayGetterVal'))
logger.lazyEvaluation(cfg.get('lazyEvaluation'))
if (sources) logger.viewLogInSources(cfg.get('viewLogInSources'))
logger.maxNum(maxLogNum)
cfg.on('change', (key, val) => {
switch (key) {
case 'catchGlobalErr':
return val ? this.catchGlobalErr() : this.ignoreGlobalErr()
case 'overrideConsole':
return val ? this.overrideConsole() : this.restoreConsole()
case 'maxLogNum':
return logger.maxNum(val === 'infinite' ? val : +val)
case 'displayExtraInfo':
return logger.displayHeader(val)
case 'displayUnenumerable':
return logger.displayUnenumerable(val)
case 'displayGetterVal':
return logger.displayGetterVal(val)
case 'lazyEvaluation':
return logger.lazyEvaluation(val)
case 'viewLogInSources':
return logger.viewLogInSources(val)
case 'useWorker':
stringify.useWorker = val
return
}
})
const settings = container.get('settings')
settings
.text('Console')
.switch(cfg, 'catchGlobalErr', 'Catch Global Errors')
.switch(cfg, 'overrideConsole', 'Override Console')
.switch(cfg, 'displayIfErr', 'Auto Display If Error Occurs')
.switch(cfg, 'displayExtraInfo', 'Display Extra Information')
.switch(cfg, 'displayUnenumerable', 'Display Unenumerable Properties')
.switch(cfg, 'displayGetterVal', 'Access Getter Value')
.switch(cfg, 'lazyEvaluation', 'Lazy Evaluation')
if (isWorkerSupported) settings.switch(cfg, 'useWorker', 'Use Web Worker')
if (sources) {
settings.switch(cfg, 'viewLogInSources', 'View Log In Sources Panel')
}
settings
.select(cfg, 'maxLogNum', 'Max Log Number', [
'infinite',
'250',
'125',
'100',
'50',
'10'
])
.separator()
}
}
const CONSOLE_METHOD = [
'log',
'error',
'info',
'warn',
'dir',
'time',
'timeEnd',
'clear',
'table',
'assert',
'count',
'countReset',
'debug',
'group',
'groupCollapsed',
'groupEnd'
]