import ready from 'licia/ready' import isFn from 'licia/isFn' import $ from 'licia/$' import { getFileName } from './util' export default function(eruda) { let { evalCss } = eruda.util class Timing extends eruda.Tool { constructor() { super() this.name = 'timing' this.displayName = 'タイミング' this._style = evalCss(require('./style.scss')) this._performanceTimingData = [] this._performanceTiming = {} this._showPerformanceDetail = false this._resourceTimingData = [] this._tpl = require('./template.hbs') let performance = (this._performance = window.webkitPerformance || window.performance) this._hasResourceTiming = performance && isFn(performance.getEntries) } init($el, container) { super.init($el, container) this._container = container this._bindEvent() } show() { super.show() this._render() } hide() { super.hide() } destroy() { super.destroy() evalCss.remove(this._style) } _bindEvent() { let $el = this._$el, container = this._container let self = this $el .on('click', '.eruda-performance-timing', function() { self._showPerformanceDetail = !self._showPerformanceDetail self._render() }) .on('click', '.eruda-entry', function() { let idx = $(this).data('idx'), data = self._resourceTimingData[Number(idx)] if (data.initiatorType === 'img') { showSources('img', data.url) } }) .on('click', '.eruda-refresh-resource-timing', () => { this._render() }) function showSources(type, data) { let sources = container.get('sources') if (!sources) return sources.set(type, data) container.showTool('sources') } } _getPerformanceTimingData() { let performance = this._performance if (!performance) return let timing = performance.timing if (!timing) return let data = [] /* eslint-disable no-unused-vars */ let { navigationStart, unloadEventStart, unloadEventEnd, redirectStart, redirectEnd, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd, domLoading, domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, domComplete, loadEventStart, loadEventEnd } = timing let start = navigationStart, end = loadEventEnd, ready = true, total = end - start function getData(name, startTime, endTime) { let duration = endTime - startTime if (duration < 0) ready = false return { name: name, start: ((startTime - start) / total) * 100, duration: duration, len: (duration / total) * 100 } } data.push(getData('合計', navigationStart, loadEventEnd)) data.push(getData('ネットワーク/サーバー', navigationStart, responseStart)) data.push(getData('アプリキャッシュ', fetchStart, domainLookupStart)) data.push(getData('DNS', domainLookupStart, domainLookupEnd)) data.push(getData('TCP', connectStart, connectEnd)) data.push(getData('最初のバイトまでの時間', requestStart, responseStart)) data.push(getData('レスポンス', responseStart, responseEnd)) data.push(getData('アンロード', unloadEventStart, unloadEventEnd)) data.push(getData('DOM処理', domLoading, domComplete)) data.push(getData('DOM構築', domLoading, domInteractive)) if (!ready) return this._performanceTimingData = data let performanceTiming = {} ;[ 'navigationStart', 'unloadEventStart', 'unloadEventEnd', 'redirectStart', 'redirectEnd', 'fetchStart', 'domainLookupStart', 'domainLookupEnd', 'connectStart', 'connectEnd', 'secureConnectionStart', 'requestStart', 'responseStart', 'responseEnd', 'domLoading', 'domInteractive', 'domContentLoadedEventStart', 'domContentLoadedEventEnd', 'domComplete', 'loadEventStart', 'loadEventEnd' ].forEach(val => { performanceTiming[val] = timing[val] === 0 ? 0 : timing[val] - start }) this._performanceTiming = performanceTiming } _getResourceTimingData() { if (!this._hasResourceTiming) return let entries = this._performance.getEntries(), data = [] let totalTime = 0 entries.forEach(entry => { if (entry.entryType !== 'resource') return if (entry.responseEnd > totalTime) totalTime = entry.responseEnd }) entries.forEach(entry => { if (entry.entryType !== 'resource') return let timeline = { left: (entry.startTime / totalTime) * 100, connection: ((entry.requestStart - entry.startTime) / totalTime) * 100, ttfb: ((entry.responseStart - entry.requestStart) / totalTime) * 100, response: ((entry.responseEnd - entry.responseStart) / totalTime) * 100 } data.push({ name: getFileName(entry.name), displayTime: Math.round(entry.duration) + 'ms', url: entry.name, timeline, initiatorType: entry.initiatorType }) }) this._resourceTimingData = data } _render() { if (!this.active) return this._getResourceTimingData() let renderData = { entries: this._resourceTimingData } if (this._performanceTimingData.length === 0) { ready(() => { this._getPerformanceTimingData() this._render() }) } else { this._getPerformanceTimingData() } renderData.data = this._performanceTimingData renderData.timing = this._performanceTiming renderData.showPerformanceDetail = this._showPerformanceDetail if (!renderData.timing && !renderData.entries) { renderData.notSupported = true } this._renderHtml(this._tpl(renderData)) } _renderHtml(html) { if (html === this._lastHtml) return this._lastHtml = html this._$el.html(html) } } return new Timing() }