// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmaccessibility.h" #include "qwasmscreen.h" #include "qwasmwindow.h" #include "qwasmintegration.h" #include #if QT_CONFIG(accessibility) #include Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility") // Qt WebAssembly a11y backend // // This backend implements accessibility support by creating "shadowing" html // elements for each Qt UI element. We access the DOM by using Emscripten's // val.h API. // // Currently, html elements are created in response to notifyAccessibilityUpdate // events. In addition or alternatively, we could also walk the accessibility tree // from setRootObject(). QWasmAccessibility::QWasmAccessibility() { s_instance = this; } QWasmAccessibility::~QWasmAccessibility() { s_instance = nullptr; } QWasmAccessibility *QWasmAccessibility::s_instance = nullptr; QWasmAccessibility* QWasmAccessibility::get() { return s_instance; } void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window) { get()->addAccessibilityEnableButtonImpl(window); } void QWasmAccessibility::removeAccessibilityEnableButton(QWindow *window) { get()->removeAccessibilityEnableButtonImpl(window); } void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window) { if (m_accessibilityEnabled) return; emscripten::val container = getContainer(window); emscripten::val document = getDocument(container); emscripten::val button = document.call("createElement", std::string("button")); button.set("innerText", std::string("Enable Screen Reader")); button["classList"].call("add", emscripten::val("hidden-visually-read-by-screen-reader")); container.call("appendChild", button); auto enableContext = std::make_tuple(button, std::make_unique (button, std::string("click"), [this](emscripten::val) { enableAccessibility(); })); m_enableButtons.insert(std::make_pair(window, std::move(enableContext))); } void QWasmAccessibility::removeAccessibilityEnableButtonImpl(QWindow *window) { auto it = m_enableButtons.find(window); if (it == m_enableButtons.end()) return; // Remove button auto [element, callback] = it->second; Q_UNUSED(callback); element["parentElement"].call("removeChild", element); m_enableButtons.erase(it); } void QWasmAccessibility::enableAccessibility() { // Enable accessibility globally for the applicaton. Remove all "enable" // buttons and populate the accessibility tree, starting from the root object. Q_ASSERT(!m_accessibilityEnabled); m_accessibilityEnabled = true; for (const auto& [key, value] : m_enableButtons) { const auto &[element, callback] = value; Q_UNUSED(key); Q_UNUSED(callback); element["parentElement"].call("removeChild", element); } m_enableButtons.clear(); populateAccessibilityTree(QAccessible::queryAccessibleInterface(m_rootObject)); } emscripten::val QWasmAccessibility::getContainer(QWindow *window) { return window ? static_cast(window->handle())->a11yContainer() : emscripten::val::undefined(); } emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface) { if (!iface) return emscripten::val::undefined(); return getContainer(getWindow(iface)); } QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface) { QWindow *window = iface->window(); // this is needed to add tabs as the window is not available if (!window && iface->parent()) window = iface->parent()->window(); return window; } emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container) { if (container.isUndefined()) return emscripten::val::global("document"); return container["ownerDocument"]; } emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface) { return getDocument(getContainer(iface)); } emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface) { // Get the html container element for the interface; this depends on which // QScreen it is on. If the interface is not on a screen yet we get an undefined // container, and the code below handles that case as well. emscripten::val container = getContainer(iface); // Get the correct html document for the container, or fall back // to the global document. TODO: Does using the correct document actually matter? emscripten::val document = getDocument(container); // Translate the Qt a11y elemen role into html element type + ARIA role. // Here we can either create
elements with a spesific ARIA role, // or create e.g.