UNPKG

eruda2

Version:

Console for Mobile Browsers

573 lines (493 loc) 14.1 kB
import Tool from '../DevTools/Tool' import Settings from '../Settings/Settings' import { $, unique, safeStorage, each, isStr, startWith, trim, orientation, sameOrigin, ajax, MutationObserver, toArr, concat, isNull, lowerCase, contain, filter, map, } from '../lib/util' import { isErudaEl } from '../lib/extraUtil' import evalCss from '../lib/evalCss' import chobitsu from 'chobitsu' function getResource(name) { const imageData = [] const performance = (this._performance = window.webkitPerformance || window.performance) if (performance && performance.getEntries) { const entries = this._performance.getEntries() entries.forEach((entry) => { if (entry.initiatorType === name || isImg(entry.name)) { imageData.push(entry.name) } }) } else { $(name).each(function() { const $this = $(this) const src = $this.attr('src') if ($this.attr('exclude') == 'true') { return } imageData.push(src) }) } return unique(imageData).sort() } export default class Resources extends Tool { constructor() { super() this._style = evalCss(require('./Resources.scss')) this.name = 'resources' this._localStoreData = [] this._localStoreSearchKeyword = '' this._hideErudaSetting = false this._sessionStoreData = [] this._sessionStoreSearchKeyword = '' this._cookieData = [] this._cookieSearchKeyword = '' this._scriptData = [] this._stylesheetData = [] this._iframeData = [] this._imageData = [] this._videoData = [] this._audioData = [] this._observeElement = true this._tpl = require('./Resources.hbs') } init($el, container) { super.init($el) this._container = container this.refresh() this._bindEvent() this._initObserver() this._initCfg() } refresh() { return this.refreshLocalStorage() .refreshSessionStorage() .refreshCookie() .refreshScript() .refreshStylesheet() .refreshIframe() .refreshImage() .refreshVideo() .refreshAudio() ._render() } destroy() { super.destroy() this._disableObserver() evalCss.remove(this._style) this._rmCfg() } refreshScript() { let scriptData = [] $('script').each(function() { const src = this.src if (src !== '') scriptData.push(src) }) scriptData = unique(scriptData) this._scriptData = scriptData return this } refreshStylesheet() { let stylesheetData = [] $('link').each(function() { if (this.rel !== 'stylesheet') return stylesheetData.push(this.href) }) stylesheetData = unique(stylesheetData) this._stylesheetData = stylesheetData return this } refreshIframe() { let iframeData = [] $('iframe').each(function() { const $this = $(this) const src = $this.attr('src') if (src) iframeData.push(src) }) iframeData = unique(iframeData) this._iframeData = iframeData return this } refreshLocalStorage() { this._refreshStorage('local') return this } refreshSessionStorage() { this._refreshStorage('session') return this } _refreshStorage(type) { let store = safeStorage(type, false) if (!store) return const storeData = [] // Mobile safari is not able to loop through localStorage directly. store = JSON.parse(JSON.stringify(store)) each(store, (val, key) => { // According to issue 20, not all values are guaranteed to be string. if (!isStr(val)) return if (this._hideErudaSetting) { if (startWith(key, 'eruda') || key === 'active-eruda') return } storeData.push({ key: key, val: sliceStr(val, 200), }) }) this['_' + type + 'StoreData'] = storeData } refreshCookie() { const { cookies } = chobitsu.domain('Network').getCookies() const cookieData = map(cookies, ({ name, value }) => ({ key: name, val: value, })) this._cookieData = cookieData return this } refreshImage() { this._imageData = getResource.bind(this)('img') return this } refreshVideo() { this._videoData = getResource.bind(this)('video') return this } refreshAudio() { this._audioData = getResource.bind(this)('audio') return this } show() { super.show() if (this._observeElement) this._enableObserver() return this.refresh() } hide() { this._disableObserver() return super.hide() } _bindEvent() { const self = this const $el = this._$el const container = this._container $el .on('click', '.eruda-refresh-local-storage', () => { container.notify('Refreshed') this.refreshLocalStorage()._render() }) .on('click', '.eruda-refresh-session-storage', () => { container.notify('Refreshed') this.refreshSessionStorage()._render() }) .on('click', '.eruda-refresh-cookie', () => { container.notify('Refreshed') this.refreshCookie()._render() }) .on('click', '.eruda-refresh-script', () => { container.notify('Refreshed') this.refreshScript()._render() }) .on('click', '.eruda-refresh-stylesheet', () => { container.notify('Refreshed') this.refreshStylesheet()._render() }) .on('click', '.eruda-refresh-iframe', () => { container.notify('Refreshed') this.refreshIframe()._render() }) .on('click', '.eruda-refresh-image', () => { container.notify('Refreshed') this.refreshImage()._render() }) .on('click', '.eruda-refresh-video', () => { container.notify('Refreshed') this.refreshVideo()._render() }) .on('click', '.eruda-refresh-audio', () => { container.notify('Refreshed') this.refreshAudio ()._render() }) .on('click', '.eruda-search', function() { const $this = $(this) const type = $this.data('type') let filter = prompt('Filter') if (isNull(filter)) return filter = trim(filter) switch (type) { case 'local': self._localStoreSearchKeyword = filter break case 'session': self._sessionStoreSearchKeyword = filter break case 'cookie': self._cookieSearchKeyword = filter break } self._render() }) .on('click', '.eruda-delete-storage', function() { const $this = $(this) const key = $this.data('key') const type = $this.data('type') if (type === 'local') { localStorage.removeItem(key) self.refreshLocalStorage()._render() } else { sessionStorage.removeItem(key) self.refreshSessionStorage()._render() } }) .on('click', '.eruda-delete-cookie', function() { const key = $(this).data('key') chobitsu.domain('Network').deleteCookies({ name: key }) self.refreshCookie()._render() }) .on('click', '.eruda-clear-storage', function() { const type = $(this).data('type') if (type === 'local') { each(self._localStoreData, (val) => localStorage.removeItem(val.key)) self.refreshLocalStorage()._render() } else { each(self._sessionStoreData, (val) => sessionStorage.removeItem(val.key) ) self.refreshSessionStorage()._render() } }) .on('click', '.eruda-clear-cookie', () => { chobitsu.domain('Storage').clearDataForOrigin({ storageTypes: 'cookies', }) this.refreshCookie()._render() }) .on('click', '.eruda-storage-val', function() { const $this = $(this) const key = $this.data('key') const type = $this.data('type') const val = type === 'local' ? localStorage.getItem(key) : sessionStorage.getItem(key) try { showSources('object', JSON.parse(val)) } catch (e) { showSources('raw', val) } }) .on('click', '.eruda-img-link', function() { const src = $(this).attr('src') showSources('img', src) }) .on('click', '.eruda-css-link', linkFactory('css')) .on('click', '.eruda-js-link', linkFactory('js')) .on('click', '.eruda-iframe-link', linkFactory('iframe')) orientation.on('change', () => this._render()) function showSources(type, data) { const sources = container.get('sources') if (!sources) return sources.set(type, data) container.showTool('sources') return true } function linkFactory(type) { return function(e) { if (!container.get('sources')) return e.preventDefault() const url = $(this).attr('href') if (type === 'iframe' || !sameOrigin(location.href, url)) { showSources('iframe', url) } else { ajax({ url, success: (data) => { showSources(type, data) }, dataType: 'raw', }) } } } } _rmCfg() { const cfg = this.config const settings = this._container.get('settings') if (!settings) return settings .remove(cfg, 'hideErudaSetting') .remove(cfg, 'observeElement') .remove('Resources') } _initCfg() { const cfg = (this.config = Settings.createCfg('resources', { hideErudaSetting: true, observeElement: true, })) if (cfg.get('hideErudaSetting')) this._hideErudaSetting = true if (!cfg.get('observeElement')) this._observeElement = false cfg.on('change', (key, val) => { switch (key) { case 'hideErudaSetting': this._hideErudaSetting = val return case 'observeElement': this._observeElement = val return val ? this._enableObserver() : this._disableObserver() } }) const settings = this._container.get('settings') settings .text('Resources') .switch(cfg, 'hideErudaSetting', 'Hide Eruda Setting') .switch(cfg, 'observeElement', 'Auto Refresh Elements') .separator() } _render() { const cookieData = this._cookieData const scriptData = this._scriptData const stylesheetData = this._stylesheetData const imageData = this._imageData const videoData = this._videoData const audioData = this._audioData const localStoreSearchKeyword = this._localStoreSearchKeyword const sessionStoreSearchKeyword = this._sessionStoreSearchKeyword const cookieSearchKeyword = this._cookieSearchKeyword function filterData(data, keyword) { keyword = lowerCase(keyword) if (!keyword) return data return filter(data, ({ key, val }) => { return ( contain(lowerCase(key), keyword) || contain(lowerCase(val), keyword) ) }) } this._renderHtml( this._tpl({ localStoreData: filterData( this._localStoreData, localStoreSearchKeyword ), localStoreSearchKeyword, sessionStoreData: filterData( this._sessionStoreData, sessionStoreSearchKeyword ), sessionStoreSearchKeyword, cookieData: filterData(cookieData, cookieSearchKeyword), cookieSearchKeyword, cookieState: getState('cookie', cookieData.length), scriptData, scriptState: getState('script', scriptData.length), stylesheetData, stylesheetState: getState('stylesheet', stylesheetData.length), iframeData: this._iframeData, imageData, videoData, audioData, imageState: getState('image', imageData.length), }) ) } _renderHtml(html) { if (html === this._lastHtml) return this._lastHtml = html this._$el.html(html) } _initObserver() { this._observer = new MutationObserver((mutations) => { let needToRender = false each(mutations, (mutation) => { if (this._handleMutation(mutation)) needToRender = true }) if (needToRender) this._render() }) } _handleMutation(mutation) { if (isErudaEl(mutation.target)) return const checkEl = (el) => { const tagName = getLowerCaseTagName(el) switch (tagName) { case 'script': this.refreshScript() return true case 'img': this.refreshImage() return true case 'link': this.refreshStylesheet() return true } return false } if (mutation.type === 'attributes') { if (checkEl(mutation.target)) return true } else if (mutation.type === 'childList') { if (checkEl(mutation.target)) return true let nodes = toArr(mutation.addedNodes) nodes = concat(nodes, toArr(mutation.removedNodes)) for (const node of nodes) { if (checkEl(node)) return true } } return false } _enableObserver() { this._observer.observe(document.documentElement, { attributes: true, childList: true, subtree: true, }) } _disableObserver() { this._observer.disconnect() } } function getState(type, len) { if (len === 0) return '' let warn = 0 let danger = 0 switch (type) { case 'cookie': warn = 30 danger = 60 break case 'script': warn = 5 danger = 10 break case 'stylesheet': warn = 4 danger = 8 break case 'image': warn = 50 danger = 100 break } if (len >= danger) return 'danger' if (len >= warn) return 'warn' return 'ok' } function getLowerCaseTagName(el) { if (!el.tagName) return '' return el.tagName.toLowerCase() } const sliceStr = (str, len) => str.length < len ? str : str.slice(0, len) + '...' const regImg = /\.(jpeg|jpg|gif|png)$/ const isImg = (url) => regImg.test(url)