| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview ChromeVox log page. |
| */ |
| import {BackgroundBridge} from '../common/background_bridge.js'; |
| import {LogType, SerializableLog} from '../common/log_types.js'; |
| |
| /** Class to manage the log page. */ |
| export class LogPage { |
| constructor() { |
| this.initPage_(); |
| } |
| |
| static async init() { |
| if (LogPage.instance) { |
| throw new Error('LogPage can only be initiated once.'); |
| } |
| LogPage.instance = new LogPage(); |
| await LogPage.instance.update(); |
| } |
| |
| /** |
| * @param {!SerializableLog} log |
| * @private |
| */ |
| addLogToPage_(log) { |
| const div = document.getElementById(IdName.LIST); |
| const p = document.createElement(ElementName.PARAGRAPH); |
| |
| const typeName = document.createElement(ElementName.SPAN); |
| typeName.textContent = log.logType; |
| typeName.className = ClassName.TYPE; |
| p.appendChild(typeName); |
| |
| const timeStamp = document.createElement(ElementName.SPAN); |
| timeStamp.textContent = this.formatTimeStamp_(log.date); |
| timeStamp.className = ClassName.TIME; |
| p.appendChild(timeStamp); |
| |
| /** Add hide tree button when logType is tree. */ |
| if (log.logType === LogType.TREE) { |
| const toggle = document.createElement(ElementName.LABEL); |
| const toggleCheckbox = document.createElement(ElementName.INPUT); |
| toggleCheckbox.type = InputType.CHECKBOX; |
| toggleCheckbox.checked = true; |
| toggleCheckbox.onclick = event => textWrapper.hidden = |
| !event.target.checked; |
| |
| const toggleText = document.createElement(ElementName.SPAN); |
| toggleText.textContent = 'show tree'; |
| toggle.appendChild(toggleCheckbox); |
| toggle.appendChild(toggleText); |
| p.appendChild(toggle); |
| } |
| |
| /** textWrapper should be in block scope, not function scope. */ |
| const textWrapper = document.createElement(ElementName.PRE); |
| textWrapper.textContent = log.value; |
| textWrapper.className = ClassName.TEXT; |
| p.appendChild(textWrapper); |
| |
| div.appendChild(p); |
| } |
| |
| /** |
| * @param {!LogType} type |
| * @return {string} |
| * @private |
| */ |
| checkboxId_(type) { |
| return type + 'Filter'; |
| } |
| |
| /** |
| * @param {!LogType} type |
| * @param {boolean} checked |
| * @private |
| */ |
| createFilterCheckbox_(type, checked) { |
| const label = document.createElement(ElementName.LABEL); |
| const input = document.createElement(ElementName.INPUT); |
| input.id = this.checkboxId_(type); |
| input.type = InputType.CHECKBOX; |
| input.classList.add(ClassName.FILTER); |
| input.checked = checked; |
| input.addEventListener(EventType.CLICK, () => this.updateUrlParams_()); |
| label.appendChild(input); |
| |
| const span = document.createElement(ElementName.SPAN); |
| span.textContent = type; |
| label.appendChild(span); |
| |
| document.getElementById(IdName.FILTER).appendChild(label); |
| } |
| |
| /** @private */ |
| getDownloadFileName_() { |
| const date = new Date(); |
| return [ |
| 'chromevox_logpage', |
| date.getMonth() + 1, |
| date.getDate(), |
| date.getHours(), |
| date.getMinutes(), |
| date.getSeconds(), |
| ].join('_') + |
| '.txt'; |
| } |
| |
| /** @private */ |
| initPage_() { |
| const params = new URLSearchParams(location.search); |
| for (const type of Object.values(LogType)) { |
| const enabled = |
| (params.get(type) === String(true) || params.get(type) === null); |
| this.createFilterCheckbox_(type, enabled); |
| } |
| |
| const clearLogButton = document.getElementById(IdName.CLEAR); |
| clearLogButton.onclick = () => this.onClear_(); |
| |
| const saveLogButton = document.getElementById(IdName.SAVE); |
| saveLogButton.onclick = event => this.onSave_(event); |
| } |
| |
| /** |
| * @param {!LogType} type |
| * @private |
| */ |
| isEnabled_(type) { |
| return document.getElementById(this.checkboxId_(type)).checked; |
| } |
| |
| /** |
| * @param {Element} log |
| * @private |
| */ |
| logToString_(log) { |
| const logText = []; |
| logText.push(log.querySelector(`.${ClassName.TYPE}`).textContent); |
| logText.push(log.querySelector(`.${ClassName.TIME}`).textContent); |
| logText.push(log.querySelector(`.${ClassName.TEXT}`).textContent); |
| return logText.join(' '); |
| } |
| |
| /** @private */ |
| async onClear_() { |
| await BackgroundBridge.LogStore.clearLog(); |
| location.reload(); |
| } |
| |
| /** |
| * When saveLog button is clicked this function runs. |
| * Save the current log appeared in the page as a plain text. |
| * @param {Event} event |
| * @private |
| */ |
| onSave_(event) { |
| let outputText = ''; |
| const logs = |
| document.querySelectorAll(`#${IdName.LIST} ${ElementName.PARAGRAPH}`); |
| for (const log of logs) { |
| outputText += this.logToString_(log) + '\n'; |
| } |
| |
| const a = document.createElement(ElementName.ANCHOR); |
| a.download = this.getDownloadFileName_(); |
| a.href = 'data:text/plain; charset=utf-8,' + encodeURI(outputText); |
| a.click(); |
| } |
| |
| /** Update the logs. */ |
| async update() { |
| const logs = await BackgroundBridge.LogStore.getLogs(); |
| if (!logs) { |
| return; |
| } |
| |
| for (const log of logs) { |
| if (this.isEnabled_(log.logType)) { |
| this.addLogToPage_(log); |
| } |
| } |
| } |
| |
| /** |
| * Update the URL parameter based on the checkboxes. |
| * @private |
| */ |
| updateUrlParams_() { |
| const urlParams = []; |
| for (const type of Object.values(LogType)) { |
| urlParams.push(type + 'Filter=' + LogPage.instance.isEnabled_(type)); |
| } |
| location.search = '?' + urlParams.join('&'); |
| } |
| |
| /** |
| * Format time stamp. |
| * In this log, events are dispatched many times in a short time, so |
| * milliseconds order time stamp is required. |
| * @param {!string} dateStr |
| * @return {!string} |
| * @private |
| */ |
| formatTimeStamp_(dateStr) { |
| const date = new Date(dateStr); |
| let time = date.getTime(); |
| time -= date.getTimezoneOffset() * 1000 * 60; |
| let timeStr = |
| ('00' + Math.floor(time / 1000 / 60 / 60) % 24).slice(-2) + ':'; |
| timeStr += ('00' + Math.floor(time / 1000 / 60) % 60).slice(-2) + ':'; |
| timeStr += ('00' + Math.floor(time / 1000) % 60).slice(-2) + '.'; |
| timeStr += ('000' + time % 1000).slice(-3); |
| return timeStr; |
| } |
| } |
| |
| /** @type {LogPage} */ |
| LogPage.instance; |
| |
| // Local to module. |
| |
| /** @enum {string} */ |
| const ClassName = { |
| FILTER: 'log-filter', |
| TEXT: 'log-text', |
| TIME: 'log-time-tag', |
| TYPE: 'log-type-tag', |
| }; |
| |
| /** @enum {string} */ |
| const ElementName = { |
| ANCHOR: 'a', |
| INPUT: 'input', |
| LABEL: 'label', |
| PARAGRAPH: 'p', |
| PRE: 'pre', |
| SPAN: 'span', |
| }; |
| |
| /** @enum {string} */ |
| const EventType = { |
| CLICK: 'click', |
| }; |
| |
| /** @enum {string} */ |
| const IdName = { |
| CLEAR: 'clearLog', |
| FILTER: 'logFilters', |
| LIST: 'logList', |
| SAVE: 'saveLog', |
| }; |
| |
| /** @enum {string} */ |
| const InputType = { |
| CHECKBOX: 'checkbox', |
| }; |