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