// 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, assertNotReachedCase } 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
var AxMode;
(function (AxMode) {
    AxMode[AxMode["NATIVE_APIS"] = 1] = "NATIVE_APIS";
    AxMode[AxMode["WEB_CONTENTS"] = 2] = "WEB_CONTENTS";
    AxMode[AxMode["INLINE_TEXT_BOXES"] = 4] = "INLINE_TEXT_BOXES";
    AxMode[AxMode["EXTENDED_PROPERTIES"] = 8] = "EXTENDED_PROPERTIES";
    AxMode[AxMode["HTML"] = 16] = "HTML";
    AxMode[AxMode["HTML_METADATA"] = 32] = "HTML_METADATA";
    AxMode[AxMode["LABEL_IMAGES"] = 64] = "LABEL_IMAGES";
    AxMode[AxMode["PDF_PRINTING"] = 128] = "PDF_PRINTING";
    AxMode[AxMode["ANNOTATE_MAIN_NODE"] = 256] = "ANNOTATE_MAIN_NODE";
    AxMode[AxMode["FROM_PLATFORM"] = 512] = "FROM_PLATFORM";
    AxMode[AxMode["SCREEN_READER"] = 1024] = "SCREEN_READER";
    AxMode[AxMode["NATIVE_ADAPTED_WEB_CONTENTS"] = 2048] = "NATIVE_ADAPTED_WEB_CONTENTS";
})(AxMode || (AxMode = {}));
class BrowserProxy {
    initialize() {
        chrome.send('initialize');
    }
    toggleAccessibility(processId, routingId, modeId, shouldRequestTree) {
        chrome.send('toggleAccessibility', [{
                processId,
                routingId,
                modeId,
                shouldRequestTree,
            }]);
    }
    requestNativeUiTree(sessionId, requestType, allow, allowEmpty, deny) {
        chrome.send('requestNativeUITree', [{
                sessionId,
                requestType,
                filters: { allow, allowEmpty, deny },
            }]);
    }
    requestWebContentsTree(processId, routingId, requestType, allow, allowEmpty, deny) {
        chrome.send('requestWebContentsTree', [
            { processId, routingId, requestType, filters: { allow, allowEmpty, deny } },
        ]);
    }
    requestWidgetsTree(widgetId, requestType, allow, allowEmpty, deny) {
        chrome.send('requestWidgetsTree', [{ widgetId, requestType, filters: { allow, allowEmpty, deny } }]);
    }
    requestAccessibilityEvents(processId, routingId, start) {
        chrome.send('requestAccessibilityEvents', [{ processId, routingId, start }]);
    }
    setGlobalFlag(flagName, enabled) {
        chrome.send('setGlobalFlag', [{ flagName, enabled }]);
    }
    setGlobalString(stringName, value) {
        chrome.send('setGlobalString', [{ stringName, value }]);
    }
}
const browserProxy = new BrowserProxy();
function requestData() {
    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) {
    switch (data.type) {
        case 'page':
            return 'page_' + data.processId + '_' + data.routingId;
        case 'browser':
            return 'browser_' + data.sessionId;
        case 'widget':
            return 'widget_' + data.widgetId;
        default:
            return assertNotReachedCase(data, 'Unknown data type.');
    }
}
function toggleAccessibility(data, mode, 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, element) {
    return new Promise((resolve) => {
        const allow = getRequiredElement('filter-allow').value;
        const allowEmpty = getRequiredElement('filter-allow-empty').value;
        const deny = getRequiredElement('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];
        if (data.type === 'browser') {
            const delay = getRequiredElement('native-ui-delay').valueAsNumber;
            setTimeout(() => {
                browserProxy.requestNativeUiTree(data.sessionId, requestType, allow, allowEmpty, deny);
                resolve();
            }, delay);
        }
        else if (data.type === 'widget') {
            browserProxy.requestWidgetsTree(data.widgetId, requestType, allow, allowEmpty, deny);
            resolve();
        }
        else {
            browserProxy.requestWebContentsTree(data.processId, data.routingId, requestType, allow, allowEmpty, deny);
            resolve();
        }
    });
}
function requestEvents(data, element) {
    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('.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('.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, data.lockedPlatformModes.native);
    bindCheckbox('web', data.web, data.lockedPlatformModes.web);
    bindCheckbox('text', data.text, data.lockedPlatformModes.text);
    bindCheckbox('extendedProperties', data.extendedProperties, data.lockedPlatformModes.extendedProperties);
    bindCheckbox('screenReader', data.screenReader, data.lockedPlatformModes.screenReader);
    bindCheckbox('html', data.html, data.lockedPlatformModes.html);
    bindDropdown('apiType', data.supportedApiTypes, data.apiType);
    bindCheckbox('isolate', data.isolate);
    bindCheckbox('locked', data.locked);
    getRequiredElement('active_at_name').textContent = data.detectedATName;
    getRequiredElement('active_at_is_screen_reader').textContent =
        data.isScreenReaderActive ? 'Yes' : 'No';
    if (data.uiaClientProcessNames !== undefined) {
        updateUiaClientList(data.uiaClientProcessNames);
    }
    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';
    }
    updateDisplay(data);
    // 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('filter-allow').value =
        allow ? allow : '*';
    getRequiredElement('filter-allow-empty').value =
        allowEmpty ? allowEmpty : '';
    getRequiredElement('filter-deny').value = deny ? deny : '';
    browserProxy.initialize();
    addWebUiListener('copyTree', copyTree);
    addWebUiListener('showOrRefreshTree', showOrRefreshTree);
    addWebUiListener('startOrStopEvents', startOrStopEvents);
    addWebUiListener('updateDisplay', updateDisplay);
}
function bindCheckbox(name, value, disable) {
    const checkbox = getRequiredElement(name);
    checkbox.checked = value;
    if (disable) {
        checkbox.disabled = true;
        const label = document.querySelector('label:has(#' + name + ')');
        if (label) {
            label.setAttribute('title', 'Forced on because of an interaction with an assistive technology, ' +
                'application or platform feature.\n' +
                'To uncheck, use the below checkbox labeled, ' +
                '"Suppress automatic accessibility enablement..."');
            label.classList.add('disabled');
        }
        return;
    }
    checkbox.addEventListener('change', function () {
        browserProxy.setGlobalFlag(name, checkbox.checked);
        document.location.reload();
    });
}
function bindDropdown(name, options, value) {
    const dropdown = getRequiredElement(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 updateUiaClientList(processNames) {
    const list = getRequiredElement('uia_clients_list');
    list.textContent = '';
    if (processNames.length === 0) {
        const item = document.createElement('li');
        item.textContent = 'None';
        list.appendChild(item);
        return;
    }
    for (const processName of processNames) {
        const item = document.createElement('li');
        item.textContent = processName;
        list.appendChild(item);
    }
}
function addToPagesList(data) {
    // 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);
    const pages = getRequiredElement('pages');
    pages.appendChild(row);
}
function addToBrowsersList(data) {
    const id = getIdFromData(data);
    const row = document.createElement('div');
    row.className = 'row';
    row.id = id;
    formatRow(row, data);
    const browsers = getRequiredElement('browsers');
    browsers.appendChild(row);
}
function addToWidgetsList(data) {
    const id = getIdFromData(data);
    const row = document.createElement('div');
    row.className = 'row';
    row.id = id;
    formatRow(row, data);
    const widgets = getRequiredElement('widgets');
    widgets.appendChild(row);
}
function formatRow(row, data) {
    if (!('url' in data)) {
        if ('error' in data) {
            row.appendChild(createErrorMessageElement(data));
            return;
        }
    }
    if (data.type === 'page') {
        const siteInfo = document.createElement('div');
        const properties = ['faviconUrl', 'name', 'url'];
        for (let j = 0; j < properties.length; j++) {
            siteInfo.appendChild(formatValue(data, properties[j]));
        }
        row.appendChild(siteInfo);
        // Create a row of buttons that can be used to read and modify the
        // AXModes scoped to a specific WebContents.
        row.appendChild(createModeElement(AxMode.NATIVE_APIS, data, 'native'));
        row.appendChild(createModeElement(AxMode.WEB_CONTENTS, data, 'web'));
        row.appendChild(createModeElement(AxMode.INLINE_TEXT_BOXES, data, 'text'));
        row.appendChild(createModeElement(AxMode.EXTENDED_PROPERTIES, data, 'extendedProperties'));
        row.appendChild(createModeElement(AxMode.SCREEN_READER, data, 'screenReader'));
        row.appendChild(createModeElement(AxMode.HTML, data, 'html'));
        row.appendChild(createModeElement(AxMode.HTML_METADATA, data, 'metadata'));
        row.appendChild(createModeElement(AxMode.PDF_PRINTING, data, 'pdfPrinting'));
        row.appendChild(createModeElement(AxMode.LABEL_IMAGES, data, 'labelImages', 
        /*readonly=*/ true));
        row.appendChild(createModeElement(AxMode.ANNOTATE_MAIN_NODE, data, 'annotateMainNode', 
        /* readOnly= */ true));
        // AxMode.FROM_PLATFORM is unconditionally filtered out and is therefore
        // never presented to renderers or the user.
    }
    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, 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, 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, headingText, id) {
    const h3 = document.createElement('h3');
    h3.textContent = headingText;
    h3.style.display = 'inline';
    h3.id = id + '-title';
    parentElement.appendChild(h3);
}
function formatValue(data, property) {
    const value = data[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), '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) {
    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.ANNOTATE_MAIN_NODE:
            return 'Annotate main node';
        case AxMode.SCREEN_READER:
            return 'Screen reader';
        default:
            assertNotReached();
    }
}
// Create a button element that can be used to modify the AXMode in the given
// WebContents/page only. The label consists of the name for the AXMode
// (via globalStateName) and the value of the AXMode (via PageData).
// AXModes that do not allow modification this way should pass readOnly == true.
function createModeElement(mode, data, 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[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, id, refresh) {
    const show = document.createElement('button');
    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(data, show).then(() => {
            show.textContent = 'Refresh accessibility tree';
            show.ariaLabel = `Refresh accessibility tree for ${data.name}`;
        });
    });
    return show;
}
function createHideAccessibilityTreeElement(id, name) {
    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, id) {
    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, id) {
    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) {
    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) {
    const id = getIdFromData(data);
    const row = $(id);
    if (!row) {
        return;
    }
    row.textContent = '';
    formatRow(row, data);
    getRequiredElement(id + '-showOrRefreshTree').focus();
}
// WebUI listener handler for the 'startOrStopEvents' event.
function startOrStopEvents(data) {
    const id = getIdFromData(data);
    const row = $(id);
    if (!row) {
        return;
    }
    row.textContent = '';
    formatRow(row, data);
    getRequiredElement(id + '-startOrStopEvents').focus();
}
// WebUI listener handler for the 'copyTree' event.
function copyTree(data) {
    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();
    }
}
// Updates the page with the state contained in `data`.
function updateDisplay(data) {
    if (data.native !== undefined) {
        getRequiredElement('native').checked = data.native;
    }
    if (data.web !== undefined) {
        getRequiredElement('web').checked = data.web;
    }
    if (data.text !== undefined) {
        getRequiredElement('text').checked = data.text;
    }
    if (data.extendedProperties !== undefined) {
        getRequiredElement('extendedProperties').checked =
            data.extendedProperties;
    }
    if (data.screenReader !== undefined) {
        getRequiredElement('screenReader').checked =
            data.screenReader;
    }
    if (data.html !== undefined) {
        getRequiredElement('html').checked = data.html;
    }
    // 
}
// type is either 'tree' or 'eventLogs'
function createAccessibilityOutputElement(data, id, type) {
    let treeElement = $(id + '-' + type);
    if (!treeElement) {
        treeElement = document.createElement('pre');
        treeElement.id = id + '-' + type;
    }
    const dataSplitByLine = data[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);
