diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmaccessibility.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmaccessibility.cpp | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp new file mode 100644 index 0000000000..4c3cb46ba3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -0,0 +1,789 @@ +// 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 <QtGui/qwindow.h> + +#if QT_CONFIG(accessibility) + +#include <QtGui/private/qaccessiblebridgeutils_p.h> + +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<emscripten::val>("createElement", std::string("button")); + button.set("innerText", std::string("Enable Screen Reader")); + button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader")); + container.call<void>("appendChild", button); + + auto enableContext = std::make_tuple(button, std::make_unique<qstdweb::EventCallback> + (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<void>("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<void>("removeChild", element); + } + m_enableButtons.clear(); + populateAccessibilityTree(QAccessible::queryAccessibleInterface(m_rootObject)); +} + +emscripten::val QWasmAccessibility::getContainer(QWindow *window) +{ + return window ? static_cast<QWasmWindow *>(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 <div> elements with a spesific ARIA role, + // or create e.g. <button> elements which should have built-in accessibility. + emscripten::val element = [this, iface, document] { + + emscripten::val element = emscripten::val::undefined(); + + switch (iface->role()) { + + case QAccessible::Button: { + element = document.call<emscripten::val>("createElement", std::string("button")); + element.call<void>("addEventListener", emscripten::val("click"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + case QAccessible::CheckBox: { + element = document.call<emscripten::val>("createElement", std::string("input")); + element.call<void>("setAttribute", std::string("type"), std::string("checkbox")); + if (iface->state().checked) { + element.call<void>("setAttribute", std::string("checked"), std::string("true")); + } + element.call<void>("addEventListener", emscripten::val("change"), + emscripten::val::module_property("qtEventReceived"), true); + + } break; + + case QAccessible::RadioButton: { + element = document.call<emscripten::val>("createElement", std::string("input")); + element.call<void>("setAttribute", std::string("type"), std::string("radio")); + if (iface->state().checked) { + element.call<void>("setAttribute", std::string("checked"), std::string("true")); + } + element.set(std::string("name"), std::string("buttonGroup")); + element.call<void>("addEventListener", emscripten::val("change"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + + case QAccessible::SpinBox: { + element = document.call<emscripten::val>("createElement", std::string("input")); + element.call<void>("setAttribute", std::string("type"), std::string("number")); + std::string valueString = iface->valueInterface()->currentValue().toString().toStdString(); + element.call<void>("setAttribute", std::string("value"), valueString); + element.call<void>("addEventListener", emscripten::val("change"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + + case QAccessible::Slider: { + element = document.call<emscripten::val>("createElement", std::string("input")); + element.call<void>("setAttribute", std::string("type"), std::string("range")); + std::string valueString = iface->valueInterface()->currentValue().toString().toStdString(); + element.call<void>("setAttribute", std::string("value"), valueString); + element.call<void>("addEventListener", emscripten::val("change"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + + case QAccessible::PageTabList:{ + element = document.call<emscripten::val>("createElement", std::string("div")); + element.call<void>("setAttribute", std::string("role"), std::string("tablist")); + QString idName = iface->text(QAccessible::Name).replace(" ", "_"); + idName += "_tabList"; + element.call<void>("setAttribute", std::string("id"), idName.toStdString()); + + for (int i = 0; i < iface->childCount(); ++i) { + if (iface->child(i)->role() == QAccessible::PageTab){ + emscripten::val elementTab = emscripten::val::undefined(); + elementTab = ensureHtmlElement(iface->child(i)); + elementTab.call<void>("setAttribute", std::string("aria-owns"), idName.toStdString()); + setHtmlElementGeometry(iface->child(i)); + } + } + } break; + + case QAccessible::PageTab:{ + element = document.call<emscripten::val>("createElement", std::string("button")); + element.call<void>("setAttribute", std::string("role"), std::string("tab")); + QString text = iface->text(QAccessible::Name); + element.call<void>("setAttribute", std::string("title"), text.toStdString()); + element.call<void>("addEventListener", emscripten::val("click"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + + case QAccessible::ScrollBar: { + element = document.call<emscripten::val>("createElement", std::string("div")); + element.call<void>("setAttribute", std::string("role"), std::string("scrollbar")); + std::string valueString = iface->valueInterface()->currentValue().toString().toStdString(); + element.call<void>("setAttribute", std::string("aria-valuenow"), valueString); + element.call<void>("addEventListener", emscripten::val("change"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + + case QAccessible::StaticText: { + element = document.call<emscripten::val>("createElement", std::string("textarea")); + element.call<void>("setAttribute", std::string("readonly"), std::string("true")); + + } break; + case QAccessible::Dialog: { + element = document.call<emscripten::val>("createElement", std::string("dialog")); + }break; + case QAccessible::ToolBar:{ + element = document.call<emscripten::val>("createElement", std::string("div")); + QString text = iface->text(QAccessible::Name); + + element.call<void>("setAttribute", std::string("role"), std::string("toolbar")); + element.call<void>("setAttribute", std::string("title"), text.toStdString()); + element.call<void>("addEventListener", emscripten::val("click"), + emscripten::val::module_property("qtEventReceived"), true); + }break; + case QAccessible::MenuItem: + case QAccessible::ButtonMenu: { + element = document.call<emscripten::val>("createElement", std::string("button")); + QString text = iface->text(QAccessible::Name); + + element.call<void>("setAttribute", std::string("role"), std::string("menuitem")); + element.call<void>("setAttribute", std::string("title"), text.toStdString()); + element.call<void>("addEventListener", emscripten::val("click"), + emscripten::val::module_property("qtEventReceived"), true); + }break; + case QAccessible::MenuBar: + case QAccessible::PopupMenu: { + element = document.call<emscripten::val>("createElement",std::string("div")); + QString text = iface->text(QAccessible::Name); + element.call<void>("setAttribute", std::string("role"), std::string("menubar")); + element.call<void>("setAttribute", std::string("title"), text.toStdString()); + for (int i = 0; i < iface->childCount(); ++i) { + emscripten::val childElement = emscripten::val::undefined(); + childElement= ensureHtmlElement(iface->child(i)); + childElement.call<void>("setAttribute", std::string("aria-owns"), text.toStdString()); + setHtmlElementTextName(iface->child(i)); + setHtmlElementGeometry(iface->child(i)); + } + }break; + case QAccessible::EditableText: { + element = document.call<emscripten::val>("createElement", std::string("input")); + element.call<void>("setAttribute", std::string("type"),std::string("text")); + element.call<void>("addEventListener", emscripten::val("input"), + emscripten::val::module_property("qtEventReceived"), true); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role(); + element = document.call<emscripten::val>("createElement", std::string("div")); + } + + return element; + + }(); + + // Add the html element to the container if we have one. If not there + // is a second chance when handling the ObjectShow event. + if (!container.isUndefined()) + container.call<void>("appendChild", element); + + return element; +} + +void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface) +{ + Q_UNUSED(iface); + qCDebug(lcQpaAccessibility) << "TODO destroyHtmlElement"; +} + +emscripten::val QWasmAccessibility::ensureHtmlElement(QAccessibleInterface *iface) +{ + auto it = m_elements.find(iface); + if (it != m_elements.end()) + return it.value(); + + emscripten::val element = createHtmlElement(iface); + m_elements.insert(iface, element); + + return element; +} + +void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible) +{ + emscripten::val element = ensureHtmlElement(iface); + emscripten::val container = getContainer(iface); + + if (container.isUndefined()) { + qCDebug(lcQpaAccessibility) << "TODO: setHtmlElementVisibility: unable to find html container for element" << iface; + return; + } + + container.call<void>("appendChild", element); + + element.set("ariaHidden", !visible); // ariaHidden mean completely hidden; maybe some sort of soft-hidden should be used. +} + +void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface) +{ + emscripten::val element = ensureHtmlElement(iface); + + // QAccessibleInterface gives us the geometry in global (screen) coordinates. Translate that + // to window geometry in order to position elements relative to window origin. + QWindow *window = getWindow(iface); + if (!window) + qCWarning(lcQpaAccessibility) << "Unable to find window for" << iface << "setting null geometry"; + QRect screenGeometry = iface->rect(); + QPoint windowPos = window ? window->mapFromGlobal(screenGeometry.topLeft()) : QPoint(); + QRect windowGeometry(windowPos, screenGeometry.size()); + + setHtmlElementGeometry(element, windowGeometry); +} + +void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry) +{ + // Position the element using "position: absolute" in order to place + // it under the corresponding Qt element in the screen. + emscripten::val style = element["style"]; + style.set("position", std::string("absolute")); + style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the + // screen element, but isn't + style.set("left", std::to_string(geometry.x()) + "px"); + style.set("top", std::to_string(geometry.y()) + "px"); + style.set("width", std::to_string(geometry.width()) + "px"); + style.set("height", std::to_string(geometry.height()) + "px"); +} + +void QWasmAccessibility::setHtmlElementTextName(QAccessibleInterface *iface) +{ + emscripten::val element = ensureHtmlElement(iface); + QString text = iface->text(QAccessible::Name); + element.set("innerHTML", text.toStdString()); // FIXME: use something else than innerHTML +} + +void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) { + emscripten::val element = ensureHtmlElement(iface); + QString text = iface->text(QAccessible::Name); + element.call<void>("setAttribute", std::string("name"), text.toStdString()); + QString value = iface->text(QAccessible::Value); + element.set("innerHTML", value.toStdString()); +} + +void QWasmAccessibility::setHtmlElementDescription(QAccessibleInterface *iface) { + emscripten::val element = ensureHtmlElement(iface); + QString desc = iface->text(QAccessible::Description); + element.call<void>("setAttribute", std::string("aria-description"), desc.toStdString()); +} + +void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) { + + switch (event->type()) { + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::Focus: + case QAccessible::TextRemoved: + case QAccessible::TextInserted: + case QAccessible::TextCaretMoved: { + setHtmlElementTextNameLE(event->accessibleInterface()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event) +{ + + QAccessibleInterface *iface = m_elements.key(event["target"]); + if (iface == nullptr) { + return; + } else { + QString eventType = QString::fromStdString(event["type"].as<std::string>()); + const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface); + if (actionNames.contains(QAccessibleActionInterface::pressAction())) { + + iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction()); + + } else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) { + + iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction()); + + } else if (actionNames.contains(QAccessibleActionInterface::increaseAction()) || + actionNames.contains(QAccessibleActionInterface::decreaseAction())) { + + QString val = QString::fromStdString(event["target"]["value"].as<std::string>()); + + iface->valueInterface()->setCurrentValue(val.toInt()); + + } else if (eventType == "input") { + + // as EditableTextInterface is not implemented in qml accessibility + // so we need to check the role for text to update in the textbox during accessibility + + if (iface->editableTextInterface() || iface->role() == QAccessible::EditableText) { + std::string insertText = event["target"]["value"].as<std::string>(); + iface->setText(QAccessible::Value, QString::fromStdString(insertText)); + } + } + } +} + +void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event) +{ + qCDebug(lcQpaAccessibility) << "TODO: implement handleButtonUpdate for event" << event->type(); +} + +void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::StateChanged: { + QAccessibleInterface *accessible = event->accessibleInterface(); + emscripten::val element = ensureHtmlElement(accessible); + bool checkedString = accessible->state().checked ? true : false; + element.call<void>("setAttribute", std::string("checked"), checkedString); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type(); + break; + } +} +void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event) +{ + QAccessibleInterface *iface = event->accessibleInterface(); + QString text = iface->text(QAccessible::Name); + QString desc = iface->text(QAccessible::Description); + switch (event->type()) { + case QAccessible::NameChanged: + case QAccessible::StateChanged:{ + emscripten::val element = ensureHtmlElement(iface); + element.call<void>("setAttribute", std::string("title"), text.toStdString()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type(); + break; + } +} +void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event) +{ + QAccessibleInterface *iface = event->accessibleInterface(); + QString text = iface->text(QAccessible::Name); + QString desc = iface->text(QAccessible::Description); + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: + case QAccessible::MenuStart ://"TODO: To implement later + case QAccessible::PopupMenuStart://"TODO: To implement later + case QAccessible::StateChanged:{ + emscripten::val element = ensureHtmlElement(iface); + element.call<void>("setAttribute", std::string("title"), text.toStdString()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type(); + break; + } +} +void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) { + + switch (event->type()) { + case QAccessible::NameChanged: + case QAccessible::Focus: + case QAccessible::DialogStart: + case QAccessible::StateChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface) +{ + if (!iface) + return; + + // Create html element for the interface, sync up properties. + ensureHtmlElement(iface); + const bool visible = !iface->state().invisible; + setHtmlElementVisibility(iface, visible); + setHtmlElementGeometry(iface); + setHtmlElementTextName(iface); + setHtmlElementDescription(iface); + + for (int i = 0; i < iface->childCount(); ++i) + populateAccessibilityTree(iface->child(i)); +} + +void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::StateChanged: { + QAccessibleInterface *accessible = event->accessibleInterface(); + emscripten::val element = ensureHtmlElement(accessible); + std::string checkedString = accessible->state().checked ? "true" : "false"; + element.call<void>("setAttribute", std::string("checked"), checkedString); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::ValueChanged: { + QAccessibleInterface *accessible = event->accessibleInterface(); + emscripten::val element = ensureHtmlElement(accessible); + std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString(); + element.call<void>("setAttribute", std::string("value"), valueString); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::ValueChanged: { + QAccessibleInterface *accessible = event->accessibleInterface(); + emscripten::val element = ensureHtmlElement(accessible); + std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString(); + element.call<void>("setAttribute", std::string("value"), valueString); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qDebug() << "TODO: implement handleSliderUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::ValueChanged: { + QAccessibleInterface *accessible = event->accessibleInterface(); + emscripten::val element = ensureHtmlElement(accessible); + std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString(); + element.call<void>("setAttribute", std::string("aria-valuenow"), valueString); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qDebug() << "TODO: implement handleSliderUpdate for event" << event->type(); + break; + } + +} + +void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::Focus: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::NameChanged: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::Focus: { + setHtmlElementTextName(event->accessibleInterface()); + } break; + case QAccessible::DescriptionChanged: { + setHtmlElementDescription(event->accessibleInterface()); + } break; + default: + qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type(); + break; + } +} + +void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) +{ + if (!m_accessibilityEnabled) + return; + + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qWarning() << "notifyAccessibilityUpdate with null a11y interface" ; + return; + } + + // Handle some common event types. See + // https://doc.qt.io/qt-5/qaccessible.html#Event-enum + switch (event->type()) { + case QAccessible::ObjectShow: + setHtmlElementVisibility(iface, true); + + // Sync up properties on show; + setHtmlElementGeometry(iface); + setHtmlElementTextName(iface); + setHtmlElementDescription(iface); + + return; + break; + case QAccessible::ObjectHide: + setHtmlElementVisibility(iface, false); + return; + break; + // TODO: maybe handle more types here + default: + break; + }; + + // Switch on interface role, see + // https://doc.qt.io/qt-5/qaccessibleinterface.html#role + switch (iface->role()) { + case QAccessible::StaticText: + handleStaticTextUpdate(event); + break; + case QAccessible::Button: + handleStaticTextUpdate(event); + break; + case QAccessible::CheckBox: + handleCheckBoxUpdate(event); + break; + case QAccessible::EditableText: + handleLineEditUpdate(event); + break; + case QAccessible::Dialog: + handleDialogUpdate(event); + break; + case QAccessible::MenuItem: + case QAccessible::MenuBar: + case QAccessible::PopupMenu: + handleMenuUpdate(event); + break; + case QAccessible::ToolBar: + case QAccessible::ButtonMenu: + handleToolUpdate(event); + case QAccessible::RadioButton: + handleRadioButtonUpdate(event); + break; + case QAccessible::SpinBox: + handleSpinBoxUpdate(event); + break; + case QAccessible::Slider: + handleSliderUpdate(event); + break; + case QAccessible::PageTab: + handlePageTabUpdate(event); + break; + case QAccessible::PageTabList: + handlePageTabListUpdate(event); + break; + case QAccessible::ScrollBar: + handleScrollBarUpdate(event); + break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role(); + }; +} + +void QWasmAccessibility::setRootObject(QObject *root) +{ + m_rootObject = root; +} + +void QWasmAccessibility::initialize() +{ + +} + +void QWasmAccessibility::cleanup() +{ + +} + +void QWasmAccessibility::onHtmlEventReceived(emscripten::val event) +{ + static_cast<QWasmAccessibility *>(QWasmIntegration::get()->accessibility())->handleEventFromHtmlElement(event); +} + +EMSCRIPTEN_BINDINGS(qtButtonEvent) { + function("qtEventReceived", &QWasmAccessibility::onHtmlEventReceived); +} + +#endif // QT_CONFIG(accessibility) |