| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; |
| import type {Time} from '//resources/mojo/mojo/public/mojom/base/time.mojom-webui.js'; |
| |
| import {BrowserProxy} from './browser_proxy.js'; |
| import {getCss} from './event_log.css.js'; |
| import {getHtml} from './event_log.html.js'; |
| |
| /** |
| * Converts a mojo time to a JS time. |
| */ |
| function convertMojoTimeToJs(mojoTime: Time): Date { |
| // The JS Date() is based off of the number of milliseconds since the |
| // UNIX epoch (1970-01-01 00::00:00 UTC), while |internalValue| of the |
| // base::Time (represented in mojom.Time) represents the number of |
| // microseconds since the Windows FILETIME epoch (1601-01-01 00:00:00 UTC). |
| // This computes the final JS time by computing the epoch delta and the |
| // conversion from microseconds to milliseconds. |
| const windowsEpoch = Date.UTC(1601, 0, 1, 0, 0, 0, 0); |
| const unixEpoch = Date.UTC(1970, 0, 1, 0, 0, 0, 0); |
| // |epochDeltaInMs| equals to base::Time::kTimeTToMicrosecondsOffset. |
| const epochDeltaInMs = unixEpoch - windowsEpoch; |
| const timeInMs = Number(mojoTime.internalValue) / 1000; |
| |
| return new Date(timeInMs - epochDeltaInMs); |
| } |
| |
| export class EventLogMessage { |
| eventTime: Date; |
| sourceLinkText: string = ''; |
| sourceLinkURL: string = ''; |
| message: string; |
| |
| constructor( |
| eventTime: Time, sourceFile: string, sourceLine: number, |
| message: string) { |
| this.eventTime = convertMojoTimeToJs(eventTime); |
| this.message = message; |
| this.setSourceLink(sourceFile, sourceLine); |
| } |
| |
| setSourceLink(sourceFile: string, sourceLine: number) { |
| if (!sourceFile.startsWith('../../')) { |
| this.sourceLinkText = `${sourceFile}(${sourceLine})`; |
| return; |
| } |
| const fileName = sourceFile.slice(sourceFile.lastIndexOf('/') + 1); |
| if (fileName.length === 0) { |
| this.sourceLinkText = `${sourceFile}(${sourceLine})`; |
| return; |
| } |
| this.sourceLinkText = `${fileName}(${sourceLine})`; |
| this.sourceLinkURL = |
| `https://source.chromium.org/chromium/chromium/src/+/main:${ |
| sourceFile.slice(6)};l=${sourceLine}`; |
| } |
| |
| /** |
| * Returns a string for dumping the message to logs. |
| */ |
| toLogDump() { |
| return `${this.eventTime} ${this.sourceLinkText} ${this.message}`; |
| } |
| } |
| |
| export class OnDeviceInternalsEventLogElement extends CrLitElement { |
| static get is() { |
| return 'on-device-internals-event-log'; |
| } |
| |
| static override get styles() { |
| return getCss(); |
| } |
| |
| override render() { |
| return getHtml.bind(this)(); |
| } |
| |
| static override get properties() { |
| return { |
| eventLogMessages_: {type: Array}, |
| }; |
| } |
| |
| protected accessor eventLogMessages_: EventLogMessage[] = []; |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| BrowserProxy.getInstance().callbackRouter.onLogMessageAdded.addListener( |
| this.onLogMessageAdded_.bind(this)); |
| } |
| |
| /** |
| * The callback to save the logs to a file. |
| */ |
| protected onEventLogsDumpClick_() { |
| const data = |
| this.eventLogMessages_.map(message => message.toLogDump()).join('\r\n'); |
| const blob = new Blob([data], {'type': 'text/json'}); |
| const url = URL.createObjectURL(blob); |
| const filename = 'optimization_guide_internals_logs_dump.json'; |
| |
| const a = document.createElement('a'); |
| a.setAttribute('href', url); |
| a.setAttribute('download', filename); |
| |
| const event = new MouseEvent('click', { |
| bubbles: true, |
| cancelable: true, |
| view: window, |
| }); |
| a.dispatchEvent(event); |
| } |
| |
| private onLogMessageAdded_( |
| eventTime: Time, sourceFile: string, sourceLine: number, |
| message: string) { |
| this.eventLogMessages_.push( |
| new EventLogMessage(eventTime, sourceFile, sourceLine, message)); |
| this.requestUpdate(); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'on-device-internals-event-log': OnDeviceInternalsEventLogElement; |
| } |
| } |
| |
| customElements.define( |
| OnDeviceInternalsEventLogElement.is, OnDeviceInternalsEventLogElement); |