Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmaccessibility.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5#include "qwasmscreen.h"
6#include "qwasmwindow.h"
8#include <QtCore/private/qwasmsuspendresumecontrol_p.h>
9#include <QtGui/qwindow.h>
10
11#include <sstream>
12
14{
15 QWasmAccessibility::enable();
16}
17
18#if QT_CONFIG(accessibility)
19
20#include <QtGui/private/qaccessiblebridgeutils_p.h>
21
22Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
23
24namespace {
25EM_JS(emscripten::EM_VAL, getActiveElement_js, (emscripten::EM_VAL undefHandle), {
26 var activeEl = document.activeElement;
27 while (true) {
28 if (!activeEl) {
29 return undefHandle;
30 } else if (activeEl.shadowRoot) {
31 activeEl = activeEl.shadowRoot.activeElement;
32 } else {
33 return Emval.toHandle(activeEl);
34 }
35 }
36})
37}
38
39// Qt WebAssembly a11y backend
40//
41// This backend implements accessibility support by creating "shadowing" html
42// elements for each Qt UI element. We access the DOM by using Emscripten's
43// val.h API.
44//
45// Currently, html elements are created in response to notifyAccessibilityUpdate
46// events. In addition or alternatively, we could also walk the accessibility tree
47// from setRootObject().
48
49QWasmAccessibility::QWasmAccessibility()
50{
51 s_instance = this;
52
53 if (qEnvironmentVariableIntValue("QT_WASM_ENABLE_ACCESSIBILITY") == 1)
54 enableAccessibility();
55
56 // Register accessibility element event handler
57 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
58 Q_ASSERT(suspendResume);
59 m_eventHandlerIndex = suspendResume->registerEventHandler([this](const emscripten::val event){
60 this->handleEventFromHtmlElement(event);
61 });
62}
63
64QWasmAccessibility::~QWasmAccessibility()
65{
66 // Remove accessibility element event handler
67 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
68 Q_ASSERT(suspendResume);
69 suspendResume->removeEventHandler(m_eventHandlerIndex);
70
71 s_instance = nullptr;
72}
73
74QWasmAccessibility *QWasmAccessibility::s_instance = nullptr;
75
76QWasmAccessibility* QWasmAccessibility::get()
77{
78 return s_instance;
79}
80
81void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window)
82{
83 get()->addAccessibilityEnableButtonImpl(window);
84}
85
86void QWasmAccessibility::onShowWindow(QWindow *window)
87{
88 get()->onShowWindowImpl(window);
89}
90
91void QWasmAccessibility::onRemoveWindow(QWindow *window)
92{
93 get()->onRemoveWindowImpl(window);
94}
95
96bool QWasmAccessibility::isEnabled()
97{
98 return get()->m_accessibilityEnabled;
99}
100void QWasmAccessibility::enable()
101{
102 if (!isEnabled())
103 get()->enableAccessibility();
104}
105
106void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
107{
108 if (m_accessibilityEnabled)
109 return;
110
111 emscripten::val container = getElementContainer(window);
112 emscripten::val document = getDocument(container);
113 emscripten::val button = document.call<emscripten::val>("createElement", std::string("button"));
114 setProperty(button, "innerText", "Enable Screen Reader");
115 button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader"));
116 container.call<void>("appendChild", button);
117
118 auto enableContext = std::make_tuple(button, std::make_unique<qstdweb::EventCallback>
119 (button, std::string("click"), [this](emscripten::val) { enableAccessibility(); }));
120 m_enableButtons.insert(std::make_pair(window, std::move(enableContext)));
121}
122
123void QWasmAccessibility::onShowWindowImpl(QWindow *window)
124{
125 if (!m_accessibilityEnabled)
126 return;
127 populateAccessibilityTree(window->accessibleRoot());
128}
129
130void QWasmAccessibility::onRemoveWindowImpl(QWindow *window)
131{
132 {
133 const auto it = m_enableButtons.find(window);
134 if (it != m_enableButtons.end()) {
135 // Remove button
136 auto [element, callback] = it->second;
137 Q_UNUSED(callback);
138 element["parentElement"].call<void>("removeChild", element);
139 m_enableButtons.erase(it);
140 }
141 }
142 {
143 auto a11yContainer = getA11yContainer(window);
144 auto describedByContainer =
145 getDescribedByContainer(window);
146 auto elementContainer = getElementContainer(window);
147 auto document = getDocument(a11yContainer);
148
149 // Remove all items by replacing the container
150 if (!describedByContainer.isUndefined()) {
151 a11yContainer.call<void>("removeChild", describedByContainer);
152 describedByContainer = document.call<emscripten::val>("createElement", std::string("div"));
153
154 a11yContainer.call<void>("appendChild", elementContainer);
155 a11yContainer.call<void>("appendChild", describedByContainer);
156 }
157 }
158}
159
160void QWasmAccessibility::enableAccessibility()
161{
162 // Enable accessibility. Remove all "enable" buttons and populate the
163 // accessibility tree for each window.
164
165 Q_ASSERT(!m_accessibilityEnabled);
166 m_accessibilityEnabled = true;
167 setActive(true);
168 for (const auto& [key, value] : m_enableButtons) {
169 const auto &[element, callback] = value;
170 Q_UNUSED(callback);
171 if (auto wasmWindow = QWasmWindow::fromWindow(key))
172 wasmWindow->onAccessibilityEnable();
173 onShowWindowImpl(key);
174 element["parentElement"].call<void>("removeChild", element);
175 }
176 m_enableButtons.clear();
177}
178
179bool QWasmAccessibility::isWindowNode(QAccessibleInterface *iface)
180{
181 return (iface && !getWindow(iface->parent()) && getWindow(iface));
182}
183
184emscripten::val QWasmAccessibility::getA11yContainer(QWindow *window)
185{
186 const auto wasmWindow = QWasmWindow::fromWindow(window);
187 if (!wasmWindow)
188 return emscripten::val::undefined();
189
190 auto a11yContainer = wasmWindow->a11yContainer();
191 if (a11yContainer["childElementCount"].as<unsigned>() == 2)
192 return a11yContainer;
193
194 Q_ASSERT(a11yContainer["childElementCount"].as<unsigned>() == 0);
195
196 const auto document = getDocument(a11yContainer);
197 if (document.isUndefined())
198 return emscripten::val::undefined();
199
200 auto elementContainer = document.call<emscripten::val>("createElement", std::string("div"));
201 auto describedByContainer = document.call<emscripten::val>("createElement", std::string("div"));
202
203 a11yContainer.call<void>("appendChild", elementContainer);
204 a11yContainer.call<void>("appendChild", describedByContainer);
205
206 return a11yContainer;
207}
208
209emscripten::val QWasmAccessibility::getA11yContainer(QAccessibleInterface *iface)
210{
211 return getA11yContainer(getWindow(iface));
212}
213
214emscripten::val QWasmAccessibility::getDescribedByContainer(QWindow *window)
215{
216 auto a11yContainer = getA11yContainer(window);
217 if (a11yContainer.isUndefined())
218 return emscripten ::val::undefined();
219
220 Q_ASSERT(a11yContainer["childElementCount"].as<unsigned>() == 2);
221 Q_ASSERT(!a11yContainer["children"][1].isUndefined());
222
223 return a11yContainer["children"][1];
224}
225
226emscripten::val QWasmAccessibility::getDescribedByContainer(QAccessibleInterface *iface)
227{
228 return getDescribedByContainer(getWindow(iface));
229}
230
231emscripten::val QWasmAccessibility::getElementContainer(QWindow *window)
232{
233 auto a11yContainer = getA11yContainer(window);
234 if (a11yContainer.isUndefined())
235 return emscripten ::val::undefined();
236
237 Q_ASSERT(a11yContainer["childElementCount"].as<unsigned>() == 2);
238 Q_ASSERT(!a11yContainer["children"][0].isUndefined());
239 return a11yContainer["children"][0];
240}
241
242emscripten::val QWasmAccessibility::getElementContainer(QAccessibleInterface *iface)
243{
244 // Here we skip QWindow nodes, as they are already present. Such nodes
245 // has a parent window of null.
246 //
247 // The next node should return the a11y container.
248 // Further nodes should return the element of the parent.
249 if (!getWindow(iface))
250 return emscripten::val::undefined();
251
252 if (isWindowNode(iface))
253 return emscripten::val::undefined();
254
255 if (isWindowNode(iface->parent()))
256 return getElementContainer(getWindow(iface->parent()));
257
258 // Regular node
259 return getHtmlElement(iface->parent());
260}
261
262QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
263{
264 if (!iface)
265 return nullptr;
266
267 QWindow *window = iface->window();
268 // this is needed to add tabs as the window is not available
269 if (!window && iface->parent())
270 window = iface->parent()->window();
271 return window;
272}
273
274emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
275{
276 if (container.isUndefined())
277 return emscripten::val::global("document");
278 return container["ownerDocument"];
279}
280
281emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface)
282{
283 return getDocument(getA11yContainer(iface));
284}
285
286void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr,
287 const std::string &val)
288{
289 if (val != "")
290 element.call<void>("setAttribute", attr, val);
291 else
292 element.call<void>("removeAttribute", attr);
293}
294
295void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr,
296 const char *val)
297{
298 setAttribute(element, attr, std::string(val));
299}
300
301void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr, bool val)
302{
303 if (val)
304 element.call<void>("setAttribute", attr, val);
305 else
306 element.call<void>("removeAttribute", attr);
307}
308
309void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property,
310 const std::string &val)
311{
312 element.set(property, val);
313}
314
315void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property, const char *val)
316{
317 setProperty(element, property, std::string(val));
318}
319
320void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property, bool val)
321{
322 element.set(property, val);
323}
324
325void QWasmAccessibility::setNamedAttribute(QAccessibleInterface *iface, const std::string &attribute, QAccessible::Text text)
326{
327 const emscripten::val element = getHtmlElement(iface);
328 setAttribute(element, attribute, iface->text(text).toStdString());
329}
330void QWasmAccessibility::setNamedProperty(QAccessibleInterface *iface, const std::string &property, QAccessible::Text text)
331{
332 const emscripten::val element = getHtmlElement(iface);
333 setProperty(element, property, iface->text(text).toStdString());
334}
335
336void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripten::val element, const char *eventType)
337{
338 element.set("data-qta11yinterface", reinterpret_cast<size_t>(iface));
339 element.call<void>("addEventListener", emscripten::val(eventType),
340 QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex),
341 true);
342}
343
344void QWasmAccessibility::sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType)
345{
346 if (iface->object()) {
347 QAccessibleEvent event(iface->object(), eventType);
348 handleUpdateByInterfaceRole(&event);
349 } else {
350 QAccessibleEvent event(iface, eventType);
351 handleUpdateByInterfaceRole(&event);
352 }
353}
354
355emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
356{
357 // Get the html container element for the interface; this depends on which
358 // QScreen it is on. If the interface is not on a screen yet we get an undefined
359 // container, and the code below handles that case as well.
360 emscripten::val container = getElementContainer(iface);
361
362 // Get the correct html document for the container, or fall back
363 // to the global document. TODO: Does using the correct document actually matter?
364 emscripten::val document = getDocument(container);
365
366 // Translate the Qt a11y elemen role into html element type + ARIA role.
367 // Here we can either create <div> elements with a spesific ARIA role,
368 // or create e.g. <button> elements which should have built-in accessibility.
369 emscripten::val element = [this, iface, document] {
370
371 emscripten::val element = emscripten::val::undefined();
372
373 switch (iface->role()) {
374
375 case QAccessible::Button: {
376 element = document.call<emscripten::val>("createElement", std::string("button"));
377 addEventListener(iface, element, "click");
378 } break;
379
380 case QAccessible::CheckBox: {
381 // QGroupBox uses both CheckBox, and a group with subwidgets
382 element = document.call<emscripten::val>("createElement", std::string("input"));
383 setAttribute(element, "type", "checkbox");
384 setAttribute(element, "checked", iface->state().checked);
385 setProperty(element, "indeterminate", iface->state().checkStateMixed);
386 addEventListener(iface, element, "change");
387 if (iface->childCount() > 0) {
388 auto checkbox = element;
389 auto label = document.call<emscripten::val>("createElement", std::string("span"));
390
391 const std::string id = QString::asprintf("lbid%p", iface).toStdString();
392 setAttribute(label, "id", id);
393
394 element = document.call<emscripten::val>("createElement", std::string("div"));
395 element.call<void>("appendChild", label);
396 element.call<void>("appendChild", checkbox);
397
398 setAttribute(element, "role", "group");
399 setAttribute(element, "aria-labelledby", id);
400 addEventListener(iface, checkbox, "focus");
401 }
402 } break;
403
404 case QAccessible::Switch: {
405 element = document.call<emscripten::val>("createElement", std::string("button"));
406 setAttribute(element, "type", "button");
407 setAttribute(element, "role", "switch");
408 if (iface->state().checked)
409 setAttribute(element, "aria-checked", "true");
410 else
411 setAttribute(element, "aria-checked", "false");
412 addEventListener(iface, element, "change");
413 } break;
414
415 case QAccessible::RadioButton: {
416 element = document.call<emscripten::val>("createElement", std::string("input"));
417 setAttribute(element, "type", "radio");
418 setAttribute(element, "checked", iface->state().checked);
419 setProperty(element, "name", "buttonGroup");
420 addEventListener(iface, element, "change");
421 } break;
422
423 case QAccessible::SpinBox:
424 case QAccessible::Slider: {
425 const auto minValue = iface->valueInterface()->minimumValue().toString().toStdString();
426 const auto maxValue = iface->valueInterface()->maximumValue().toString().toStdString();
427 const auto stepValue =
428 iface->valueInterface()->minimumStepSize().toString().toStdString();
429 const auto value = iface->valueInterface()->currentValue().toString().toStdString();
430 element = document.call<emscripten::val>("createElement", std::string("input"));
431 setAttribute(element,"type", "number");
432 setAttribute(element, "min", minValue);
433 setAttribute(element, "max", maxValue);
434 setAttribute(element, "step", stepValue);
435 setProperty(element, "value", value);
436 } break;
437
438 case QAccessible::PageTabList:{
439 element = document.call<emscripten::val>("createElement", std::string("div"));
440 setAttribute(element, "role", "tablist");
441 setHtmlElementOrientation(element, iface);
442
443 m_elements[iface] = element;
444
445 for (int i = 0; i < iface->childCount(); ++i)
446 createHtmlElement(iface->child(i));
447
448 } break;
449
450 case QAccessible::PageTab:{
451 const QString text = iface->text(QAccessible::Name);
452 element = document.call<emscripten::val>("createElement", std::string("button"));
453 setAttribute(element, "role", "tab");
454 setAttribute(element, "title", text.toStdString());
455 addEventListener(iface, element, "click");
456 } break;
457
458 case QAccessible::ScrollBar: {
459 const std::string valueString =
460 iface->valueInterface()->currentValue().toString().toStdString();
461 element = document.call<emscripten::val>("createElement", std::string("div"));
462 setAttribute(element, "role", "scrollbar");
463 setAttribute(element, "aria-valuenow", valueString);
464 setHtmlElementOrientation(element, iface);
465 addEventListener(iface, element, "change");
466 } break;
467
468 case QAccessible::StaticText: {
469 element = document.call<emscripten::val>("createElement", std::string("div"));
470 } break;
471 case QAccessible::Dialog: {
472 element = document.call<emscripten::val>("createElement", std::string("dialog"));
473 }break;
474 case QAccessible::ToolBar:{
475 const QString text = iface->text(QAccessible::Name);
476 element = document.call<emscripten::val>("createElement", std::string("div"));
477 setAttribute(element, "role", "toolbar");
478 setAttribute(element, "title", text.toStdString());
479 setHtmlElementOrientation(element, iface);
480 addEventListener(iface, element, "click");
481 }break;
482 case QAccessible::MenuItem:
483 case QAccessible::ButtonMenu: {
484 const QString text = iface->text(QAccessible::Name);
485 element = document.call<emscripten::val>("createElement", std::string("button"));
486 setAttribute(element, "role", "menuitem");
487 setAttribute(element, "title", text.toStdString());
488 addEventListener(iface, element, "click");
489 }break;
490 case QAccessible::MenuBar:
491 case QAccessible::PopupMenu: {
492 const QString text = iface->text(QAccessible::Name);
493 element = document.call<emscripten::val>("createElement", std::string("div"));
494 setAttribute(element, "role", "menubar");
495 setAttribute(element, "title", text.toStdString());
496 setHtmlElementOrientation(element, iface);
497 m_elements[iface] = element;
498
499 for (int i = 0; i < iface->childCount(); ++i) {
500 emscripten::val childElement = createHtmlElement(iface->child(i));
501 setAttribute(childElement, "aria-owns", text.toStdString());
502 }
503 }break;
504 case QAccessible::EditableText: {
505 element = document.call<emscripten::val>("createElement", std::string("input"));
506 setAttribute(element, "type", "text");
507 setAttribute(element, "contenteditable", "true");
508 setAttribute(element, "readonly", iface->state().readOnly);
509 setProperty(element, "inputMode", "text");
510 } break;
511 default:
512 qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role();
513 element = document.call<emscripten::val>("createElement", std::string("div"));
514 }
515
516 addEventListener(iface, element, "focus");
517 return element;
518
519 }();
520
521 m_elements[iface] = element;
522
523 setHtmlElementGeometry(iface);
524 setHtmlElementDisabled(iface);
525 setHtmlElementVisibility(iface, !iface->state().invisible);
526 handleIdentifierUpdate(iface);
527 handleDescriptionChanged(iface);
528 sendEvent(iface, QAccessible::NameChanged);
529
530 linkToParent(iface);
531 // Link in child elements
532 for (int i = 0; i < iface->childCount(); ++i) {
533 if (!getHtmlElement(iface->child(i)).isUndefined())
534 linkToParent(iface->child(i));
535 }
536
537 return element;
538}
539
540void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
541{
542 Q_UNUSED(iface);
543 qCDebug(lcQpaAccessibility) << "TODO destroyHtmlElement";
544}
545
546emscripten::val QWasmAccessibility::getHtmlElement(QAccessibleInterface *iface)
547{
548 auto it = m_elements.find(iface);
549 if (it != m_elements.end())
550 return it.value();
551
552 return emscripten::val::undefined();
553}
554
555void QWasmAccessibility::repairLinks(QAccessibleInterface *iface)
556{
557 // relink any children that are linked to the wrong parent,
558 // This can be caused by a missing ParentChanged event.
559 bool moved = false;
560 for (int i = 0; i < iface->childCount(); ++i) {
561 const auto elementI = getHtmlElement(iface->child(i));
562 const auto containerI = getElementContainer(iface->child(i));
563
564 if (!elementI.isUndefined() &&
565 !containerI.isUndefined() &&
566 !elementI["parentElement"].isUndefined() &&
567 !elementI["parentElement"].isNull() &&
568 elementI["parentElement"] != containerI) {
569 moved = true;
570 break;
571 }
572 }
573 if (moved) {
574 for (int i = 0; i < iface->childCount(); ++i) {
575 const auto elementI = getHtmlElement(iface->child(i));
576 const auto containerI = getElementContainer(iface->child(i));
577 if (!elementI.isUndefined() && !containerI.isUndefined())
578 containerI.call<void>("appendChild", elementI);
579 }
580 }
581}
582
583void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
584{
585 emscripten::val element = getHtmlElement(iface);
586 emscripten::val container = getElementContainer(iface);
587
588 if (container.isUndefined() || element.isUndefined())
589 return;
590
591 // Make sure that we don't change the focused element
592 const auto activeElementBefore = emscripten::val::take_ownership(
593 getActiveElement_js(emscripten::val::undefined().as_handle()));
594
595
596 repairLinks(iface->parent());
597
598 emscripten::val next = emscripten::val::undefined();
599 const int thisIndex = iface->parent()->indexOfChild(iface);
600 if (thisIndex >= 0) {
601 Q_ASSERT(thisIndex < iface->parent()->childCount());
602 for (int i = thisIndex + 1; i < iface->parent()->childCount(); ++i) {
603 const auto elementI = getHtmlElement(iface->parent()->child(i));
604 if (!elementI.isUndefined() &&
605 elementI["parentElement"] == container) {
606 next = elementI;
607 break;
608 }
609 }
610 if (next.isUndefined())
611 container.call<void>("appendChild", element);
612 else
613 container.call<void>("insertBefore", element, next);
614 }
615 const auto activeElementAfter = emscripten::val::take_ownership(
616 getActiveElement_js(emscripten::val::undefined().as_handle()));
617 if (activeElementBefore != activeElementAfter) {
618 if (!activeElementBefore.isUndefined() && !activeElementBefore.isNull())
619 activeElementBefore.call<void>("focus");
620 }
621}
622
623void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible)
624{
625 emscripten::val element = getHtmlElement(iface);
626 setAttribute(element, "aria-hidden", !visible);
627}
628
629void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
630{
631 const emscripten::val element = getHtmlElement(iface);
632
633 QRect windowGeometry = iface->rect();
634 if (iface->parent()) {
635 // Both iface and iface->parent returns geometry in screen coordinates
636 // We only want the relative coordinates, so the coordinate system does
637 // not matter as long as it is the same.
638 const QRect parentRect = iface->parent()->rect();
639 const QRect thisRect = iface->rect();
640 const QRect result(thisRect.topLeft() - parentRect.topLeft(), thisRect.size());
641 windowGeometry = result;
642 } else {
643 // Elements without a parent are not a part of the a11y tree, and don't
644 // have meaningful geometry.
645 Q_ASSERT(!getWindow(iface));
646 }
647 setHtmlElementGeometry(element, windowGeometry);
648}
649
650void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
651{
652 // Position the element using "position: absolute" in order to place
653 // it under the corresponding Qt element in the screen.
654 emscripten::val style = element["style"];
655 style.set("position", std::string("absolute"));
656 style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
657 // screen element, but isn't
658 style.set("left", std::to_string(geometry.x()) + "px");
659 style.set("top", std::to_string(geometry.y()) + "px");
660 style.set("width", std::to_string(geometry.width()) + "px");
661 style.set("height", std::to_string(geometry.height()) + "px");
662}
663
664void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
665{
666 const auto element = getHtmlElement(iface);
667 element.call<void>("focus");
668}
669
670void QWasmAccessibility::setHtmlElementDisabled(QAccessibleInterface *iface)
671{
672 auto element = getHtmlElement(iface);
673 setAttribute(element, "aria-disabled", iface->state().disabled);
674}
675
676void QWasmAccessibility::setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface)
677{
678 Q_ASSERT(iface);
679 if (QAccessibleAttributesInterface *attributesIface = iface->attributesInterface()) {
680 const QVariant orientationVariant =
681 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
682 if (orientationVariant.isValid()) {
683 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
684 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
685 const std::string value = orientation == Qt::Horizontal ? "horizontal" : "vertical";
686 setAttribute(element, "aria-orientation", value);
687 }
688 }
689}
690
691void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
692{
693 switch (event->type()) {
694 case QAccessible::NameChanged: {
695 // StaticText is a div
696 setNamedProperty(event->accessibleInterface(), "innerText", QAccessible::Name);
697 } break;
698 default:
699 qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
700 break;
701 }
702}
703
704void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
705{
706 switch (event->type()) {
707 case QAccessible::StateChanged: {
708 auto iface = event->accessibleInterface();
709 auto element = getHtmlElement(iface);
710 setAttribute(element, "readonly", iface->state().readOnly);
711 if (iface->state().passwordEdit)
712 setProperty(element, "type", "password");
713 else
714 setProperty(element, "type", "text");
715 } break;
716 case QAccessible::NameChanged: {
717 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
718 } break;
719 case QAccessible::ObjectShow:
720 case QAccessible::Focus: {
721 auto iface = event->accessibleInterface();
722 auto element = getHtmlElement(iface);
723 if (!element.isUndefined()) {
724 setAttribute(element, "readonly", iface->state().readOnly);
725 if (iface->state().passwordEdit)
726 setProperty(element, "type", "password");
727 else
728 setProperty(element, "type", "text");
729 }
730 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
731 } break;
732 case QAccessible::TextRemoved:
733 case QAccessible::TextInserted:
734 case QAccessible::TextCaretMoved: {
735 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
736 } break;
737 default:
738 qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
739 break;
740 }
741}
742
743void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
744{
745 if (event["target"].isNull() || event["target"].isUndefined())
746 return;
747
748 if (event["target"]["data-qta11yinterface"].isNull() || event["target"]["data-qta11yinterface"].isUndefined())
749 return;
750
751 auto iface = reinterpret_cast<QAccessibleInterface *>(event["target"]["data-qta11yinterface"].as<size_t>());
752 if (m_elements.find(iface) == m_elements.end())
753 return;
754
755 const QString eventType = QString::fromStdString(event["type"].as<std::string>());
756 const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
757
758 if (eventType == "focus") {
759 if (actionNames.contains(QAccessibleActionInterface::setFocusAction()))
760 iface->actionInterface()->doAction(QAccessibleActionInterface::setFocusAction());
761 } else if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
762 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
763 } else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) {
764 iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
765 }
766}
767
768void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
769{
770 switch (event->type()) {
771 case QAccessible::Focus:
772 case QAccessible::NameChanged: {
773 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
774 } break;
775 default:
776 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
777 break;
778 }
779}
780
781void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
782{
783 switch (event->type()) {
784 case QAccessible::Focus:
785 case QAccessible::NameChanged: {
786 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
787 } break;
788 case QAccessible::StateChanged: {
789 QAccessibleInterface *accessible = event->accessibleInterface();
790 const emscripten::val element = getHtmlElement(accessible);
791 setAttribute(element, "checked", accessible->state().checked);
792 setProperty(element, "indeterminate", accessible->state().checkStateMixed);
793 } break;
794 default:
795 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
796 break;
797 }
798}
799
800void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event)
801{
802 QAccessibleInterface *iface = event->accessibleInterface();
803 const emscripten::val parent = getHtmlElement(iface);
804 const emscripten::val label = parent["children"][0];
805 const emscripten::val checkbox = parent["children"][1];
806
807 switch (event->type()) {
808 case QAccessible::Focus:
809 case QAccessible::NameChanged: {
810 setProperty(label, "innerText", iface->text(QAccessible::Name).toStdString());
811 setAttribute(checkbox, "aria-label", iface->text(QAccessible::Name).toStdString());
812 } break;
813 case QAccessible::StateChanged: {
814 setAttribute(checkbox, "checked", iface->state().checked);
815 setProperty(checkbox, "indeterminate", iface->state().checkStateMixed);
816 } break;
817 default:
818 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
819 break;
820 }
821}
822
823void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
824{
825 switch (event->type()) {
826 case QAccessible::Focus:
827 case QAccessible::NameChanged: {
828 /* A switch is like a button in this regard */
829 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
830 } break;
831 case QAccessible::StateChanged: {
832 QAccessibleInterface *accessible = event->accessibleInterface();
833 const emscripten::val element = getHtmlElement(accessible);
834 if (accessible->state().checked)
835 setAttribute(element, "aria-checked", "true");
836 else
837 setAttribute(element, "aria-checked", "false");
838 } break;
839 default:
840 qCDebug(lcQpaAccessibility) << "TODO: implement handleSwitchUpdate for event" << event->type();
841 break;
842 }
843}
844
845void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
846{
847 QAccessibleInterface *iface = event->accessibleInterface();
848 QString text = iface->text(QAccessible::Name);
849 QString desc = iface->text(QAccessible::Description);
850 switch (event->type()) {
851 case QAccessible::NameChanged:
852 case QAccessible::StateChanged:{
853 const emscripten::val element = getHtmlElement(iface);
854 setAttribute(element, "title", text.toStdString());
855 } break;
856 default:
857 qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type();
858 break;
859 }
860}
861void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
862{
863 QAccessibleInterface *iface = event->accessibleInterface();
864 QString text = iface->text(QAccessible::Name);
865 QString desc = iface->text(QAccessible::Description);
866 switch (event->type()) {
867 case QAccessible::Focus:
868 case QAccessible::NameChanged:
869 case QAccessible::MenuStart ://"TODO: To implement later
870 case QAccessible::StateChanged:{
871 const emscripten::val element = getHtmlElement(iface);
872 setAttribute(element, "title", text.toStdString());
873 } break;
874 case QAccessible::PopupMenuStart: {
875 if (iface->childCount() > 0) {
876 const auto childElement = getHtmlElement(iface->child(0));
877 childElement.call<void>("focus");
878 }
879 } break;
880 default:
881 qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type();
882 break;
883 }
884}
885void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
886
887 switch (event->type()) {
888 case QAccessible::NameChanged:
889 case QAccessible::Focus:
890 case QAccessible::DialogStart:
891 case QAccessible::StateChanged: {
892 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
893 } break;
894 default:
895 qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
896 break;
897 }
898}
899
900void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
901{
902 if (!iface)
903 return;
904
905 // We ignore toplevel windows which is categorized
906 // by getWindow(iface->parent()) != getWindow(iface)
907 const QWindow *window1 = getWindow(iface);
908 const QWindow *window0 = (iface->parent()) ? getWindow(iface->parent()) : nullptr;
909
910 if (window1 && window0 == window1) {
911 // Create html element for the interface, sync up properties.
912 bool exists = !getHtmlElement(iface).isUndefined();
913 if (!exists)
914 exists = !createHtmlElement(iface).isUndefined();
915
916 if (exists) {
917 linkToParent(iface);
918 setHtmlElementVisibility(iface, !iface->state().invisible);
919 setHtmlElementGeometry(iface);
920 setHtmlElementDisabled(iface);
921 handleIdentifierUpdate(iface);
922 handleDescriptionChanged(iface);
923 sendEvent(iface, QAccessible::NameChanged);
924 }
925 }
926 for (int i = 0; i < iface->childCount(); ++i)
927 populateAccessibilityTree(iface->child(i));
928}
929
930void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
931{
932 switch (event->type()) {
933 case QAccessible::Focus:
934 case QAccessible::NameChanged: {
935 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
936 } break;
937 case QAccessible::StateChanged: {
938 QAccessibleInterface *accessible = event->accessibleInterface();
939 const emscripten::val element = getHtmlElement(accessible);
940 setAttribute(element, "checked", accessible->state().checked);
941 } break;
942 default:
943 qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type();
944 break;
945 }
946}
947
948void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
949{
950 switch (event->type()) {
951 case QAccessible::ObjectCreated:
952 case QAccessible::StateChanged: {
953 } break;
954 case QAccessible::Focus:
955 case QAccessible::NameChanged: {
956 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
957 } break;
958 case QAccessible::ValueChanged: {
959 QAccessibleInterface *accessible = event->accessibleInterface();
960 const emscripten::val element = getHtmlElement(accessible);
961 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
962 setProperty(element, "value", valueString);
963 } break;
964 default:
965 qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
966 break;
967 }
968}
969
970void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
971{
972 switch (event->type()) {
973 case QAccessible::ObjectCreated:
974 case QAccessible::StateChanged: {
975 } break;
976 case QAccessible::Focus:
977 case QAccessible::NameChanged: {
978 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
979 } break;
980 case QAccessible::ValueChanged: {
981 QAccessibleInterface *accessible = event->accessibleInterface();
982 const emscripten::val element = getHtmlElement(accessible);
983 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
984 setProperty(element, "value", valueString);
985 } break;
986 default:
987 qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
988 break;
989 }
990}
991
992void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
993{
994 switch (event->type()) {
995 case QAccessible::Focus:
996 case QAccessible::NameChanged: {
997 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
998 } break;
999 case QAccessible::ValueChanged: {
1000 QAccessibleInterface *accessible = event->accessibleInterface();
1001 const emscripten::val element = getHtmlElement(accessible);
1002 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1003 setAttribute(element, "aria-valuenow", valueString);
1004 } break;
1005 default:
1006 qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
1007 break;
1008 }
1009
1010}
1011
1012void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
1013{
1014 switch (event->type()) {
1015 case QAccessible::NameChanged: {
1016 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1017 } break;
1018 case QAccessible::Focus: {
1019 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1020 } break;
1021 default:
1022 qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
1023 break;
1024 }
1025}
1026
1027void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
1028{
1029 switch (event->type()) {
1030 case QAccessible::NameChanged: {
1031 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1032 } break;
1033 case QAccessible::Focus: {
1034 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1035 } break;
1036 default:
1037 qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
1038 break;
1039 }
1040}
1041
1042void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
1043{
1044 const emscripten::val element = getHtmlElement(iface);
1045 QString id = iface->text(QAccessible::Identifier).replace(" ", "_");
1046 if (id.isEmpty() && iface->role() == QAccessible::PageTabList) {
1047 std::ostringstream oss;
1048 oss << "tabList_0x" << (void *)iface;
1049 id = QString::fromUtf8(oss.str());
1050 }
1051
1052 setAttribute(element, "id", id.toStdString());
1053 if (!id.isEmpty()) {
1054 if (iface->role() == QAccessible::PageTabList) {
1055 for (int i = 0; i < iface->childCount(); ++i) {
1056 const auto child = getHtmlElement(iface->child(i));
1057 setAttribute(child, "aria-owns", id.toStdString());
1058 }
1059 }
1060 }
1061}
1062
1063void QWasmAccessibility::handleDescriptionChanged(QAccessibleInterface *iface)
1064{
1065 const auto desc = iface->text(QAccessible::Description).toStdString();
1066 auto element = getHtmlElement(iface);
1067 auto container = getDescribedByContainer(iface);
1068 if (!container.isUndefined()) {
1069 std::ostringstream oss;
1070 oss << "dbid_" << (void *)iface;
1071 auto id = oss.str();
1072
1073 auto describedBy = container.call<emscripten::val>("querySelector", "#" + std::string(id));
1074 if (desc.empty()) {
1075 if (!describedBy.isUndefined() && !describedBy.isNull()) {
1076 container.call<void>("removeChild", describedBy);
1077 }
1078 setAttribute(element, "aria-describedby", "");
1079 } else {
1080 if (describedBy.isUndefined() || describedBy.isNull()) {
1081 auto document = getDocument(container);
1082 describedBy = document.call<emscripten::val>("createElement", std::string("p"));
1083
1084 container.call<void>("appendChild", describedBy);
1085 }
1086 setAttribute(describedBy, "id", id);
1087 setAttribute(describedBy, "aria-hidden", true);
1088 setAttribute(element, "aria-describedby", id);
1089 setProperty(describedBy, "innerText", desc);
1090 }
1091 }
1092}
1093
1094void QWasmAccessibility::createObject(QAccessibleInterface *iface)
1095{
1096 if (getHtmlElement(iface).isUndefined())
1097 createHtmlElement(iface);
1098}
1099
1100void QWasmAccessibility::removeObject(QAccessibleInterface *iface)
1101{
1102 // Do not dereference the object pointer. it might be invalid.
1103 // Do not dereference the iface either, it refers to the object.
1104 // Note: we may remove children, making them have parentElement undefined
1105 // so we need to check for parentElement here. We do assume that removeObject
1106 // is called on all objects, just not in any predefined order.
1107 const auto it = m_elements.find(iface);
1108 if (it != m_elements.end()) {
1109 auto element = it.value();
1110 auto container = getDescribedByContainer(iface);
1111 if (!container.isUndefined()) {
1112 std::ostringstream oss;
1113 oss << "dbid_" << (void *)iface;
1114 auto id = oss.str();
1115 auto describedBy = container.call<emscripten::val>("querySelector", "#" + std::string(id));
1116 if (!describedBy.isUndefined() && !describedBy.isNull() &&
1117 !describedBy["parentElement"].isUndefined() && !describedBy["parentElement"].isNull())
1118 describedBy["parentElement"].call<void>("removeChild", describedBy);
1119 }
1120 if (!element["parentElement"].isUndefined() && !element["parentElement"].isNull())
1121 element["parentElement"].call<void>("removeChild", element);
1122 m_elements.erase(it);
1123 }
1124}
1125
1126void QWasmAccessibility::unlinkParentForChildren(QAccessibleInterface *iface)
1127{
1128 auto element = getHtmlElement(iface);
1129 if (!element.isUndefined()) {
1130 auto oldContainer = element["parentElement"];
1131 auto newContainer = getElementContainer(iface);
1132 if (!oldContainer.isUndefined() &&
1133 !oldContainer.isNull() &&
1134 oldContainer != newContainer) {
1135 oldContainer.call<void>("removeChild", element);
1136 }
1137 }
1138 for (int i = 0; i < iface->childCount(); ++i)
1139 unlinkParentForChildren(iface->child(i));
1140}
1141
1142void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
1143{
1144 auto element = getHtmlElement(iface);
1145 if (!element.isUndefined()) {
1146 if (element["parentElement"].isUndefined() ||
1147 element["parentElement"].isNull()) {
1148 linkToParent(iface);
1149 }
1150 }
1151 for (int i = 0; i < iface->childCount(); ++i)
1152 relinkParentForChildren(iface->child(i));
1153}
1154
1155void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
1156{
1157 if (handleUpdateByEventType(event))
1158 handleUpdateByInterfaceRole(event);
1159}
1160
1161bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
1162{
1163 if (!m_accessibilityEnabled)
1164 return false;
1165
1166 QAccessibleInterface *iface = event->accessibleInterface();
1167 if (!iface) {
1168 qWarning() << "handleUpdateByEventType with null a11y interface" << event->type() << event->object();
1169 return false;
1170 }
1171
1172 // Handle event types that creates/removes objects.
1173 switch (event->type()) {
1174 case QAccessible::ObjectCreated:
1175 // Do nothing, there are too many changes to the interface
1176 // before ObjectShow is called
1177 return false;
1178
1179 case QAccessible::ObjectDestroyed:
1180 // The object might be under destruction, and the interface is not valid
1181 // but we can look at the pointer,
1182 removeObject(iface);
1183 return false;
1184
1185 case QAccessible::ObjectShow: // We do not get ObjectCreated from widgets, we get ObjectShow
1186 createObject(iface);
1187 break;
1188
1189 case QAccessible::ParentChanged:
1190 unlinkParentForChildren(iface);
1191 relinkParentForChildren(iface);
1192 break;
1193
1194 default:
1195 break;
1196 };
1197
1198 if (getHtmlElement(iface).isUndefined())
1199 return false;
1200
1201 // Handle some common event types. See
1202 // https://doc.qt.io/qt-5/qaccessible.html#Event-enum
1203 switch (event->type()) {
1204 case QAccessible::StateChanged: {
1205 QAccessibleStateChangeEvent *stateChangeEvent = (QAccessibleStateChangeEvent *)event;
1206 if (stateChangeEvent->changedStates().disabled)
1207 setHtmlElementDisabled(iface);
1208 } break;
1209
1210 case QAccessible::DescriptionChanged:
1211 handleDescriptionChanged(iface);
1212 return false;
1213
1214 case QAccessible::Focus:
1215 // We do not get all callbacks for the geometry
1216 // hence we update here as well.
1217 setHtmlElementGeometry(iface);
1218 setHtmlElementFocus(iface);
1219 break;
1220
1221 case QAccessible::IdentifierChanged:
1222 handleIdentifierUpdate(iface);
1223 return false;
1224
1225 case QAccessible::ObjectShow:
1226 linkToParent(iface);
1227 setHtmlElementVisibility(iface, true);
1228
1229 // Sync up properties on show;
1230 setHtmlElementGeometry(iface);
1231 sendEvent(iface, QAccessible::NameChanged);
1232 break;
1233
1234 case QAccessible::ObjectHide:
1235 linkToParent(iface);
1236 setHtmlElementVisibility(iface, false);
1237 return false;
1238
1239 case QAccessible::LocationChanged:
1240 setHtmlElementGeometry(iface);
1241 return false;
1242
1243 // TODO: maybe handle more types here
1244 default:
1245 break;
1246 };
1247
1248 return true;
1249}
1250
1251void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
1252{
1253 if (!m_accessibilityEnabled)
1254 return;
1255
1256 QAccessibleInterface *iface = event->accessibleInterface();
1257 if (!iface) {
1258 qWarning() << "handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
1259 return;
1260 }
1261
1262 // Switch on interface role, see
1263 // https://doc.qt.io/qt-5/qaccessibleinterface.html#role
1264 switch (iface->role()) {
1265 case QAccessible::StaticText:
1266 handleStaticTextUpdate(event);
1267 break;
1268 case QAccessible::Button:
1269 handleButtonUpdate(event);
1270 break;
1271 case QAccessible::CheckBox:
1272 if (iface->childCount() > 0)
1273 handleGroupBoxUpdate(event);
1274 else
1275 handleCheckBoxUpdate(event);
1276 break;
1277 case QAccessible::Switch:
1278 handleSwitchUpdate(event);
1279 break;
1280 case QAccessible::EditableText:
1281 handleLineEditUpdate(event);
1282 break;
1283 case QAccessible::Dialog:
1284 handleDialogUpdate(event);
1285 break;
1286 case QAccessible::MenuItem:
1287 case QAccessible::MenuBar:
1288 case QAccessible::PopupMenu:
1289 handleMenuUpdate(event);
1290 break;
1291 case QAccessible::ToolBar:
1292 case QAccessible::ButtonMenu:
1293 handleToolUpdate(event);
1294 case QAccessible::RadioButton:
1295 handleRadioButtonUpdate(event);
1296 break;
1297 case QAccessible::SpinBox:
1298 handleSpinBoxUpdate(event);
1299 break;
1300 case QAccessible::Slider:
1301 handleSliderUpdate(event);
1302 break;
1303 case QAccessible::PageTab:
1304 handlePageTabUpdate(event);
1305 break;
1306 case QAccessible::PageTabList:
1307 handlePageTabListUpdate(event);
1308 break;
1309 case QAccessible::ScrollBar:
1310 handleScrollBarUpdate(event);
1311 break;
1312 default:
1313 qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role();
1314 };
1315}
1316
1317void QWasmAccessibility::setRootObject(QObject *root)
1318{
1319 m_rootObject = root;
1320}
1321
1322void QWasmAccessibility::initialize()
1323{
1324
1325}
1326
1327void QWasmAccessibility::cleanup()
1328{
1329
1330}
1331
1332#endif // QT_CONFIG(accessibility)
void QWasmAccessibilityEnable()