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