UNPKG

chrome-devtools-frontend

Version:
248 lines (217 loc) • 10.4 kB
// Copyright (c) 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as i18n from '../i18n/i18n.js'; import * as SDK from '../sdk/sdk.js'; import * as UI from '../ui/ui.js'; export const UIStrings = { /** *@description Text in Indexed DBViews of the Application panel */ version: 'Version', /** *@description Table heading for Service Workers update information. Update is a noun. */ updateActivity: 'Update Activity', /** *@description Title for the timeline tab. */ timeline: 'Timeline', /** *@description Text in Service Workers Update Life Cycle *@example {2} PH1 */ startTimeS: 'Start time: {PH1}', /** *@description Text for end time of an event *@example {2} PH1 */ endTimeS: 'End time: {PH1}', }; const str_ = i18n.i18n.registerUIStrings('resources/ServiceWorkerUpdateCycleHelper.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class ServiceWorkerUpdateCycleHelper { static calculateServiceWorkerUpdateRanges(registration: SDK.ServiceWorkerManager.ServiceWorkerRegistration): Array<ServiceWorkerUpdateRange> { function addRange(ranges: Array<ServiceWorkerUpdateRange>, range: ServiceWorkerUpdateRange): void { if (range.start < Number.MAX_VALUE && range.start <= range.end) { ranges.push(range); } } /** * Add ranges representing Install, Wait or Activate of a sw version represented by id. */ function addNormalizedRanges( ranges: Array<ServiceWorkerUpdateRange>, id: string, startInstallTime: number, endInstallTime: number, startActivateTime: number, endActivateTime: number, status: Protocol.ServiceWorker.ServiceWorkerVersionStatus): void { addRange(ranges, {id, phase: ServiceWorkerUpdateNames.Install, start: startInstallTime, end: endInstallTime}); if (status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Activating || status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Activated || status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Redundant) { addRange(ranges, { id, phase: ServiceWorkerUpdateNames.Wait, start: endInstallTime, end: startActivateTime, }); addRange( ranges, {id, phase: ServiceWorkerUpdateNames.Activate, start: startActivateTime, end: endActivateTime}); } } function rangesForVersion(version: SDK.ServiceWorkerManager.ServiceWorkerVersion): Array<ServiceWorkerUpdateRange> { let state: SDK.ServiceWorkerManager.ServiceWorkerVersionState|null = version.currentState; let endActivateTime: number = 0; let beginActivateTime: number = 0; let endInstallTime: number = 0; let beginInstallTime: number = 0; const currentStatus = state.status; if (currentStatus === Protocol.ServiceWorker.ServiceWorkerVersionStatus.New) { return []; } while (state) { // find the earliest timestamp of different stage on record. if (state.status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Activated) { endActivateTime = state.last_updated_timestamp; } else if (state.status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Activating) { if (endActivateTime === 0) { endActivateTime = state.last_updated_timestamp; } beginActivateTime = state.last_updated_timestamp; } else if (state.status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Installed) { endInstallTime = state.last_updated_timestamp; } else if (state.status === Protocol.ServiceWorker.ServiceWorkerVersionStatus.Installing) { if (endInstallTime === 0) { endInstallTime = state.last_updated_timestamp; } beginInstallTime = state.last_updated_timestamp; } state = state.previousState; } /** @type {Array <ServiceWorkerUpdateRange>} */ const ranges: Array<ServiceWorkerUpdateRange> = []; addNormalizedRanges( ranges, version.id, beginInstallTime, endInstallTime, beginActivateTime, endActivateTime, currentStatus); return ranges; } const versions = registration.versionsByMode(); const modes = [ SDK.ServiceWorkerManager.ServiceWorkerVersion.Modes.Active, SDK.ServiceWorkerManager.ServiceWorkerVersion.Modes.Waiting, SDK.ServiceWorkerManager.ServiceWorkerVersion.Modes.Installing, SDK.ServiceWorkerManager.ServiceWorkerVersion.Modes.Redundant, ]; for (const mode of modes) { const version = versions.get(mode); if (version) { const ranges = rangesForVersion(version); return ranges; } } return []; } static createTimingTable(registration: SDK.ServiceWorkerManager.ServiceWorkerRegistration): Element { const tableElement = document.createElement('table'); tableElement.classList.add('service-worker-update-timing-table'); UI.Utils.appendStyle(tableElement, 'resources/serviceWorkerUpdateCycleView.css'); const timeRanges = this.calculateServiceWorkerUpdateRanges(registration); this.updateTimingTable(tableElement, timeRanges); return tableElement; } private static createTimingTableHead(tableElement: Element): void { const serverHeader = tableElement.createChild('tr', 'service-worker-update-timing-table-header'); UI.UIUtils.createTextChild(serverHeader.createChild('td'), i18nString(UIStrings.version)); UI.UIUtils.createTextChild(serverHeader.createChild('td'), i18nString(UIStrings.updateActivity)); UI.UIUtils.createTextChild(serverHeader.createChild('td'), i18nString(UIStrings.timeline)); } private static removeRows(tableElement: Element): void { const rows = tableElement.getElementsByTagName('tr'); while (rows[0]) { if (rows[0].parentNode) { rows[0].parentNode.removeChild(rows[0]); } } } private static updateTimingTable(tableElement: Element, timeRanges: Array<ServiceWorkerUpdateRange>): void { this.removeRows(tableElement); this.createTimingTableHead(tableElement); /** @type {!Array<ServiceWorkerUpdateRange>} */ const timeRangeArray = timeRanges; if (timeRangeArray.length === 0) { return; } const startTimes = timeRangeArray.map(r => r.start); const endTimes = timeRangeArray.map(r => r.end); const startTime = startTimes.reduce((a, b) => Math.min(a, b)); const endTime = endTimes.reduce((a, b) => Math.max(a, b)); const scale = 100 / (endTime - startTime); for (const range of timeRangeArray) { const phaseName = range.phase; const left = (scale * (range.start - startTime)); const right = (scale * (endTime - range.end)); const tr = tableElement.createChild('tr'); const timingBarVersionElement = tr.createChild('td'); UI.UIUtils.createTextChild(timingBarVersionElement, '#' + range.id); timingBarVersionElement.classList.add('service-worker-update-timing-bar-clickable'); timingBarVersionElement.setAttribute('tabindex', '0'); timingBarVersionElement.setAttribute('role', 'switch'); UI.ARIAUtils.setChecked(timingBarVersionElement, false); const timingBarTitleElement = tr.createChild('td'); UI.UIUtils.createTextChild(timingBarTitleElement, phaseName); this.constructUpdateDetails(tableElement, tr, range); const barContainer = tr.createChild('td').createChild('div', 'service-worker-update-timing-row'); const bar = <HTMLElement>( barContainer.createChild('span', 'service-worker-update-timing-bar ' + phaseName.toLowerCase())); bar.style.left = left + '%'; bar.style.right = right + '%'; bar.textContent = '\u200B'; // Important for 0-time items to have 0 width. } } /** * Detailed information about an update phase. Currently starting and ending time. */ private static constructUpdateDetails(tableElement: Element, tr: Element, range: ServiceWorkerUpdateRange): void { const detailsElement = tableElement.createChild('tr', 'service-worker-update-timing-bar-details'); detailsElement.classList.add('service-worker-update-timing-bar-details-collapsed'); self.onInvokeElement(tr, event => this.onToggleUpdateDetails(detailsElement, event)); const detailsView = new UI.TreeOutline.TreeOutlineInShadow(); detailsElement.appendChild(detailsView.element); const startTimeItem = document.createElementWithClass('div', 'service-worker-update-details-treeitem'); const startTime = (new Date(range.start)).toISOString(); startTimeItem.textContent = i18nString(UIStrings.startTimeS, {PH1: startTime}); const startTimeTreeElement = new UI.TreeOutline.TreeElement(startTimeItem); detailsView.appendChild(startTimeTreeElement); const endTimeItem = document.createElementWithClass('div', 'service-worker-update-details-treeitem'); const endTime = (new Date(range.end)).toISOString(); endTimeItem.textContent = i18nString(UIStrings.endTimeS, {PH1: endTime}); const endTimeTreeElement = new UI.TreeOutline.TreeElement(endTimeItem); detailsView.appendChild(endTimeTreeElement); } private static onToggleUpdateDetails(detailsRow: Element, event: Event): void { if (!event.target) { return; } const target: Element = <Element>(event.target); if (target.classList.contains('service-worker-update-timing-bar-clickable')) { detailsRow.classList.toggle('service-worker-update-timing-bar-details-collapsed'); detailsRow.classList.toggle('service-worker-update-timing-bar-details-expanded'); const expanded = target.getAttribute('aria-checked') === 'true'; UI.ARIAUtils.setChecked(target, !expanded); } } static refresh(tableElement: Element, registration: SDK.ServiceWorkerManager.ServiceWorkerRegistration): void { const timeRanges = this.calculateServiceWorkerUpdateRanges(registration); this.updateTimingTable(tableElement, timeRanges); } } export const enum ServiceWorkerUpdateNames { Install = 'Install', Wait = 'Wait', Activate = 'Activate', } export interface ServiceWorkerUpdateRange { id: string; phase: ServiceWorkerUpdateNames; start: number; end: number; }