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
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 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 elementContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-elements-container"));
202 auto describedByContainer = document.call<emscripten::val>("createElement", std::string("div"));
203 describedByContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-describedby-container"));
204
205 a11yContainer.call<void>("appendChild", elementContainer);
206 a11yContainer.call<void>("appendChild", describedByContainer);
207
208 return a11yContainer;
209}
210
211emscripten::val QWasmAccessibility::getA11yContainer(QAccessibleInterface *iface)
212{
213 return getA11yContainer(getWindow(iface));
214}
215
216emscripten::val QWasmAccessibility::getDescribedByContainer(QWindow *window)
217{
218 auto a11yContainer = getA11yContainer(window);
219 if (a11yContainer.isUndefined())
220 return emscripten ::val::undefined();
221
222 Q_ASSERT(a11yContainer["childElementCount"].as<unsigned>() == 2);
223 Q_ASSERT(!a11yContainer["children"][1].isUndefined());
224
225 return a11yContainer["children"][1];
226}
227
228emscripten::val QWasmAccessibility::getDescribedByContainer(QAccessibleInterface *iface)
229{
230 return getDescribedByContainer(getWindow(iface));
231}
232
233emscripten::val QWasmAccessibility::getElementContainer(QWindow *window)
234{
235 auto a11yContainer = getA11yContainer(window);
236 if (a11yContainer.isUndefined())
237 return emscripten ::val::undefined();
238
239 Q_ASSERT(a11yContainer["childElementCount"].as<unsigned>() == 2);
240 Q_ASSERT(!a11yContainer["children"][0].isUndefined());
241 return a11yContainer["children"][0];
242}
243
244emscripten::val QWasmAccessibility::getElementContainer(QAccessibleInterface *iface)
245{
246 // Here we skip QWindow nodes, as they are already present. Such nodes
247 // has a parent window of null.
248 //
249 // The next node should return the a11y container.
250 // Further nodes should return the element of the parent.
251 if (!getWindow(iface))
252 return emscripten::val::undefined();
253
254 if (isWindowNode(iface))
255 return emscripten::val::undefined();
256
257 if (isWindowNode(iface->parent()))
258 return getElementContainer(getWindow(iface->parent()));
259
260 // Regular node
261 return getHtmlElement(iface->parent());
262}
263
264QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
265{
266 if (!iface)
267 return nullptr;
268
269 QWindow *window = iface->window();
270 // this is needed to add tabs as the window is not available
271 if (!window && iface->parent())
272 window = iface->parent()->window();
273 return window;
274}
275
276emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
277{
278 if (container.isUndefined())
279 return emscripten::val::global("document");
280 return container["ownerDocument"];
281}
282
283emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface)
284{
285 return getDocument(getA11yContainer(iface));
286}
287
288void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr,
289 const std::string &val)
290{
291 if (val != "")
292 element.call<void>("setAttribute", attr, val);
293 else
294 element.call<void>("removeAttribute", attr);
295}
296
297void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr,
298 const char *val)
299{
300 setAttribute(element, attr, std::string(val));
301}
302
303void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr, bool val)
304{
305 if (val)
306 element.call<void>("setAttribute", attr, val);
307 else
308 element.call<void>("removeAttribute", attr);
309}
310
311void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property,
312 const std::string &val)
313{
314 element.set(property, val);
315}
316
317void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property, const char *val)
318{
319 setProperty(element, property, std::string(val));
320}
321
322void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property, bool val)
323{
324 element.set(property, val);
325}
326
327void QWasmAccessibility::setNamedAttribute(QAccessibleInterface *iface, const std::string &attribute, QAccessible::Text text)
328{
329 const emscripten::val element = getHtmlElement(iface);
330 setAttribute(element, attribute, iface->text(text).toStdString());
331}
332void QWasmAccessibility::setNamedProperty(QAccessibleInterface *iface, const std::string &property, QAccessible::Text text)
333{
334 const emscripten::val element = getHtmlElement(iface);
335 setProperty(element, property, iface->text(text).toStdString());
336}
337
338void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripten::val element, const char *eventType)
339{
340 element.set("data-qta11yinterface", reinterpret_cast<size_t>(iface));
341 element.call<void>("addEventListener", emscripten::val(eventType),
342 QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex),
343 true);
344}
345
346void QWasmAccessibility::sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType)
347{
348 if (iface->object()) {
349 QAccessibleEvent event(iface->object(), eventType);
350 handleUpdateByInterfaceRole(&event);
351 } else {
352 QAccessibleEvent event(iface, eventType);
353 handleUpdateByInterfaceRole(&event);
354 }
355}
356
357emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
358{
359 // Get the html container element for the interface; this depends on which
360 // QScreen it is on. If the interface is not on a screen yet we get an undefined
361 // container, and the code below handles that case as well.
362 emscripten::val container = getElementContainer(iface);
363
364 // Get the correct html document for the container, or fall back
365 // to the global document. TODO: Does using the correct document actually matter?
366 emscripten::val document = getDocument(container);
367
368 // Translate the Qt a11y elemen role into html element type + ARIA role.
369 // Here we can either create <div> elements with a spesific ARIA role,
370 // or create e.g. <button> elements which should have built-in accessibility.
371 emscripten::val element = [this, iface, document] {
372
373 emscripten::val element = emscripten::val::undefined();
374
375 switch (iface->role()) {
376
377 case QAccessible::Button: {
378 element = document.call<emscripten::val>("createElement", std::string("button"));
379 addEventListener(iface, element, "click");
380 } break;
381
382 case QAccessible::Grouping:
383 case QAccessible::CheckBox: {
384 // Three cases:
385 // 1) role=CheckBox, childCount() == 0 -> Checkbox
386 // 2) role=CheckBox, childCount() > 0 -> GroupBox w/checkbox
387 // 3) role=Grouping -> GroupBox w/label
388
389 emscripten::val checkbox = emscripten::val::undefined();
390 if (iface->role() == QAccessible::CheckBox) {
391 checkbox = document.call<emscripten::val>("createElement", std::string("input"));
392 setAttribute(checkbox, "type", "checkbox");
393 setAttribute(checkbox, "checked", iface->state().checked);
394 setProperty(checkbox, "indeterminate", iface->state().checkStateMixed);
395 addEventListener(iface, checkbox, "change");
396 }
397
398 if (iface->childCount() > 0 || iface->role() == QAccessible::Grouping) {
399 auto label = document.call<emscripten::val>("createElement", std::string("span"));
400
401 const std::string id = QString::asprintf("lbid%p", iface).toStdString();
402 setAttribute(label, "id", id);
403
404 element = document.call<emscripten::val>("createElement", std::string("div"));
405 element.call<void>("appendChild", label);
406
407 setAttribute(element, "role", "group");
408 setAttribute(element, "aria-labelledby", id);
409
410 if (!checkbox.isUndefined()) {
411 element.call<void>("appendChild", checkbox);
412 addEventListener(iface, checkbox, "focus");
413 }
414 } else {
415 element = checkbox;
416 }
417 } break;
418
419 case QAccessible::Switch: {
420 element = document.call<emscripten::val>("createElement", std::string("button"));
421 setAttribute(element, "type", "button");
422 setAttribute(element, "role", "switch");
423 if (iface->state().checked)
424 setAttribute(element, "aria-checked", "true");
425 else
426 setAttribute(element, "aria-checked", "false");
427 addEventListener(iface, element, "change");
428 } break;
429
430 case QAccessible::RadioButton: {
431 element = document.call<emscripten::val>("createElement", std::string("input"));
432 setAttribute(element, "type", "radio");
433 setAttribute(element, "checked", iface->state().checked);
434 setProperty(element, "name", "buttonGroup");
435 addEventListener(iface, element, "change");
436 } break;
437
438 case QAccessible::Dial:
439 case QAccessible::SpinBox:
440 case QAccessible::Slider: {
441 const auto minValue = iface->valueInterface()->minimumValue().toString().toStdString();
442 const auto maxValue = iface->valueInterface()->maximumValue().toString().toStdString();
443 const auto stepValue =
444 iface->valueInterface()->minimumStepSize().toString().toStdString();
445 const auto value = iface->valueInterface()->currentValue().toString().toStdString();
446 element = document.call<emscripten::val>("createElement", std::string("input"));
447 setAttribute(element,"type", "number");
448 setAttribute(element, "min", minValue);
449 setAttribute(element, "max", maxValue);
450 setAttribute(element, "step", stepValue);
451 setProperty(element, "value", value);
452 addEventListener(iface, element, "change");
453 } break;
454
455 case QAccessible::PageTabList:{
456 element = document.call<emscripten::val>("createElement", std::string("div"));
457 setAttribute(element, "role", "tablist");
458 setHtmlElementOrientation(element, iface);
459
460 m_elements[iface] = element;
461
462 for (int i = 0; i < iface->childCount(); ++i)
463 createHtmlElement(iface->child(i));
464
465 } break;
466
467 case QAccessible::PageTab:{
468 const QString text = iface->text(QAccessible::Name);
469 element = document.call<emscripten::val>("createElement", std::string("button"));
470 setAttribute(element, "role", "tab");
471 setAttribute(element, "title", text.toStdString());
472 addEventListener(iface, element, "click");
473 } break;
474
475 case QAccessible::ScrollBar: {
476 // Events for scrollbars are handled by Qt, but setFocusPolicy must
477 // be called for this to happen.
478 const auto minValue = iface->valueInterface()->minimumValue().toString().toStdString();
479 const auto maxValue = iface->valueInterface()->maximumValue().toString().toStdString();
480 const auto value = iface->valueInterface()->currentValue().toString().toStdString();
481 element = document.call<emscripten::val>("createElement", std::string("div"));
482 setAttribute(element, "tabindex", "0");
483 setAttribute(element, "role", "scrollbar");
484 setAttribute(element, "aria-valuemin", minValue);
485 setAttribute(element, "aria-valuemax", maxValue);
486 setAttribute(element, "aria-valuenow", value);
487 setHtmlElementOrientation(element, iface);
488 addEventListener(iface, element, "change");
489 } break;
490
491 case QAccessible::StaticText: {
492 element = document.call<emscripten::val>("createElement", std::string("div"));
493 } break;
494 case QAccessible::Dialog: {
495 element = document.call<emscripten::val>("createElement", std::string("dialog"));
496 }break;
497 case QAccessible::ToolBar:{
498 const QString text = iface->text(QAccessible::Name);
499 element = document.call<emscripten::val>("createElement", std::string("div"));
500 setAttribute(element, "role", "toolbar");
501 setAttribute(element, "title", text.toStdString());
502 setHtmlElementOrientation(element, iface);
503 addEventListener(iface, element, "click");
504 }break;
505 case QAccessible::MenuItem:
506 case QAccessible::ButtonDropDown:
507 case QAccessible::ButtonMenu: {
508 const QString text = iface->text(QAccessible::Name);
509 element = document.call<emscripten::val>("createElement", std::string("button"));
510 setAttribute(element, "role", "menuitem");
511 setAttribute(element, "title", text.toStdString());
512 addEventListener(iface, element, "click");
513 }break;
514 case QAccessible::MenuBar:
515 case QAccessible::PopupMenu: {
516 const QString text = iface->text(QAccessible::Name);
517 element = document.call<emscripten::val>("createElement", std::string("div"));
518 setAttribute(element, "role", "menubar");
519 setAttribute(element, "title", text.toStdString());
520 setHtmlElementOrientation(element, iface);
521 m_elements[iface] = element;
522
523 for (int i = 0; i < iface->childCount(); ++i) {
524 emscripten::val childElement = createHtmlElement(iface->child(i));
525 setAttribute(childElement, "aria-owns", text.toStdString());
526 }
527 }break;
528 case QAccessible::EditableText: {
529 element = document.call<emscripten::val>("createElement", std::string("input"));
530 setAttribute(element, "type", "text");
531 setAttribute(element, "contenteditable", "true");
532 setAttribute(element, "readonly", iface->state().readOnly);
533 setAttribute(element, "autocorrect", "off");
534 setProperty(element, "inputMode", "text");
535 } break;
536 case QAccessible::List: {
537 element = document.call<emscripten::val>("createElement", std::string("div"));
538 setAttribute(element, "tabindex", "0");
539 setAttribute(element, "role", "list");
540 } break;
541 case QAccessible::ListItem: {
542 element = document.call<emscripten::val>("createElement", std::string("div"));
543 setAttribute(element, "tabindex", "0");
544 setAttribute(element, "role", "listitem");
545 } break;
546 default:
547 qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role();
548 element = document.call<emscripten::val>("createElement", std::string("div"));
549 }
550
551 addEventListener(iface, element, "focus");
552 return element;
553
554 }();
555
556 m_elements[iface] = element;
557
558 setHtmlElementGeometry(iface);
559 setHtmlElementDisabled(iface);
560 setHtmlElementVisibility(iface, !iface->state().invisible);
561 handleIdentifierUpdate(iface);
562 handleDescriptionChanged(iface);
563 sendEvent(iface, QAccessible::NameChanged);
564
565 linkToParent(iface);
566 // Link in child elements
567 for (int i = 0; i < iface->childCount(); ++i) {
568 if (!getHtmlElement(iface->child(i)).isUndefined())
569 linkToParent(iface->child(i));
570 }
571
572 return element;
573}
574
575void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
576{
577 Q_UNUSED(iface);
578 qCDebug(lcQpaAccessibility) << "TODO destroyHtmlElement";
579}
580
581emscripten::val QWasmAccessibility::getHtmlElement(QAccessibleInterface *iface)
582{
583 auto it = m_elements.find(iface);
584 if (it != m_elements.end())
585 return it.value();
586
587 return emscripten::val::undefined();
588}
589
590void QWasmAccessibility::repairLinks(QAccessibleInterface *iface)
591{
592 // relink any children that are linked to the wrong parent,
593 // This can be caused by a missing ParentChanged event.
594 bool moved = false;
595 for (int i = 0; i < iface->childCount(); ++i) {
596 const auto elementI = getHtmlElement(iface->child(i));
597 const auto containerI = getElementContainer(iface->child(i));
598
599 if (!elementI.isUndefined() &&
600 !containerI.isUndefined() &&
601 !elementI["parentElement"].isUndefined() &&
602 !elementI["parentElement"].isNull() &&
603 elementI["parentElement"] != containerI) {
604 moved = true;
605 break;
606 }
607 }
608 if (moved) {
609 for (int i = 0; i < iface->childCount(); ++i) {
610 const auto elementI = getHtmlElement(iface->child(i));
611 const auto containerI = getElementContainer(iface->child(i));
612 if (!elementI.isUndefined() && !containerI.isUndefined())
613 containerI.call<void>("appendChild", elementI);
614 }
615 }
616}
617
618void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
619{
620 emscripten::val element = getHtmlElement(iface);
621 emscripten::val container = getElementContainer(iface);
622
623 if (container.isUndefined() || element.isUndefined())
624 return;
625
626 // Make sure that we don't change the focused element
627 const auto activeElementBefore = emscripten::val::take_ownership(
628 getActiveElement_js(emscripten::val::undefined().as_handle()));
629
630
631 repairLinks(iface->parent());
632
633 emscripten::val next = emscripten::val::undefined();
634 const int thisIndex = iface->parent()->indexOfChild(iface);
635 if (thisIndex >= 0) {
636 Q_ASSERT(thisIndex < iface->parent()->childCount());
637 for (int i = thisIndex + 1; i < iface->parent()->childCount(); ++i) {
638 const auto elementI = getHtmlElement(iface->parent()->child(i));
639 if (!elementI.isUndefined() &&
640 elementI["parentElement"] == container) {
641 next = elementI;
642 break;
643 }
644 }
645 if (next.isUndefined())
646 container.call<void>("appendChild", element);
647 else
648 container.call<void>("insertBefore", element, next);
649 }
650 const auto activeElementAfter = emscripten::val::take_ownership(
651 getActiveElement_js(emscripten::val::undefined().as_handle()));
652 if (activeElementBefore != activeElementAfter) {
653 if (!activeElementBefore.isUndefined() && !activeElementBefore.isNull())
654 activeElementBefore.call<void>("focus");
655 }
656}
657
658void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible)
659{
660 emscripten::val element = getHtmlElement(iface);
661 setAttribute(element, "inert", !visible);
662}
663
664void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
665{
666 const emscripten::val element = getHtmlElement(iface);
667
668 QRect windowGeometry = iface->rect();
669 if (iface->parent()) {
670 // Both iface and iface->parent returns geometry in screen coordinates
671 // We only want the relative coordinates, so the coordinate system does
672 // not matter as long as it is the same.
673 const QRect parentRect = iface->parent()->rect();
674 const QRect thisRect = iface->rect();
675 const QRect result(thisRect.topLeft() - parentRect.topLeft(), thisRect.size());
676 windowGeometry = result;
677 } else {
678 // Elements without a parent are not a part of the a11y tree, and don't
679 // have meaningful geometry.
680 Q_ASSERT(!getWindow(iface));
681 }
682 setHtmlElementGeometry(element, windowGeometry);
683}
684
685void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
686{
687 // Position the element using "position: absolute" in order to place
688 // it under the corresponding Qt element in the screen.
689 emscripten::val style = element["style"];
690 style.set("position", std::string("absolute"));
691 style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
692 // screen element, but isn't
693 style.set("left", std::to_string(geometry.x()) + "px");
694 style.set("top", std::to_string(geometry.y()) + "px");
695 style.set("width", std::to_string(geometry.width()) + "px");
696 style.set("height", std::to_string(geometry.height()) + "px");
697}
698
699void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
700{
701 const auto element = getHtmlElement(iface);
702 element.call<void>("focus");
703}
704
705void QWasmAccessibility::setHtmlElementDisabled(QAccessibleInterface *iface)
706{
707 auto element = getHtmlElement(iface);
708 setAttribute(element, "aria-disabled", iface->state().disabled);
709}
710
711void QWasmAccessibility::setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface)
712{
713 Q_ASSERT(iface);
714 if (QAccessibleAttributesInterface *attributesIface = iface->attributesInterface()) {
715 const QVariant orientationVariant =
716 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
717 if (orientationVariant.isValid()) {
718 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
719 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
720 const std::string value = orientation == Qt::Horizontal ? "horizontal" : "vertical";
721 setAttribute(element, "aria-orientation", value);
722 }
723 }
724}
725
726void QWasmAccessibility::handleListItemUpdate(QAccessibleEvent *event)
727{
728 auto iface = event->accessibleInterface();
729
730 switch (event->type()) {
731 case QAccessible::NameChanged: {
732 // ListItem is a div
733 setNamedProperty(iface, "innerText", QAccessible::Name);
734 } break;
735 default:
736 qCDebug(lcQpaAccessibility) << "TODO: implement handleListItemUpdate for event" << event->type();
737 break;
738 }
739}
740
741void QWasmAccessibility::handleDialUpdate(QAccessibleEvent *event)
742{
743 switch (event->type()) {
744 case QAccessible::ObjectCreated:
745 case QAccessible::StateChanged: {
746 } break;
747 case QAccessible::Focus:
748 case QAccessible::NameChanged: {
749 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
750 } break;
751 case QAccessible::ValueChanged: {
752 QAccessibleInterface *accessible = event->accessibleInterface();
753 const emscripten::val element = getHtmlElement(accessible);
754 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
755 setProperty(element, "value", valueString);
756 } break;
757 default:
758 qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
759 break;
760 }
761}
762
763void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
764{
765 switch (event->type()) {
766 case QAccessible::NameChanged: {
767 // StaticText is a div
768 setNamedProperty(event->accessibleInterface(), "innerText", QAccessible::Name);
769 } break;
770 default:
771 qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
772 break;
773 }
774}
775
776void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
777{
778 switch (event->type()) {
779 case QAccessible::StateChanged: {
780 auto iface = event->accessibleInterface();
781 auto element = getHtmlElement(iface);
782 setAttribute(element, "readonly", iface->state().readOnly);
783 if (iface->state().passwordEdit)
784 setProperty(element, "type", "password");
785 else
786 setProperty(element, "type", "text");
787 } break;
788 case QAccessible::ValueChanged:
789 case QAccessible::NameChanged: {
790 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
791 } break;
792 case QAccessible::ObjectShow:
793 case QAccessible::Focus: {
794 auto iface = event->accessibleInterface();
795 auto element = getHtmlElement(iface);
796 if (!element.isUndefined()) {
797 setAttribute(element, "readonly", iface->state().readOnly);
798 if (iface->state().passwordEdit)
799 setProperty(element, "type", "password");
800 else
801 setProperty(element, "type", "text");
802 }
803 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
804 } break;
805 case QAccessible::TextRemoved:
806 case QAccessible::TextInserted: {
807 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
808 } break;
809 case QAccessible::TextCaretMoved: {
810 // We lack ValueChanged event, sync on caret move instead
811 setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
812 } break;
813 default:
814 qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
815 break;
816 }
817}
818
819void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
820{
821 if (event["target"].isNull() || event["target"].isUndefined())
822 return;
823
824 if (event["target"]["data-qta11yinterface"].isNull() || event["target"]["data-qta11yinterface"].isUndefined())
825 return;
826
827 auto iface = reinterpret_cast<QAccessibleInterface *>(event["target"]["data-qta11yinterface"].as<size_t>());
828 if (m_elements.find(iface) == m_elements.end())
829 return;
830
831 const auto eventType = QString::fromStdString(event["type"].as<std::string>());
832 const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
833 bool handled = false;
834
835 if (eventType == "blur") {
836 ;
837 } else if (eventType == "keydown") {
838 ;
839 } else if (eventType == "keyup") {
840 ;
841 } else if (eventType == "focus") {
842 if (actionNames.contains(QAccessibleActionInterface::setFocusAction()))
843 iface->actionInterface()->doAction(QAccessibleActionInterface::setFocusAction());
844 } else if (eventType == "click") {
845 handled = true;
846 if (actionNames.contains(QAccessibleActionInterface::pressAction()))
847 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
848 else if (actionNames.contains(QAccessibleActionInterface::showMenuAction()))
849 iface->actionInterface()->doAction(QAccessibleActionInterface::showMenuAction());
850 } else if (eventType == "change") {
851 handled = true;
852 // Checkboxes can have both toggle and press, in this case it is
853 // important to invoke press to handle tristate correctly
854 if (actionNames.contains(QAccessibleActionInterface::pressAction()))
855 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
856 else if (actionNames.contains(QAccessibleActionInterface::toggleAction()))
857 iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
858 } else {
859 qWarning() << " Unknown event" << eventType;
860 }
861
862 if (handled) {
863 event.call<void>("preventDefault");
864 event.call<void>("stopPropagation");
865 }
866}
867
868void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
869{
870 switch (event->type()) {
871 case QAccessible::Focus:
872 case QAccessible::NameChanged: {
873 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
874 } break;
875 default:
876 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
877 break;
878 }
879}
880
881void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
882{
883 switch (event->type()) {
884 case QAccessible::Focus:
885 case QAccessible::NameChanged: {
886 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
887 } break;
888 case QAccessible::StateChanged: {
889 QAccessibleInterface *accessible = event->accessibleInterface();
890 const emscripten::val element = getHtmlElement(accessible);
891 setAttribute(element, "checked", accessible->state().checked);
892 setProperty(element, "indeterminate", accessible->state().checkStateMixed);
893 } break;
894 default:
895 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
896 break;
897 }
898}
899
900void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event)
901{
902 QAccessibleInterface *iface = event->accessibleInterface();
903
904 emscripten::val parent = getHtmlElement(iface);
905 emscripten::val label = parent["children"][0];
906 emscripten::val checkbox = emscripten::val::undefined();
907 if (iface->role() == QAccessible::CheckBox)
908 checkbox = parent["children"][1];
909
910 switch (event->type()) {
911 case QAccessible::Focus:
912 case QAccessible::NameChanged: {
913 setProperty(label, "innerText", iface->text(QAccessible::Name).toStdString());
914 if (!checkbox.isUndefined())
915 setAttribute(checkbox, "aria-label", iface->text(QAccessible::Name).toStdString());
916 } break;
917 case QAccessible::StateChanged: {
918 if (!checkbox.isUndefined()) {
919 setAttribute(checkbox, "checked", iface->state().checked);
920 setProperty(checkbox, "indeterminate", iface->state().checkStateMixed);
921 }
922 } break;
923 default:
924 qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
925 break;
926 }
927}
928
929void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
930{
931 switch (event->type()) {
932 case QAccessible::Focus:
933 case QAccessible::NameChanged: {
934 /* A switch is like a button in this regard */
935 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
936 } break;
937 case QAccessible::StateChanged: {
938 QAccessibleInterface *accessible = event->accessibleInterface();
939 const emscripten::val element = getHtmlElement(accessible);
940 if (accessible->state().checked)
941 setAttribute(element, "aria-checked", "true");
942 else
943 setAttribute(element, "aria-checked", "false");
944 } break;
945 default:
946 qCDebug(lcQpaAccessibility) << "TODO: implement handleSwitchUpdate for event" << event->type();
947 break;
948 }
949}
950
951void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
952{
953 QAccessibleInterface *iface = event->accessibleInterface();
954 QString text = iface->text(QAccessible::Name);
955 QString desc = iface->text(QAccessible::Description);
956 switch (event->type()) {
957 case QAccessible::NameChanged:
958 case QAccessible::StateChanged:{
959 const emscripten::val element = getHtmlElement(iface);
960 setAttribute(element, "title", text.toStdString());
961 } break;
962 default:
963 qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type();
964 break;
965 }
966}
967void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
968{
969 QAccessibleInterface *iface = event->accessibleInterface();
970 QString text = iface->text(QAccessible::Name);
971 QString desc = iface->text(QAccessible::Description);
972 switch (event->type()) {
973 case QAccessible::Focus:
974 case QAccessible::NameChanged:
975 case QAccessible::MenuStart ://"TODO: To implement later
976 case QAccessible::StateChanged:{
977 const emscripten::val element = getHtmlElement(iface);
978 setAttribute(element, "title", text.toStdString());
979 } break;
980 case QAccessible::PopupMenuStart: {
981 if (iface->childCount() > 0) {
982 const auto childElement = getHtmlElement(iface->child(0));
983 childElement.call<void>("focus");
984 }
985 } break;
986 default:
987 qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type();
988 break;
989 }
990}
991void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
992
993 switch (event->type()) {
994 case QAccessible::NameChanged:
995 case QAccessible::Focus:
996 case QAccessible::DialogStart:
997 case QAccessible::StateChanged: {
998 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
999 } break;
1000 default:
1001 qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
1002 break;
1003 }
1004}
1005
1006void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
1007{
1008 if (!iface)
1009 return;
1010
1011 // We ignore toplevel windows which is categorized
1012 // by getWindow(iface->parent()) != getWindow(iface)
1013 const QWindow *window1 = getWindow(iface);
1014 const QWindow *window0 = (iface->parent()) ? getWindow(iface->parent()) : nullptr;
1015
1016 if (window1 && window0 == window1) {
1017 // Create html element for the interface, sync up properties.
1018 bool exists = !getHtmlElement(iface).isUndefined();
1019 if (!exists)
1020 exists = !createHtmlElement(iface).isUndefined();
1021
1022 if (exists) {
1023 linkToParent(iface);
1024 setHtmlElementVisibility(iface, !iface->state().invisible);
1025 setHtmlElementGeometry(iface);
1026 setHtmlElementDisabled(iface);
1027 handleIdentifierUpdate(iface);
1028 handleDescriptionChanged(iface);
1029 sendEvent(iface, QAccessible::NameChanged);
1030 }
1031 }
1032 for (int i = 0; i < iface->childCount(); ++i)
1033 populateAccessibilityTree(iface->child(i));
1034}
1035
1036void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
1037{
1038 switch (event->type()) {
1039 case QAccessible::Focus:
1040 case QAccessible::NameChanged: {
1041 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1042 } break;
1043 case QAccessible::StateChanged: {
1044 QAccessibleInterface *accessible = event->accessibleInterface();
1045 const emscripten::val element = getHtmlElement(accessible);
1046 setAttribute(element, "checked", accessible->state().checked);
1047 } break;
1048 default:
1049 qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type();
1050 break;
1051 }
1052}
1053
1054void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
1055{
1056 switch (event->type()) {
1057 case QAccessible::ObjectCreated:
1058 case QAccessible::StateChanged: {
1059 } break;
1060 case QAccessible::Focus:
1061 case QAccessible::NameChanged: {
1062 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1063 } break;
1064 case QAccessible::ValueChanged: {
1065 QAccessibleInterface *accessible = event->accessibleInterface();
1066 const emscripten::val element = getHtmlElement(accessible);
1067 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1068 setProperty(element, "value", valueString);
1069 } break;
1070 default:
1071 qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
1072 break;
1073 }
1074}
1075
1076void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
1077{
1078 switch (event->type()) {
1079 case QAccessible::ObjectCreated:
1080 case QAccessible::StateChanged: {
1081 } break;
1082 case QAccessible::Focus:
1083 case QAccessible::NameChanged: {
1084 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1085 } break;
1086 case QAccessible::ValueChanged: {
1087 QAccessibleInterface *accessible = event->accessibleInterface();
1088 const emscripten::val element = getHtmlElement(accessible);
1089 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1090 setProperty(element, "value", valueString);
1091 } break;
1092 default:
1093 qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
1094 break;
1095 }
1096}
1097
1098void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
1099{
1100 switch (event->type()) {
1101 case QAccessible::Focus:
1102 case QAccessible::NameChanged: {
1103 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1104 } break;
1105 case QAccessible::ValueChanged: {
1106 QAccessibleInterface *accessible = event->accessibleInterface();
1107 const emscripten::val element = getHtmlElement(accessible);
1108 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1109 setAttribute(element, "aria-valuenow", valueString);
1110 } break;
1111 default:
1112 qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
1113 break;
1114 }
1115
1116}
1117
1118void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
1119{
1120 switch (event->type()) {
1121 case QAccessible::NameChanged: {
1122 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1123 } break;
1124 case QAccessible::Focus: {
1125 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1126 } break;
1127 default:
1128 qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
1129 break;
1130 }
1131}
1132
1133void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
1134{
1135 switch (event->type()) {
1136 case QAccessible::NameChanged: {
1137 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1138 } break;
1139 case QAccessible::Focus: {
1140 setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
1141 } break;
1142 default:
1143 qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
1144 break;
1145 }
1146}
1147
1148void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
1149{
1150 const emscripten::val element = getHtmlElement(iface);
1151 QString id = iface->text(QAccessible::Identifier).replace(" ", "_");
1152 if (id.isEmpty() && iface->role() == QAccessible::PageTabList) {
1153 std::ostringstream oss;
1154 oss << "tabList_0x" << (void *)iface;
1155 id = QString::fromUtf8(oss.str());
1156 }
1157
1158 setAttribute(element, "id", id.toStdString());
1159 if (!id.isEmpty()) {
1160 if (iface->role() == QAccessible::PageTabList) {
1161 for (int i = 0; i < iface->childCount(); ++i) {
1162 const auto child = getHtmlElement(iface->child(i));
1163 setAttribute(child, "aria-owns", id.toStdString());
1164 }
1165 }
1166 }
1167}
1168
1169void QWasmAccessibility::handleDescriptionChanged(QAccessibleInterface *iface)
1170{
1171 const auto desc = iface->text(QAccessible::Description).toStdString();
1172 auto element = getHtmlElement(iface);
1173 auto container = getDescribedByContainer(iface);
1174 if (!container.isUndefined()) {
1175 std::ostringstream oss;
1176 oss << "dbid_" << (void *)iface;
1177 auto id = oss.str();
1178
1179 auto describedBy = container.call<emscripten::val>("querySelector", "#" + std::string(id));
1180 if (desc.empty()) {
1181 if (!describedBy.isUndefined() && !describedBy.isNull()) {
1182 container.call<void>("removeChild", describedBy);
1183 }
1184 setAttribute(element, "aria-describedby", "");
1185 } else {
1186 if (describedBy.isUndefined() || describedBy.isNull()) {
1187 auto document = getDocument(container);
1188 describedBy = document.call<emscripten::val>("createElement", std::string("p"));
1189
1190 container.call<void>("appendChild", describedBy);
1191 }
1192 setAttribute(describedBy, "id", id);
1193 setAttribute(describedBy, "inert", true);
1194 setAttribute(element, "aria-describedby", id);
1195 setProperty(describedBy, "innerText", desc);
1196 }
1197 }
1198}
1199
1200void QWasmAccessibility::handleAnnouncement(QAccessibleInterface *iface,
1201 QAccessibleAnnouncementEvent *announcementEvent)
1202{
1203 emscripten::val element = getHtmlElement(iface);
1204 if (element["ariaNotify"].isUndefined())
1205 return;
1206
1207 const std::string message = announcementEvent->message().toStdString();
1208 const std::string prio(announcementEvent->politeness()
1209 == QAccessible::AnnouncementPoliteness::Assertive
1210 ? "high"
1211 : "normal");
1212 emscripten::val options = emscripten::val::object();
1213 options.set("priority", prio);
1214 element.call<void>("ariaNotify", message, options);
1215}
1216
1217void QWasmAccessibility::createObject(QAccessibleInterface *iface)
1218{
1219 if (getHtmlElement(iface).isUndefined())
1220 createHtmlElement(iface);
1221}
1222
1223void QWasmAccessibility::removeObject(QAccessibleInterface *iface)
1224{
1225 // Do not dereference the object pointer. it might be invalid.
1226 // Do not dereference the iface either, it refers to the object.
1227 // Note: we may remove children, making them have parentElement undefined
1228 // so we need to check for parentElement here. We do assume that removeObject
1229 // is called on all objects, just not in any predefined order.
1230 const auto it = m_elements.find(iface);
1231 if (it != m_elements.end()) {
1232 auto element = it.value();
1233 auto container = getDescribedByContainer(iface);
1234 if (!container.isUndefined()) {
1235 std::ostringstream oss;
1236 oss << "dbid_" << (void *)iface;
1237 auto id = oss.str();
1238 auto describedBy = container.call<emscripten::val>("querySelector", "#" + std::string(id));
1239 if (!describedBy.isUndefined() && !describedBy.isNull() &&
1240 !describedBy["parentElement"].isUndefined() && !describedBy["parentElement"].isNull())
1241 describedBy["parentElement"].call<void>("removeChild", describedBy);
1242 }
1243 if (!element["parentElement"].isUndefined() && !element["parentElement"].isNull())
1244 element["parentElement"].call<void>("removeChild", element);
1245 m_elements.erase(it);
1246 }
1247}
1248
1249void QWasmAccessibility::unlinkParentForChildren(QAccessibleInterface *iface)
1250{
1251 auto element = getHtmlElement(iface);
1252 if (!element.isUndefined()) {
1253 auto oldContainer = element["parentElement"];
1254 auto newContainer = getElementContainer(iface);
1255 if (!oldContainer.isUndefined() &&
1256 !oldContainer.isNull() &&
1257 oldContainer != newContainer) {
1258 oldContainer.call<void>("removeChild", element);
1259 }
1260 }
1261 for (int i = 0; i < iface->childCount(); ++i)
1262 unlinkParentForChildren(iface->child(i));
1263}
1264
1265void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
1266{
1267 auto element = getHtmlElement(iface);
1268 if (!element.isUndefined()) {
1269 if (element["parentElement"].isUndefined() ||
1270 element["parentElement"].isNull()) {
1271 linkToParent(iface);
1272 }
1273 }
1274 for (int i = 0; i < iface->childCount(); ++i)
1275 relinkParentForChildren(iface->child(i));
1276}
1277
1278void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
1279{
1280 if (handleUpdateByEventType(event))
1281 handleUpdateByInterfaceRole(event);
1282}
1283
1284bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
1285{
1286 if (!m_accessibilityEnabled)
1287 return false;
1288
1289 QAccessibleInterface *iface = event->accessibleInterface();
1290 if (!iface) {
1291 qWarning() << "handleUpdateByEventType with null a11y interface" << event->type() << event->object();
1292 return false;
1293 }
1294
1295 // Handle event types that creates/removes objects.
1296 switch (event->type()) {
1297 case QAccessible::ObjectCreated:
1298 // Do nothing, there are too many changes to the interface
1299 // before ObjectShow is called
1300 return false;
1301
1302 case QAccessible::ObjectDestroyed:
1303 // The object might be under destruction, and the interface is not valid
1304 // but we can look at the pointer,
1305 removeObject(iface);
1306 return false;
1307
1308 case QAccessible::ObjectShow: // We do not get ObjectCreated from widgets, we get ObjectShow
1309 createObject(iface);
1310 break;
1311
1312 case QAccessible::ParentChanged:
1313 unlinkParentForChildren(iface);
1314 relinkParentForChildren(iface);
1315 break;
1316
1317 default:
1318 break;
1319 };
1320
1321 if (getHtmlElement(iface).isUndefined())
1322 return false;
1323
1324 // Handle some common event types. See
1325 // https://doc.qt.io/qt-6/qaccessible.html#Event-enum
1326 switch (event->type()) {
1327 case QAccessible::Announcement:
1328 handleAnnouncement(iface, static_cast<QAccessibleAnnouncementEvent *>(event));
1329 return false;
1330
1331 case QAccessible::StateChanged: {
1332 QAccessibleStateChangeEvent *stateChangeEvent = (QAccessibleStateChangeEvent *)event;
1333 if (stateChangeEvent->changedStates().disabled)
1334 setHtmlElementDisabled(iface);
1335 } break;
1336
1337 case QAccessible::DescriptionChanged:
1338 handleDescriptionChanged(iface);
1339 return false;
1340
1341 case QAccessible::Focus:
1342 // We do not get all callbacks for the geometry
1343 // hence we update here as well.
1344 setHtmlElementGeometry(iface);
1345 setHtmlElementFocus(iface);
1346 break;
1347
1348 case QAccessible::IdentifierChanged:
1349 handleIdentifierUpdate(iface);
1350 return false;
1351
1352 case QAccessible::ObjectShow:
1353 linkToParent(iface);
1354 setHtmlElementVisibility(iface, true);
1355
1356 // Sync up properties on show;
1357 setHtmlElementGeometry(iface);
1358 sendEvent(iface, QAccessible::NameChanged);
1359 break;
1360
1361 case QAccessible::ObjectHide:
1362 // Do not call linkToParent, only set visibility to false
1363 setHtmlElementVisibility(iface, false);
1364 return false;
1365
1366 case QAccessible::LocationChanged:
1367 setHtmlElementGeometry(iface);
1368 return false;
1369
1370 // TODO: maybe handle more types here
1371 default:
1372 break;
1373 };
1374
1375 return true;
1376}
1377
1378void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
1379{
1380 if (!m_accessibilityEnabled)
1381 return;
1382
1383 QAccessibleInterface *iface = event->accessibleInterface();
1384 if (!iface) {
1385 qWarning() << "handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
1386 return;
1387 }
1388
1389 // Switch on interface role, see
1390 // https://doc.qt.io/qt-5/qaccessibleinterface.html#role
1391 switch (iface->role()) {
1392 case QAccessible::List:
1393 break;
1394 case QAccessible::ListItem:
1395 handleListItemUpdate(event);
1396 break;
1397 case QAccessible::StaticText:
1398 handleStaticTextUpdate(event);
1399 break;
1400 case QAccessible::Button:
1401 handleButtonUpdate(event);
1402 break;
1403 case QAccessible::CheckBox:
1404 if (iface->childCount() > 0)
1405 handleGroupBoxUpdate(event);
1406 else
1407 handleCheckBoxUpdate(event);
1408 break;
1409 case QAccessible::Switch:
1410 handleSwitchUpdate(event);
1411 break;
1412 case QAccessible::EditableText:
1413 handleLineEditUpdate(event);
1414 break;
1415 case QAccessible::Dialog:
1416 handleDialogUpdate(event);
1417 break;
1418 case QAccessible::MenuItem:
1419 case QAccessible::MenuBar:
1420 case QAccessible::PopupMenu:
1421 handleMenuUpdate(event);
1422 break;
1423 case QAccessible::ToolBar:
1424 case QAccessible::ButtonMenu:
1425 handleToolUpdate(event);
1426 case QAccessible::RadioButton:
1427 handleRadioButtonUpdate(event);
1428 break;
1429 case QAccessible::Dial:
1430 handleDialUpdate(event);
1431 break;
1432 case QAccessible::SpinBox:
1433 handleSpinBoxUpdate(event);
1434 break;
1435 case QAccessible::Slider:
1436 handleSliderUpdate(event);
1437 break;
1438 case QAccessible::PageTab:
1439 handlePageTabUpdate(event);
1440 break;
1441 case QAccessible::PageTabList:
1442 handlePageTabListUpdate(event);
1443 break;
1444 case QAccessible::ScrollBar:
1445 handleScrollBarUpdate(event);
1446 break;
1447 case QAccessible::Grouping:
1448 handleGroupBoxUpdate(event);
1449 break;
1450 default:
1451 qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role();
1452 };
1453}
1454
1455void QWasmAccessibility::setRootObject(QObject *root)
1456{
1457 m_rootObject = root;
1458}
1459
1460void QWasmAccessibility::initialize()
1461{
1462
1463}
1464
1465void QWasmAccessibility::cleanup()
1466{
1467
1468}
1469
1470#endif // QT_CONFIG(accessibility)
void QWasmAccessibilityEnable()