| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'chrome://resources/js/action_link.js'; |
| |
| import {assert, assertNotReached} from 'chrome://resources/js/assert.js'; |
| import {addWebUiListener} from 'chrome://resources/js/cr.js'; |
| import {sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.js'; |
| import {$, getRequiredElement} from 'chrome://resources/js/util.js'; |
| |
| // Note: keep these values in sync with the values in |
| // ui/accessibility/ax_mode.h |
| enum AxMode { |
| NATIVE_APIS = 1 << 0, |
| WEB_CONTENTS = 1 << 1, |
| INLINE_TEXT_BOXES = 1 << 2, |
| EXTENDED_PROPERTIES = 1 << 3, |
| HTML = 1 << 4, |
| HTML_METADATA = 1 << 5, |
| LABEL_IMAGES = 1 << 6, |
| PDF_PRINTING = 1 << 7, |
| PDF_OCR = 1 << 8, |
| ANNOTATE_MAIN_NODE = 1 << 9, |
| } |
| |
| interface Data { |
| type: 'browser'|'page'|'widget'; |
| } |
| |
| type BrowserData = Data&{ |
| name: string, |
| sessionId: number, |
| }; |
| |
| type PageData = Data&{ |
| a11yMode: AxMode, |
| faviconUrl: string, |
| name: string, |
| pid: number, |
| processId: number, |
| routingId: number, |
| url?: string, |
| |
| // Used for GlobalStateName. |
| // Note: Does 'metadata' actually exist? Does not appear anywhere in |
| // chrome/browser/accessibility/accessibility_ui.cc. |
| metadata: boolean, |
| native: boolean, |
| pdfPrinting: boolean, |
| extendedProperties: boolean, |
| web: boolean, |
| |
| tree?: string, |
| error?: string, |
| eventLogs?: string, |
| }; |
| |
| type WidgetData = Data&{ |
| name: string, |
| widgetId: number, |
| }; |
| |
| interface InitData { |
| browsers: BrowserData[]; |
| pages: PageData[]; |
| viewsAccessibility: boolean; |
| widgets: WidgetData[]; |
| |
| supportedApiTypes: string[]; |
| apiType: string; |
| locked: boolean; |
| |
| html: boolean; |
| native: boolean; |
| pdfPrinting: boolean; |
| extendedProperties: boolean; |
| text: boolean; |
| web: boolean; |
| } |
| |
| type RequestType = 'showOrRefreshTree'; |
| |
| type GlobalStateName = |
| 'native'|'web'|'metadata'|'pdfPrinting'|'extendedProperties'; |
| |
| class BrowserProxy { |
| toggleAccessibility( |
| processId: number, routingId: number, modeId: AxMode, |
| shouldRequestTree: boolean) { |
| chrome.send('toggleAccessibility', [{ |
| processId, |
| routingId, |
| modeId, |
| shouldRequestTree, |
| }]); |
| } |
| |
| requestNativeUiTree( |
| sessionId: number, requestType: RequestType, allow: string, |
| allowEmpty: string, deny: string) { |
| chrome.send('requestNativeUITree', [{ |
| sessionId, |
| requestType, |
| filters: {allow, allowEmpty, deny}, |
| }]); |
| } |
| |
| requestWebContentsTree( |
| processId: number, routingId: number, requestType: RequestType, |
| allow: string, allowEmpty: string, deny: string) { |
| chrome.send('requestWebContentsTree', [ |
| {processId, routingId, requestType, filters: {allow, allowEmpty, deny}}, |
| ]); |
| } |
| |
| requestWidgetsTree( |
| widgetId: number, requestType: RequestType, allow: string, |
| allowEmpty: string, deny: string) { |
| chrome.send( |
| 'requestWidgetsTree', |
| [{widgetId, requestType, filters: {allow, allowEmpty, deny}}]); |
| } |
| |
| requestAccessibilityEvents( |
| processId: number, routingId: number, start: boolean) { |
| chrome.send('requestAccessibilityEvents', [{processId, routingId, start}]); |
| } |
| |
| setGlobalFlag(flagName: string, enabled: boolean) { |
| chrome.send('setGlobalFlag', [{flagName, enabled}]); |
| } |
| |
| setGlobalString(stringName: string, value: string) { |
| chrome.send('setGlobalString', [{stringName, value}]); |
| } |
| } |
| |
| const browserProxy = new BrowserProxy(); |
| |
| function requestData(): InitData { |
| const xhr = new XMLHttpRequest(); |
| xhr.open('GET', 'targets-data.json', false); |
| xhr.send(null); |
| assert(xhr.status === 200); |
| return JSON.parse(xhr.responseText); |
| } |
| |
| function getIdFromData(data: PageData|BrowserData|WidgetData): string { |
| if (data.type === 'page') { |
| const pageData = data as PageData; |
| return 'page_' + pageData.processId + '_' + pageData.routingId; |
| } else if (data.type === 'browser') { |
| return 'browser_' + (data as BrowserData).sessionId; |
| } else if (data.type === 'widget') { |
| return 'widget_' + (data as WidgetData).widgetId; |
| } else { |
| console.error('Unknown data type.', data); |
| return ''; |
| } |
| } |
| |
| function toggleAccessibility( |
| data: PageData, mode: AxMode, globalStateName: GlobalStateName) { |
| if (!(globalStateName in data)) { |
| return; |
| } |
| |
| const id = getIdFromData(data); |
| const tree = $(id + '-tree'); |
| // If the tree is visible, request a new tree with the updated mode. |
| const shouldRequestTree = !!tree && tree.style.display !== 'none'; |
| browserProxy.toggleAccessibility( |
| data.processId, data.routingId, mode, shouldRequestTree); |
| } |
| |
| function requestTree(data: BrowserData|PageData|WidgetData, element: Element) { |
| const allow = getRequiredElement<HTMLInputElement>('filter-allow').value; |
| const allowEmpty = |
| getRequiredElement<HTMLInputElement>('filter-allow-empty').value; |
| const deny = getRequiredElement<HTMLInputElement>('filter-deny').value; |
| window.localStorage['chrome-accessibility-filter-allow'] = allow; |
| window.localStorage['chrome-accessibility-filter-allow-empty'] = allowEmpty; |
| window.localStorage['chrome-accessibility-filter-deny'] = deny; |
| |
| // The calling |element| is a button with an id of the format |
| // <treeId>-<requestType>, where requestType is one of 'showOrRefreshTree', |
| // 'copyTree'. Send the request type to C++ so is calls the corresponding |
| // function with the result. |
| const requestType = element.id.split('-')[1] as RequestType; |
| if (data.type === 'browser') { |
| const delay = |
| getRequiredElement<HTMLInputElement>('native-ui-delay').valueAsNumber; |
| setTimeout(() => { |
| browserProxy.requestNativeUiTree( |
| (data as BrowserData).sessionId, requestType, allow, allowEmpty, |
| deny); |
| }, delay); |
| } else if (data.type === 'widget') { |
| browserProxy.requestWidgetsTree( |
| (data as WidgetData).widgetId, requestType, allow, allowEmpty, deny); |
| } else { |
| const pageData = data as PageData; |
| browserProxy.requestWebContentsTree( |
| pageData.processId, pageData.routingId, requestType, allow, allowEmpty, |
| deny); |
| } |
| } |
| |
| function requestEvents(data: PageData, element: HTMLElement) { |
| const start = element.textContent === 'Start recording'; |
| if (start) { |
| element.textContent = 'Stop recording'; |
| element.setAttribute('aria-expanded', 'true'); |
| |
| // Disable all other start recording buttons. UI reflects the fact that |
| // there can only be one accessibility recorder at once. |
| const buttons = document.body.querySelectorAll<HTMLButtonElement>( |
| '.recordEventsButton'); |
| for (const button of buttons) { |
| if (button !== element) { |
| button.disabled = true; |
| } |
| } |
| } else { |
| element.textContent = 'Start recording'; |
| element.setAttribute('aria-expanded', 'false'); |
| |
| // Enable all start recording buttons. |
| const buttons = document.body.querySelectorAll<HTMLButtonElement>( |
| '.recordEventsButton'); |
| for (const button of buttons) { |
| if (button !== element) { |
| button.disabled = false; |
| } |
| } |
| } |
| browserProxy.requestAccessibilityEvents( |
| data.processId, data.routingId, start); |
| } |
| |
| function initialize() { |
| const data = requestData(); |
| |
| bindCheckbox('native', data.native); |
| bindCheckbox('web', data.web); |
| bindCheckbox('text', data.text); |
| bindCheckbox('extendedProperties', data.extendedProperties); |
| bindCheckbox('html', data.html); |
| bindDropdown('apiType', data.supportedApiTypes, data.apiType); |
| bindCheckbox('locked', data.locked); |
| |
| getRequiredElement('pages').textContent = ''; |
| |
| const pages = data.pages; |
| for (let i = 0; i < pages.length; i++) { |
| addToPagesList(pages[i]!); |
| } |
| |
| const browsers = data.browsers; |
| for (let i = 0; i < browsers.length; i++) { |
| addToBrowsersList(browsers[i]!); |
| } |
| |
| if (data.viewsAccessibility) { |
| const widgets = data.widgets; |
| if (widgets.length === 0) { |
| // There should always be at least 1 Widget displayed (for the current |
| // window). If this is not the case, and Views Accessibility is enabled, |
| // the only possibility is that Views Accessibility is not enabled for |
| // the current platform. Display a message to the user to indicate this. |
| getRequiredElement('widgets-not-supported').style.display = 'block'; |
| } else { |
| for (let i = 0; i < widgets.length; i++) { |
| addToWidgetsList(widgets[i]!); |
| } |
| } |
| } else { |
| getRequiredElement('widgets').style.display = 'none'; |
| getRequiredElement('widgets-header').style.display = 'none'; |
| } |
| |
| // Cache filters so they're easily accessible on page refresh. |
| const allow = window.localStorage['chrome-accessibility-filter-allow']; |
| const allowEmpty = |
| window.localStorage['chrome-accessibility-filter-allow-empty']; |
| const deny = window.localStorage['chrome-accessibility-filter-deny']; |
| getRequiredElement<HTMLInputElement>('filter-allow').value = |
| allow ? allow : '*'; |
| getRequiredElement<HTMLInputElement>('filter-allow-empty').value = |
| allowEmpty ? allowEmpty : ''; |
| getRequiredElement<HTMLInputElement>('filter-deny').value = deny ? deny : ''; |
| |
| addWebUiListener('copyTree', copyTree); |
| addWebUiListener('showOrRefreshTree', showOrRefreshTree); |
| addWebUiListener('startOrStopEvents', startOrStopEvents); |
| } |
| |
| function bindCheckbox(name: string, value: boolean) { |
| const checkbox = getRequiredElement<HTMLInputElement>(name); |
| checkbox.checked = value; |
| checkbox.addEventListener('change', function() { |
| browserProxy.setGlobalFlag(name, checkbox.checked); |
| document.location.reload(); |
| }); |
| } |
| |
| function bindDropdown(name: string, options: string[], value: string) { |
| const dropdown = getRequiredElement<HTMLSelectElement>(name); |
| // Remove any existing options. |
| dropdown.textContent = ''; |
| // Add options based on the input array. |
| for (const optionName of options) { |
| const option = document.createElement('option'); |
| option.textContent = optionName!; |
| dropdown.appendChild(option); |
| } |
| dropdown.value = value; |
| dropdown.addEventListener('change', function() { |
| // Make sure that the dropdown value is included in options. |
| assert(options.includes(dropdown.value)); |
| browserProxy.setGlobalString(name, dropdown.value); |
| document.location.reload(); |
| }); |
| } |
| |
| function addToPagesList(data: PageData) { |
| // TODO: iterate through data and pages rows instead |
| const id = getIdFromData(data); |
| const row = document.createElement('div'); |
| row.className = 'row'; |
| row.id = id; |
| formatRow(row, data, null); |
| |
| const pages = getRequiredElement('pages'); |
| pages.appendChild(row); |
| } |
| |
| function addToBrowsersList(data: BrowserData) { |
| const id = getIdFromData(data); |
| const row = document.createElement('div'); |
| row.className = 'row'; |
| row.id = id; |
| formatRow(row, data, null); |
| |
| const browsers = getRequiredElement('browsers'); |
| browsers.appendChild(row); |
| } |
| |
| function addToWidgetsList(data: WidgetData) { |
| const id = getIdFromData(data); |
| const row = document.createElement('div'); |
| row.className = 'row'; |
| row.id = id; |
| formatRow(row, data, null); |
| |
| const widgets = getRequiredElement('widgets'); |
| widgets.appendChild(row); |
| } |
| |
| function formatRow( |
| row: HTMLElement, data: BrowserData|PageData|WidgetData, |
| requestType: RequestType|null) { |
| if (!('url' in data)) { |
| if ('error' in data) { |
| row.appendChild(createErrorMessageElement(data)); |
| return; |
| } |
| } |
| |
| if (data.type === 'page') { |
| const pageData = data as PageData; |
| const siteInfo = document.createElement('div'); |
| const properties = ['faviconUrl', 'name', 'url']; |
| for (let j = 0; j < properties.length; j++) { |
| siteInfo.appendChild(formatValue(pageData, properties[j]!)); |
| } |
| row.appendChild(siteInfo); |
| |
| row.appendChild(createModeElement(AxMode.NATIVE_APIS, pageData, 'native')); |
| row.appendChild(createModeElement(AxMode.WEB_CONTENTS, pageData, 'native')); |
| row.appendChild( |
| createModeElement(AxMode.INLINE_TEXT_BOXES, pageData, 'web')); |
| row.appendChild( |
| createModeElement(AxMode.EXTENDED_PROPERTIES, pageData, 'web')); |
| row.appendChild(createModeElement(AxMode.HTML, pageData, 'web')); |
| row.appendChild( |
| createModeElement(AxMode.HTML_METADATA, pageData, 'metadata')); |
| row.appendChild( |
| createModeElement(AxMode.PDF_PRINTING, pageData, 'pdfPrinting')); |
| row.appendChild(createModeElement( |
| AxMode.LABEL_IMAGES, pageData, 'extendedProperties', |
| /*readonly=*/ true)); |
| row.appendChild(createModeElement( |
| AxMode.ANNOTATE_MAIN_NODE, pageData, 'extendedProperties', |
| /* readOnly= */ true)); |
| } else { |
| const siteInfo = document.createElement('span'); |
| siteInfo.appendChild(formatValue(data, 'name')); |
| row.appendChild(siteInfo); |
| } |
| |
| row.appendChild(document.createTextNode(' | ')); |
| |
| const hasTree = 'tree' in data; |
| row.appendChild( |
| createShowAccessibilityTreeElement(data, row.id, requestType, hasTree)); |
| if (navigator.clipboard) { |
| row.appendChild(createCopyAccessibilityTreeElement(data, row.id)); |
| } |
| if (hasTree) { |
| row.appendChild(createHideAccessibilityTreeElement(row.id, data.name)); |
| } |
| // The accessibility event recorder currently only works for pages. |
| // TODO(abigailbklein): Add event recording for native as well. |
| if (data.type === 'page') { |
| row.appendChild(createStartStopAccessibilityEventRecordingElement( |
| data as PageData, row.id)); |
| } |
| |
| if (hasTree) { |
| row.appendChild(createAccessibilityOutputElement(data, row.id, 'tree')); |
| } else if ('eventLogs' in data) { |
| row.appendChild( |
| createAccessibilityOutputElement(data, row.id, 'eventLogs')); |
| } else if ('error' in data) { |
| row.appendChild(createErrorMessageElement(data)); |
| } |
| } |
| |
| function insertHeadingInline( |
| parentElement: HTMLElement, headingText: string, id: string) { |
| const h3 = document.createElement('h3'); |
| h3.textContent = headingText; |
| h3.style.display = 'inline'; |
| h3.id = id + '-title'; |
| parentElement.appendChild(h3); |
| } |
| |
| function formatValue( |
| data: BrowserData|PageData|WidgetData, property: string): HTMLElement { |
| const value = (data as {[k: string]: any})[property]; |
| |
| if (property === 'faviconUrl') { |
| const faviconElement = document.createElement('img'); |
| if (value) { |
| faviconElement.src = value; |
| } |
| faviconElement.alt = ''; |
| return faviconElement; |
| } |
| |
| let text = value ? String(value) : ''; |
| if (text.length > 100) { |
| text = text.substring(0, 100) + '\u2026'; |
| } // ellipsis |
| |
| const span = document.createElement('span'); |
| let unescapedText = text; |
| if (property === 'name') { |
| unescapedText = new DOMParser() |
| .parseFromString( |
| sanitizeInnerHtml(text) as unknown as string, |
| 'text/html', |
| ) |
| .documentElement.textContent || |
| text; |
| } |
| const content = ' ' + unescapedText + ' '; |
| if (property === 'name') { |
| const id = getIdFromData(data); |
| insertHeadingInline(span, content, id); |
| } else { |
| span.textContent = content; |
| } |
| span.className = property; |
| return span; |
| } |
| |
| function getNameForAccessibilityMode(mode: AxMode): string { |
| switch (mode) { |
| case AxMode.NATIVE_APIS: |
| return 'Native'; |
| case AxMode.WEB_CONTENTS: |
| return 'Web'; |
| case AxMode.INLINE_TEXT_BOXES: |
| return 'Inline text'; |
| case AxMode.EXTENDED_PROPERTIES: |
| return 'Extended properties'; |
| case AxMode.HTML: |
| return 'HTML'; |
| case AxMode.HTML_METADATA: |
| return 'HTML Metadata'; |
| case AxMode.LABEL_IMAGES: |
| return 'Label images'; |
| case AxMode.PDF_PRINTING: |
| return 'PDF printing'; |
| case AxMode.PDF_OCR: |
| return 'PDF OCR'; |
| case AxMode.ANNOTATE_MAIN_NODE: |
| return 'Annotate main node'; |
| default: |
| assertNotReached(); |
| } |
| } |
| |
| function createModeElement( |
| mode: AxMode, data: PageData, globalStateName: GlobalStateName, |
| readOnly = false) { |
| const currentMode = data.a11yMode; |
| const element = readOnly ? document.createElement('span') : |
| document.createElement('a', {is: 'action-link'}); |
| if (readOnly) { |
| element.classList.add('readOnlyMode'); |
| } else { |
| element.setAttribute('is', 'action-link'); |
| } |
| |
| element.role = 'button'; |
| |
| const stateText = ((currentMode & mode) !== 0) ? 'true' : 'false'; |
| const isEnabled = |
| (data as unknown as {[k: string]: boolean})[globalStateName]; |
| const accessibilityModeName = getNameForAccessibilityMode(mode); |
| |
| element.ariaLabel = `${accessibilityModeName} for ${data.name}`; |
| element.ariaPressed = stateText; |
| |
| if (isEnabled) { |
| element.textContent = accessibilityModeName + ': ' + stateText; |
| } else { |
| element.textContent = accessibilityModeName + ': disabled'; |
| element.classList.add('disabled'); |
| element.ariaDisabled = 'true'; |
| } |
| if (readOnly) { |
| element.ariaDisabled = 'true'; |
| } else { |
| element.addEventListener( |
| 'click', toggleAccessibility.bind(null, data, mode, globalStateName)); |
| } |
| return element; |
| } |
| |
| function createShowAccessibilityTreeElement( |
| data: BrowserData|PageData|WidgetData, id: string, |
| requestType: RequestType|null, refresh: boolean) { |
| const show = document.createElement('button'); |
| if (requestType === 'showOrRefreshTree') { |
| // Give feedback that the tree has loaded. |
| show.textContent = 'Accessibility tree loaded'; |
| show.ariaLabel = `Accessibility tree loaded for ${data.name}`; |
| setTimeout(() => { |
| show.textContent = 'Refresh accessibility tree'; |
| show.ariaLabel = `Refresh accessibility tree for ${data.name}`; |
| }, 5000); |
| } else { |
| const textContent = |
| refresh ? 'Refresh accessibility tree' : 'Show accessibility tree'; |
| show.textContent = textContent; |
| show.ariaLabel = `${textContent} for ${data.name}`; |
| } |
| show.id = id + '-showOrRefreshTree'; |
| show.setAttribute('aria-expanded', String(refresh)); |
| show.addEventListener('click', requestTree.bind(null, data, show)); |
| return show; |
| } |
| |
| function createHideAccessibilityTreeElement(id: string, name: string) { |
| const hide = document.createElement('button'); |
| hide.textContent = 'Hide accessibility tree'; |
| hide.ariaLabel = `Hide accessibility tree for ${name}`; |
| hide.id = id + '-hideTree'; |
| hide.addEventListener('click', function() { |
| const show = getRequiredElement(id + '-showOrRefreshTree'); |
| show.textContent = 'Show accessibility tree'; |
| show.ariaLabel = `Show accessibility tree for ${name}`; |
| show.setAttribute('aria-expanded', 'false'); |
| show.focus(); |
| const elements = ['hideTree', 'tree']; |
| for (let i = 0; i < elements.length; i++) { |
| const elt = $(id + '-' + elements[i]); |
| if (elt) { |
| elt.style.display = 'none'; |
| } |
| } |
| }); |
| return hide; |
| } |
| |
| function createCopyAccessibilityTreeElement( |
| data: BrowserData|PageData|WidgetData, id: string): HTMLElement { |
| const copy = document.createElement('button'); |
| copy.textContent = 'Copy accessibility tree'; |
| copy.ariaLabel = `Copy accessibility tree for ${data.name}`; |
| copy.id = id + '-copyTree'; |
| copy.addEventListener('click', requestTree.bind(null, data, copy)); |
| return copy; |
| } |
| |
| function createStartStopAccessibilityEventRecordingElement( |
| data: PageData, id: string): HTMLElement { |
| const show = document.createElement('button'); |
| show.classList.add('recordEventsButton'); |
| show.textContent = 'Start recording'; |
| show.ariaLabel = `Start recording for ${data.name}`; |
| show.id = id + '-startOrStopEvents'; |
| show.setAttribute('aria-expanded', 'false'); |
| show.addEventListener('click', requestEvents.bind(null, data, show)); |
| return show; |
| } |
| |
| function createErrorMessageElement(data: PageData): HTMLElement { |
| const errorMessageElement = document.createElement('div'); |
| const errorMessage = data.error; |
| const nbsp = '\u00a0'; |
| errorMessageElement.textContent = errorMessage + nbsp; |
| const closeLink = document.createElement('a'); |
| closeLink.href = '#'; |
| closeLink.textContent = '[close]'; |
| closeLink.addEventListener('click', function() { |
| const parentElement = errorMessageElement.parentElement!; |
| parentElement.removeChild(errorMessageElement); |
| if (parentElement.childElementCount === 0) { |
| parentElement.parentElement!.removeChild(parentElement); |
| } |
| }); |
| errorMessageElement.appendChild(closeLink); |
| return errorMessageElement; |
| } |
| |
| // WebUI listener handler for the 'showOrRefreshTree' event. |
| function showOrRefreshTree(data: PageData) { |
| const id = getIdFromData(data); |
| const row = $(id); |
| if (!row) { |
| return; |
| } |
| |
| row.textContent = ''; |
| formatRow(row, data, 'showOrRefreshTree'); |
| getRequiredElement(id + '-showOrRefreshTree').focus(); |
| } |
| |
| // WebUI listener handler for the 'startOrStopEvents' event. |
| function startOrStopEvents(data: PageData) { |
| const id = getIdFromData(data); |
| const row = $(id); |
| if (!row) { |
| return; |
| } |
| |
| row.textContent = ''; |
| formatRow(row, data, null); |
| getRequiredElement(id + '-startOrStopEvents').focus(); |
| } |
| |
| // WebUI listener handler for the 'copyTree' event. |
| function copyTree(data: PageData) { |
| const id = getIdFromData(data); |
| const row = $(id); |
| if (!row) { |
| return; |
| } |
| const copy = $(id + '-copyTree'); |
| |
| if ('tree' in data) { |
| navigator.clipboard.writeText(data.tree!) |
| .then(() => { |
| assert(copy); |
| copy.textContent = 'Copied to clipboard!'; |
| setTimeout(() => { |
| copy.textContent = 'Copy accessibility tree'; |
| }, 5000); |
| }) |
| .catch(err => { |
| console.error('Unable to copy accessibility tree.', err); |
| }); |
| } else if ('error' in data) { |
| console.error('Unable to copy accessibility tree.', data.error); |
| } |
| |
| const tree = $(id + '-tree'); |
| // If the tree is currently shown, update it since it may have changed. |
| if (tree && tree.style.display !== 'none') { |
| showOrRefreshTree(data); |
| getRequiredElement(id + '-copyTree').focus(); |
| } |
| } |
| |
| // type is either 'tree' or 'eventLogs' |
| function createAccessibilityOutputElement( |
| data: BrowserData|PageData|WidgetData, id: string, |
| type: 'tree'|'eventLogs'): HTMLElement { |
| let treeElement = $(id + '-' + type); |
| if (!treeElement) { |
| treeElement = document.createElement('pre'); |
| treeElement.id = id + '-' + type; |
| } |
| const dataSplitByLine = |
| (data as unknown as {[k: string]: string})[type]!.split(/\n/); |
| for (let i = 0; i < dataSplitByLine.length; i++) { |
| const lineElement = document.createElement('div'); |
| lineElement.textContent = dataSplitByLine[i]!; |
| treeElement.appendChild(lineElement); |
| } |
| return treeElement; |
| } |
| |
| document.addEventListener('DOMContentLoaded', initialize); |