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::Grouping:
381 case QAccessible::CheckBox: {
382 // Three cases:
383 // 1) role=CheckBox, childCount() == 0 -> Checkbox
384 // 2) role=CheckBox, childCount() > 0 -> GroupBox w/checkbox
385 // 3) role=Grouping -> GroupBox w/label
386
387 emscripten::val checkbox = emscripten::val::undefined();
388 if (iface->role() == QAccessible::CheckBox) {
389 checkbox = document.call<emscripten::val>("createElement", std::string("input"));
390 setAttribute(checkbox, "type", "checkbox");
391 setAttribute(checkbox, "checked", iface->state().checked);
392 setProperty(checkbox, "indeterminate", iface->state().checkStateMixed);
393 addEventListener(iface, checkbox, "change");
394 }
395
396 if (iface->childCount() > 0 || iface->role() == QAccessible::Grouping) {
397 auto label = document.call<emscripten::val>("createElement", std::string("span"));
398
399 const std::string id = QString::asprintf("lbid%p", iface).toStdString();
400 setAttribute(label, "id", id);
401
402 element = document.call<emscripten::val>("createElement", std::string("div"));
403 element.call<void>("appendChild", label);
404
405 setAttribute(element, "role", "group");
406 setAttribute(element, "aria-labelledby", id);
407
408 if (!checkbox.isUndefined()) {
409 element.call<void>("appendChild", checkbox);
410 addEventListener(iface, checkbox, "focus");
411 }
412 } else {
413 element = checkbox;
414 }
415 } break;
416
417 case QAccessible::Switch: {
418 element = document.call<emscripten::val>("createElement", std::string("button"));
419 setAttribute(element, "type", "button");
420 setAttribute(element, "role", "switch");
421 if (iface->state().checked)
422 setAttribute(element, "aria-checked", "true");
423 else
424 setAttribute(element, "aria-checked", "false");
425 addEventListener(iface, element, "change");
426 } break;
427
428 case QAccessible::RadioButton: {
429 element = document.call<emscripten::val>("createElement", std::string("input"));
430 setAttribute(element, "type", "radio");
431 setAttribute(element, "checked", iface->state().checked);
432 setProperty(element, "name", "buttonGroup");
433 addEventListener(iface, element, "change");
434 } break;
435
436 case QAccessible::SpinBox:
437 case QAccessible::Slider: {
438 const auto minValue = iface->valueInterface()->minimumValue().toString().toStdString();
439 const auto maxValue = iface->valueInterface()->maximumValue().toString().toStdString();
440 const auto stepValue =
441 iface->valueInterface()->minimumStepSize().toString().toStdString();
442 const auto value = iface->valueInterface()->currentValue().toString().toStdString();
443 element = document.call<emscripten::val>("createElement", std::string("input"));
444 setAttribute(element,"type", "number");
445 setAttribute(element, "min", minValue);
446 setAttribute(element, "max", maxValue);
447 setAttribute(element, "step", stepValue);
448 setProperty(element, "value", value);
449 } break;
450
451 case QAccessible::PageTabList:{
452 element = document.call<emscripten::val>("createElement", std::string("div"));
453 setAttribute(element, "role", "tablist");
454 setHtmlElementOrientation(element, iface);
455
456 m_elements[iface] = element;
457
458 for (int i = 0; i < iface->childCount(); ++i)
459 createHtmlElement(iface->child(i));
460
461 } break;
462
463 case QAccessible::PageTab:{
464 const QString text = iface->text(QAccessible::Name);
465 element = document.call<emscripten::val>("createElement", std::string("button"));
466 setAttribute(element, "role", "tab");
467 setAttribute(element, "title", text.toStdString());
468 addEventListener(iface, element, "click");
469 } break;
470
471 case QAccessible::ScrollBar: {
472 const std::string valueString =
473 iface->valueInterface()->currentValue().toString().toStdString();
474 element = document.call<emscripten::val>("createElement", std::string("div"));
475 setAttribute(element, "role", "scrollbar");
476 setAttribute(element, "aria-valuenow", valueString);
477 setHtmlElementOrientation(element, iface);
478 addEventListener(iface, element, "change");
479 } break;
480
481 case QAccessible::StaticText: {
482 element = document.call<emscripten::val>("createElement", std::string("div"));
483 } break;
484 case QAccessible::Dialog: {
485 element = document.call<emscripten::val>("createElement", std::string("dialog"));
486 }break;
487 case QAccessible::ToolBar:{
488 const QString text = iface->text(QAccessible::Name);
489 element = document.call<emscripten::val>("createElement", std::string("div"));
490 setAttribute(element, "role", "toolbar");
491 setAttribute(element, "title", text.toStdString());
492 setHtmlElementOrientation(element, iface);
493 addEventListener(iface, element, "click");
494 }break;
495 case QAccessible::MenuItem:
496 case QAccessible::ButtonMenu: {
497 const QString text = iface->text(QAccessible::Name);
498 element = document.call<emscripten::val>("createElement", std::string("button"));
499 setAttribute(element, "role", "menuitem");
500 setAttribute(element, "title", text.toStdString());
501 addEventListener(iface, element, "click");
502 }break;
503 case QAccessible::MenuBar:
504 case QAccessible::PopupMenu: {
505 const QString text = iface->text(QAccessible::Name);
506 element = document.call<emscripten::val>("createElement", std::string("div"));
507 setAttribute(element, "role", "menubar");
508 setAttribute(element, "title", text.toStdString());
509 setHtmlElementOrientation(element, iface);
510 m_elements[iface] = element;
511
512 for (int i = 0; i < iface->childCount(); ++i) {
513 emscripten::val childElement = createHtmlElement(iface->child(i));
514 setAttribute(childElement, "aria-owns", text.toStdString());
515 }
516 }break;
517 case QAccessible::EditableText: {
518 element = document.call<emscripten::val>("createElement", std::string("input"));
519 setAttribute(element, "type", "text");
520 setAttribute(element, "contenteditable", "true");
521 setAttribute(element, "readonly", iface->state().readOnly);
522 setProperty(element, "inputMode", "text");
523 } break;
524 default:
525 qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role();
526 element = document.call<emscripten::val>("createElement", std::string("div"));
527 }
528
529 addEventListener(iface, element, "focus");
530 return element;
531
532 }();
533
534 m_elements[iface] = element;
535
536 setHtmlElementGeometry(iface);
537 setHtmlElementDisabled(iface);
538 setHtmlElementVisibility(iface, !iface->state().invisible);
539 handleIdentifierUpdate(iface);
540 handleDescriptionChanged(iface);
541 sendEvent(iface, QAccessible::NameChanged);
542
543 linkToParent(iface);
544 // Link in child elements
545 for (int i = 0; i < iface->childCount(); ++i) {
546 if (!getHtmlElement(iface->child(i)).isUndefined())
547 linkToParent(iface->child(i));
548 }
549
550 return element;
551}
552
553void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
554{
555 Q_UNUSED(iface);
556 qCDebug(lcQpaAccessibility) << "TODO destroyHtmlElement";
557}
558
559emscripten::val QWasmAccessibility::getHtmlElement(QAccessibleInterface *iface)
560{
561 auto it = m_elements.find(iface);
562 if (it != m_elements.end())
563 return it.value();
564
565 return emscripten::val::undefined();
566}
567
568void QWasmAccessibility::repairLinks(QAccessibleInterface *iface)
569{
570 // relink any children that are linked to the wrong parent,
571 // This can be caused by a missing ParentChanged event.
572 bool moved = false;
573 for (int i = 0; i < iface->childCount(); ++i) {
574 const auto elementI = getHtmlElement(iface->child(i));
575 const auto containerI = getElementContainer(iface->child(i));
576
577 if (!elementI.isUndefined() &&
578 !containerI.isUndefined() &&
579 !elementI["parentElement"].isUndefined() &&
580 !elementI["parentElement"].isNull() &&
581 elementI["parentElement"] != containerI) {
582 moved = true;
583 break;
584 }
585 }
586 if (moved) {
587 for (int i = 0; i < iface->childCount(); ++i) {
588 const auto elementI = getHtmlElement(iface->child(i));
589 const auto containerI = getElementContainer(iface->child(i));
590 if (!elementI.isUndefined() && !containerI.isUndefined())
591 containerI.call<void>("appendChild", elementI);
592 }
593 }
594}
595
596void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
597{
598 emscripten::val element = getHtmlElement(iface);
599 emscripten::val container = getElementContainer(iface);
600
601 if (container.isUndefined() || element.isUndefined())
602 return;
603
604 // Make sure that we don't change the focused element
605 const auto activeElementBefore = emscripten::val::take_ownership(
606 getActiveElement_js(emscripten::val::undefined().as_handle()));
607
608
609 repairLinks(iface->parent());
610
611 emscripten::val next = emscripten::val::undefined();
612 const int thisIndex = iface->parent()->indexOfChild(iface);
613 if (thisIndex >= 0) {
614 Q_ASSERT(thisIndex < iface->parent()->childCount());
615 for (int i = thisIndex + 1; i < iface->parent()->childCount(); ++i) {
616 const auto elementI = getHtmlElement(iface->parent()->child(i));
617 if (!elementI.isUndefined() &&
618 elementI["parentElement"] == container) {
619 next = elementI;
620 break;
621 }
622 }
623 if (next.isUndefined())
624 container.call<void>("appendChild", element);
625 else
626 container.call<void>("insertBefore", element, next);
627 }
628 const auto activeElementAfter = emscripten::val::take_ownership(
629 getActiveElement_js(emscripten::val::undefined().as_handle()));
630 if (activeElementBefore != activeElementAfter) {
631 if (!activeElementBefore.isUndefined() && !activeElementBefore.isNull())
632 activeElementBefore.call<void>("focus");
633 }
634}
635
636void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible)
637{
638 emscripten::val element = getHtmlElement(iface);
639 setAttribute(element, "aria-hidden", !visible);
640}
641
642void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
643{
644 const emscripten::val element = getHtmlElement(iface);
645
646 QRect windowGeometry = iface->rect();
647 if (iface->parent()) {
648 // Both iface and iface->parent returns geometry in screen coordinates
649 // We only want the relative coordinates, so the coordinate system does
650 // not matter as long as it is the same.
651 const QRect parentRect = iface->parent()->rect();
652 const QRect thisRect = iface->rect();
653 const QRect result(thisRect.topLeft() - parentRect.topLeft(), thisRect.size());
654 windowGeometry = result;
655 } else {
656 // Elements without a parent are not a part of the a11y tree, and don't
657 // have meaningful geometry.
658 Q_ASSERT(!getWindow(iface));
659 }
660 setHtmlElementGeometry(element, windowGeometry);
661}
662
663void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
664{
665 // Position the element using "position: absolute" in order to place
666 // it under the corresponding Qt element in the screen.
667 emscripten::val style = element["style"];
668 style.set("position", std::string("absolute"));
669 style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
670 // screen element, but isn't
671 style.set("left", std::to_string(geometry.x()) + "px");
672 style.set("top", std::to_string(geometry.y()) + "px");
673 style.set("width", std::to_string(geometry.width()) + "px");
674 style.set("height", std::to_string(geometry.height()) + "px");
675}
676
677void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
678{
679 const auto element = getHtmlElement(iface);
680 element.call<void>("focus");
681}
682
683void QWasmAccessibility::setHtmlElementDisabled(QAccessibleInterface *iface)
684{
685 auto element = getHtmlElement(iface);
686 setAttribute(element, "aria-disabled", iface->state().disabled);
687}
688
689void QWasmAccessibility::setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface)
690{
691 Q_ASSERT(iface);
692 if (QAccessibleAttributesInterface *attributesIface = iface->attributesInterface()) {
693 const QVariant orientationVariant =
694 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
695 if (orientationVariant.isValid()) {
696 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
697 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
698 const std::string value = orientation == Qt::Horizontal ? "horizontal" : "vertical";
699 setAttribute(element, "aria-orientation", value);
700 }
701 }
702}
703
704void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
705{
706 switch (event->type()) {
707 case QAccessible::NameChanged: {
708 // StaticText is a div
709 setNamedProperty(event->accessibleInterface(), "innerText", QAccessible::Name);
710 } break;
711 default:
712 qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
713 break;
714 }
715}
716
717void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
718{
719 switch (event->type()) {
720 case QAccessible::StateChanged: {
721 auto iface = event->accessibleInterface();
722 auto element = getHtmlElement(iface);
723 setAttribute(element, "readonly", iface->state().readOnly);
724 if (iface->state().passwordEdit)
725 setProperty(element, "type", "password");
726 else
727 setProperty(element, "type", "text");
728 } break;
729 case QAccessible::NameChanged: {
730 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
731 } break;
732 case QAccessible::ObjectShow:
733 case QAccessible::Focus: {
734 auto iface = event->accessibleInterface();
735 auto element = getHtmlElement(iface);
736 if (!element.isUndefined()) {
737 setAttribute(element, "readonly", iface->state().readOnly);
738 if (iface->state().passwordEdit)
739 setProperty(element, "type", "password");
740 else
741 setProperty(element, "type", "text");
742 }
743 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
744 } break;
745 case QAccessible::TextRemoved:
746 case QAccessible::TextInserted:
747 case QAccessible::TextCaretMoved: {
748 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
749 } break;
750 default:
751 qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
752 break;
753 }
754}
755
756void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
757{
758 if (event["target"].isNull() || event["target"].isUndefined())
759 return;
760
761 if (event["target"]["data-qta11yinterface"].isNull() || event["target"]["data-qta11yinterface"].isUndefined())
762 return;
763
764 auto iface = reinterpret_cast<QAccessibleInterface *>(event["target"]["data-qta11yinterface"].as<size_t>());
765 if (m_elements.find(iface) == m_elements.end())
766 return;
767
768 const QString eventType = QString::fromStdString(event["type"].as<std::string>());
769 const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
770
771 if (eventType == "focus") {
772 if (actionNames.contains(QAccessibleActionInterface::setFocusAction()))
773 iface->actionInterface()->doAction(QAccessibleActionInterface::setFocusAction());
774 } else if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
775 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
776 } else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) {
777 iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
778 }
779}
780
781void QWasmAccessibility::handleButtonUpdate(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 default:
789 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
790 break;
791 }
792}
793
794void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
795{
796 switch (event->type()) {
797 case QAccessible::Focus:
798 case QAccessible::NameChanged: {
799 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
800 } break;
801 case QAccessible::StateChanged: {
802 QAccessibleInterface *accessible = event->accessibleInterface();
803 const emscripten::val element = getHtmlElement(accessible);
804 setAttribute(element, "checked", accessible->state().checked);
805 setProperty(element, "indeterminate", accessible->state().checkStateMixed);
806 } break;
807 default:
808 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
809 break;
810 }
811}
812
813void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event)
814{
815 QAccessibleInterface *iface = event->accessibleInterface();
816
817 emscripten::val parent = getHtmlElement(iface);
818 emscripten::val label = parent["children"][0];
819 emscripten::val checkbox = emscripten::val::undefined();
820 if (iface->role() == QAccessible::CheckBox)
821 checkbox = parent["children"][1];
822
823 switch (event->type()) {
824 case QAccessible::Focus:
825 case QAccessible::NameChanged: {
826 setProperty(label, "innerText", iface->text(QAccessible::Name).toStdString());
827 if (!checkbox.isUndefined())
828 setAttribute(checkbox, "aria-label", iface->text(QAccessible::Name).toStdString());
829 } break;
830 case QAccessible::StateChanged: {
831 if (!checkbox.isUndefined()) {
832 setAttribute(checkbox, "checked", iface->state().checked);
833 setProperty(checkbox, "indeterminate", iface->state().checkStateMixed);
834 }
835 } break;
836 default:
837 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
838 break;
839 }
840}
841
842void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
843{
844 switch (event->type()) {
845 case QAccessible::Focus:
846 case QAccessible::NameChanged: {
847 /* A switch is like a button in this regard */
848 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
849 } break;
850 case QAccessible::StateChanged: {
851 QAccessibleInterface *accessible = event->accessibleInterface();
852 const emscripten::val element = getHtmlElement(accessible);
853 if (accessible->state().checked)
854 setAttribute(element, "aria-checked", "true");
855 else
856 setAttribute(element, "aria-checked", "false");
857 } break;
858 default:
859 qCDebug(lcQpaAccessibility) << "TODO: implement handleSwitchUpdate for event" << event->type();
860 break;
861 }
862}
863
864void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
865{
866 QAccessibleInterface *iface = event->accessibleInterface();
867 QString text = iface->text(QAccessible::Name);
868 QString desc = iface->text(QAccessible::Description);
869 switch (event->type()) {
870 case QAccessible::NameChanged:
871 case QAccessible::StateChanged:{
872 const emscripten::val element = getHtmlElement(iface);
873 setAttribute(element, "title", text.toStdString());
874 } break;
875 default:
876 qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type();
877 break;
878 }
879}
880void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
881{
882 QAccessibleInterface *iface = event->accessibleInterface();
883 QString text = iface->text(QAccessible::Name);
884 QString desc = iface->text(QAccessible::Description);
885 switch (event->type()) {
886 case QAccessible::Focus:
887 case QAccessible::NameChanged:
888 case QAccessible::MenuStart ://"TODO: To implement later
889 case QAccessible::StateChanged:{
890 const emscripten::val element = getHtmlElement(iface);
891 setAttribute(element, "title", text.toStdString());
892 } break;
893 case QAccessible::PopupMenuStart: {
894 if (iface->childCount() > 0) {
895 const auto childElement = getHtmlElement(iface->child(0));
896 childElement.call<void>("focus");
897 }
898 } break;
899 default:
900 qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type();
901 break;
902 }
903}
904void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
905
906 switch (event->type()) {
907 case QAccessible::NameChanged:
908 case QAccessible::Focus:
909 case QAccessible::DialogStart:
910 case QAccessible::StateChanged: {
911 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
912 } break;
913 default:
914 qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
915 break;
916 }
917}
918
919void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
920{
921 if (!iface)
922 return;
923
924 // We ignore toplevel windows which is categorized
925 // by getWindow(iface->parent()) != getWindow(iface)
926 const QWindow *window1 = getWindow(iface);
927 const QWindow *window0 = (iface->parent()) ? getWindow(iface->parent()) : nullptr;
928
929 if (window1 && window0 == window1) {
930 // Create html element for the interface, sync up properties.
931 bool exists = !getHtmlElement(iface).isUndefined();
932 if (!exists)
933 exists = !createHtmlElement(iface).isUndefined();
934
935 if (exists) {
936 linkToParent(iface);
937 setHtmlElementVisibility(iface, !iface->state().invisible);
938 setHtmlElementGeometry(iface);
939 setHtmlElementDisabled(iface);
940 handleIdentifierUpdate(iface);
941 handleDescriptionChanged(iface);
942 sendEvent(iface, QAccessible::NameChanged);
943 }
944 }
945 for (int i = 0; i < iface->childCount(); ++i)
946 populateAccessibilityTree(iface->child(i));
947}
948
949void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
950{
951 switch (event->type()) {
952 case QAccessible::Focus:
953 case QAccessible::NameChanged: {
954 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
955 } break;
956 case QAccessible::StateChanged: {
957 QAccessibleInterface *accessible = event->accessibleInterface();
958 const emscripten::val element = getHtmlElement(accessible);
959 setAttribute(element, "checked", accessible->state().checked);
960 } break;
961 default:
962 qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type();
963 break;
964 }
965}
966
967void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
968{
969 switch (event->type()) {
970 case QAccessible::ObjectCreated:
971 case QAccessible::StateChanged: {
972 } break;
973 case QAccessible::Focus:
974 case QAccessible::NameChanged: {
975 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
976 } break;
977 case QAccessible::ValueChanged: {
978 QAccessibleInterface *accessible = event->accessibleInterface();
979 const emscripten::val element = getHtmlElement(accessible);
980 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
981 setProperty(element, "value", valueString);
982 } break;
983 default:
984 qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
985 break;
986 }
987}
988
989void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
990{
991 switch (event->type()) {
992 case QAccessible::ObjectCreated:
993 case QAccessible::StateChanged: {
994 } break;
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 setProperty(element, "value", valueString);
1004 } break;
1005 default:
1006 qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
1007 break;
1008 }
1009}
1010
1011void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
1012{
1013 switch (event->type()) {
1014 case QAccessible::Focus:
1015 case QAccessible::NameChanged: {
1016 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1017 } break;
1018 case QAccessible::ValueChanged: {
1019 QAccessibleInterface *accessible = event->accessibleInterface();
1020 const emscripten::val element = getHtmlElement(accessible);
1021 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1022 setAttribute(element, "aria-valuenow", valueString);
1023 } break;
1024 default:
1025 qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
1026 break;
1027 }
1028
1029}
1030
1031void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
1032{
1033 switch (event->type()) {
1034 case QAccessible::NameChanged: {
1035 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1036 } break;
1037 case QAccessible::Focus: {
1038 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1039 } break;
1040 default:
1041 qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
1042 break;
1043 }
1044}
1045
1046void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
1047{
1048 switch (event->type()) {
1049 case QAccessible::NameChanged: {
1050 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1051 } break;
1052 case QAccessible::Focus: {
1053 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1054 } break;
1055 default:
1056 qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
1057 break;
1058 }
1059}
1060
1061void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
1062{
1063 const emscripten::val element = getHtmlElement(iface);
1064 QString id = iface->text(QAccessible::Identifier).replace(" ", "_");
1065 if (id.isEmpty() && iface->role() == QAccessible::PageTabList) {
1066 std::ostringstream oss;
1067 oss << "tabList_0x" << (void *)iface;
1068 id = QString::fromUtf8(oss.str());
1069 }
1070
1071 setAttribute(element, "id", id.toStdString());
1072 if (!id.isEmpty()) {
1073 if (iface->role() == QAccessible::PageTabList) {
1074 for (int i = 0; i < iface->childCount(); ++i) {
1075 const auto child = getHtmlElement(iface->child(i));
1076 setAttribute(child, "aria-owns", id.toStdString());
1077 }
1078 }
1079 }
1080}
1081
1082void QWasmAccessibility::handleDescriptionChanged(QAccessibleInterface *iface)
1083{
1084 const auto desc = iface->text(QAccessible::Description).toStdString();
1085 auto element = getHtmlElement(iface);
1086 auto container = getDescribedByContainer(iface);
1087 if (!container.isUndefined()) {
1088 std::ostringstream oss;
1089 oss << "dbid_" << (void *)iface;
1090 auto id = oss.str();
1091
1092 auto describedBy = container.call<emscripten::val>("querySelector", "#" + std::string(id));
1093 if (desc.empty()) {
1094 if (!describedBy.isUndefined() && !describedBy.isNull()) {
1095 container.call<void>("removeChild", describedBy);
1096 }
1097 setAttribute(element, "aria-describedby", "");
1098 } else {
1099 if (describedBy.isUndefined() || describedBy.isNull()) {
1100 auto document = getDocument(container);
1101 describedBy = document.call<emscripten::val>("createElement", std::string("p"));
1102
1103 container.call<void>("appendChild", describedBy);
1104 }
1105 setAttribute(describedBy, "id", id);
1106 setAttribute(describedBy, "aria-hidden", true);
1107 setAttribute(element, "aria-describedby", id);
1108 setProperty(describedBy, "innerText", desc);
1109 }
1110 }
1111}
1112
1113void QWasmAccessibility::createObject(QAccessibleInterface *iface)
1114{
1115 if (getHtmlElement(iface).isUndefined())
1116 createHtmlElement(iface);
1117}
1118
1119void QWasmAccessibility::removeObject(QAccessibleInterface *iface)
1120{
1121 // Do not dereference the object pointer. it might be invalid.
1122 // Do not dereference the iface either, it refers to the object.
1123 // Note: we may remove children, making them have parentElement undefined
1124 // so we need to check for parentElement here. We do assume that removeObject
1125 // is called on all objects, just not in any predefined order.
1126 const auto it = m_elements.find(iface);
1127 if (it != m_elements.end()) {
1128 auto element = it.value();
1129 auto container = getDescribedByContainer(iface);
1130 if (!container.isUndefined()) {
1131 std::ostringstream oss;
1132 oss << "dbid_" << (void *)iface;
1133 auto id = oss.str();
1134 auto describedBy = container.call<emscripten::val>("querySelector", "#" + std::string(id));
1135 if (!describedBy.isUndefined() && !describedBy.isNull() &&
1136 !describedBy["parentElement"].isUndefined() && !describedBy["parentElement"].isNull())
1137 describedBy["parentElement"].call<void>("removeChild", describedBy);
1138 }
1139 if (!element["parentElement"].isUndefined() && !element["parentElement"].isNull())
1140 element["parentElement"].call<void>("removeChild", element);
1141 m_elements.erase(it);
1142 }
1143}
1144
1145void QWasmAccessibility::unlinkParentForChildren(QAccessibleInterface *iface)
1146{
1147 auto element = getHtmlElement(iface);
1148 if (!element.isUndefined()) {
1149 auto oldContainer = element["parentElement"];
1150 auto newContainer = getElementContainer(iface);
1151 if (!oldContainer.isUndefined() &&
1152 !oldContainer.isNull() &&
1153 oldContainer != newContainer) {
1154 oldContainer.call<void>("removeChild", element);
1155 }
1156 }
1157 for (int i = 0; i < iface->childCount(); ++i)
1158 unlinkParentForChildren(iface->child(i));
1159}
1160
1161void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
1162{
1163 auto element = getHtmlElement(iface);
1164 if (!element.isUndefined()) {
1165 if (element["parentElement"].isUndefined() ||
1166 element["parentElement"].isNull()) {
1167 linkToParent(iface);
1168 }
1169 }
1170 for (int i = 0; i < iface->childCount(); ++i)
1171 relinkParentForChildren(iface->child(i));
1172}
1173
1174void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
1175{
1176 if (handleUpdateByEventType(event))
1177 handleUpdateByInterfaceRole(event);
1178}
1179
1180bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
1181{
1182 if (!m_accessibilityEnabled)
1183 return false;
1184
1185 QAccessibleInterface *iface = event->accessibleInterface();
1186 if (!iface) {
1187 qWarning() << "handleUpdateByEventType with null a11y interface" << event->type() << event->object();
1188 return false;
1189 }
1190
1191 // Handle event types that creates/removes objects.
1192 switch (event->type()) {
1193 case QAccessible::ObjectCreated:
1194 // Do nothing, there are too many changes to the interface
1195 // before ObjectShow is called
1196 return false;
1197
1198 case QAccessible::ObjectDestroyed:
1199 // The object might be under destruction, and the interface is not valid
1200 // but we can look at the pointer,
1201 removeObject(iface);
1202 return false;
1203
1204 case QAccessible::ObjectShow: // We do not get ObjectCreated from widgets, we get ObjectShow
1205 createObject(iface);
1206 break;
1207
1208 case QAccessible::ParentChanged:
1209 unlinkParentForChildren(iface);
1210 relinkParentForChildren(iface);
1211 break;
1212
1213 default:
1214 break;
1215 };
1216
1217 if (getHtmlElement(iface).isUndefined())
1218 return false;
1219
1220 // Handle some common event types. See
1221 // https://doc.qt.io/qt-5/qaccessible.html#Event-enum
1222 switch (event->type()) {
1223 case QAccessible::StateChanged: {
1224 QAccessibleStateChangeEvent *stateChangeEvent = (QAccessibleStateChangeEvent *)event;
1225 if (stateChangeEvent->changedStates().disabled)
1226 setHtmlElementDisabled(iface);
1227 } break;
1228
1229 case QAccessible::DescriptionChanged:
1230 handleDescriptionChanged(iface);
1231 return false;
1232
1233 case QAccessible::Focus:
1234 // We do not get all callbacks for the geometry
1235 // hence we update here as well.
1236 setHtmlElementGeometry(iface);
1237 setHtmlElementFocus(iface);
1238 break;
1239
1240 case QAccessible::IdentifierChanged:
1241 handleIdentifierUpdate(iface);
1242 return false;
1243
1244 case QAccessible::ObjectShow:
1245 linkToParent(iface);
1246 setHtmlElementVisibility(iface, true);
1247
1248 // Sync up properties on show;
1249 setHtmlElementGeometry(iface);
1250 sendEvent(iface, QAccessible::NameChanged);
1251 break;
1252
1253 case QAccessible::ObjectHide:
1254 linkToParent(iface);
1255 setHtmlElementVisibility(iface, false);
1256 return false;
1257
1258 case QAccessible::LocationChanged:
1259 setHtmlElementGeometry(iface);
1260 return false;
1261
1262 // TODO: maybe handle more types here
1263 default:
1264 break;
1265 };
1266
1267 return true;
1268}
1269
1270void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
1271{
1272 if (!m_accessibilityEnabled)
1273 return;
1274
1275 QAccessibleInterface *iface = event->accessibleInterface();
1276 if (!iface) {
1277 qWarning() << "handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
1278 return;
1279 }
1280
1281 // Switch on interface role, see
1282 // https://doc.qt.io/qt-5/qaccessibleinterface.html#role
1283 switch (iface->role()) {
1284 case QAccessible::StaticText:
1285 handleStaticTextUpdate(event);
1286 break;
1287 case QAccessible::Button:
1288 handleButtonUpdate(event);
1289 break;
1290 case QAccessible::CheckBox:
1291 if (iface->childCount() > 0)
1292 handleGroupBoxUpdate(event);
1293 else
1294 handleCheckBoxUpdate(event);
1295 break;
1296 case QAccessible::Switch:
1297 handleSwitchUpdate(event);
1298 break;
1299 case QAccessible::EditableText:
1300 handleLineEditUpdate(event);
1301 break;
1302 case QAccessible::Dialog:
1303 handleDialogUpdate(event);
1304 break;
1305 case QAccessible::MenuItem:
1306 case QAccessible::MenuBar:
1307 case QAccessible::PopupMenu:
1308 handleMenuUpdate(event);
1309 break;
1310 case QAccessible::ToolBar:
1311 case QAccessible::ButtonMenu:
1312 handleToolUpdate(event);
1313 case QAccessible::RadioButton:
1314 handleRadioButtonUpdate(event);
1315 break;
1316 case QAccessible::SpinBox:
1317 handleSpinBoxUpdate(event);
1318 break;
1319 case QAccessible::Slider:
1320 handleSliderUpdate(event);
1321 break;
1322 case QAccessible::PageTab:
1323 handlePageTabUpdate(event);
1324 break;
1325 case QAccessible::PageTabList:
1326 handlePageTabListUpdate(event);
1327 break;
1328 case QAccessible::ScrollBar:
1329 handleScrollBarUpdate(event);
1330 break;
1331 case QAccessible::Grouping:
1332 handleGroupBoxUpdate(event);
1333 break;
1334 default:
1335 qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role();
1336 };
1337}
1338
1339void QWasmAccessibility::setRootObject(QObject *root)
1340{
1341 m_rootObject = root;
1342}
1343
1344void QWasmAccessibility::initialize()
1345{
1346
1347}
1348
1349void QWasmAccessibility::cleanup()
1350{
1351
1352}
1353
1354#endif // QT_CONFIG(accessibility)
void QWasmAccessibilityEnable()