9#include <QtCore/private/qwasmsuspendresumecontrol_p.h>
10#include <QtGui/qwindow.h>
16 QWasmAccessibility::enable();
19#if QT_CONFIG(accessibility)
21#include <QtGui/private/qaccessiblebridgeutils_p.h>
23Q_LOGGING_CATEGORY(lcQpaAccessibility,
"qt.qpa.accessibility")
26EM_JS(emscripten::EM_VAL, getActiveElement_js, (emscripten::EM_VAL undefHandle), {
27 var activeEl = document.activeElement;
31 }
else if (activeEl.shadowRoot) {
32 activeEl = activeEl.shadowRoot.activeElement;
34 return Emval.toHandle(activeEl);
50QWasmAccessibility::QWasmAccessibility()
54 if (qEnvironmentVariableIntValue(
"QT_WASM_ENABLE_ACCESSIBILITY") == 1)
55 enableAccessibility();
58 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
60 m_eventHandlerIndex = suspendResume->registerEventHandler([
this](
const emscripten::val event){
61 this->handleEventFromHtmlElement(event);
65QWasmAccessibility::~QWasmAccessibility()
68 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
69 suspendResume->removeEventHandler(m_eventHandlerIndex);
74QWasmAccessibility *QWasmAccessibility::s_instance =
nullptr;
76QWasmAccessibility* QWasmAccessibility::get()
81void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window)
83 get()->addAccessibilityEnableButtonImpl(window);
86void QWasmAccessibility::onShowWindow(QWindow *window)
88 get()->onShowWindowImpl(window);
91void QWasmAccessibility::onRemoveWindow(QWindow *window)
93 get()->onRemoveWindowImpl(window);
96bool QWasmAccessibility::isEnabled()
98 return get()->m_accessibilityEnabled;
100void QWasmAccessibility::enable()
103 get()->enableAccessibility();
106void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
108 if (m_accessibilityEnabled)
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);
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)));
123void QWasmAccessibility::onShowWindowImpl(QWindow *window)
125 if (!m_accessibilityEnabled)
127 populateAccessibilityTree(window->accessibleRoot());
130void QWasmAccessibility::onRemoveWindowImpl(QWindow *window)
133 const auto it = m_enableButtons.find(window);
134 if (it != m_enableButtons.end()) {
136 auto [element, callback] = it->second;
138 element[
"parentElement"].call<
void>(
"removeChild", element);
139 m_enableButtons.erase(it);
142 if (!m_accessibilityEnabled)
145 auto a11yContainer = getA11yContainer(window);
146 if (a11yContainer.isUndefined())
150 for (
auto it = m_elements.begin(); it != m_elements.end();) {
151 if (a11yContainer.call<
bool>(
"contains", it.value()))
152 it = m_elements.erase(it);
156 getElementContainer(window).call<
void>(
"replaceChildren");
157 getDescribedByContainer(window).call<
void>(
"replaceChildren");
160void QWasmAccessibility::enableAccessibility()
165 Q_ASSERT(!m_accessibilityEnabled);
166 m_accessibilityEnabled =
true;
168 for (
const auto& [key, value] : m_enableButtons) {
169 const auto &[element, callback] = value;
171 if (
auto wasmWindow = QWasmWindow::fromWindow(key))
172 wasmWindow->onAccessibilityEnable();
173 onShowWindowImpl(key);
174 element[
"parentElement"].call<
void>(
"removeChild", element);
176 m_enableButtons.clear();
179bool QWasmAccessibility::isWindowNode(QAccessibleInterface *iface)
181 return (iface && !getWindow(iface->parent()) && getWindow(iface));
184emscripten::val QWasmAccessibility::getA11yContainer(QWindow *window)
186 const auto wasmWindow = QWasmWindow::fromWindow(window);
188 return emscripten::val::undefined();
190 auto a11yContainer = wasmWindow->a11yContainer();
191 if (a11yContainer[
"childElementCount"].as<
unsigned>() == 2)
192 return a11yContainer;
194 Q_ASSERT(a11yContainer[
"childElementCount"].as<
unsigned>() == 0);
196 const auto document = getDocument(a11yContainer);
197 if (document.isUndefined())
198 return emscripten::val::undefined();
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"));
205 a11yContainer.call<
void>(
"appendChild", elementContainer);
206 a11yContainer.call<
void>(
"appendChild", describedByContainer);
208 return a11yContainer;
211emscripten::val QWasmAccessibility::getA11yContainer(QAccessibleInterface *iface)
213 return getA11yContainer(getWindow(iface));
216emscripten::val QWasmAccessibility::getDescribedByContainer(QWindow *window)
218 auto a11yContainer = getA11yContainer(window);
219 if (a11yContainer.isUndefined())
220 return emscripten ::val::undefined();
222 Q_ASSERT(a11yContainer[
"childElementCount"].as<
unsigned>() == 2);
223 Q_ASSERT(!a11yContainer[
"children"][1].isUndefined());
225 return a11yContainer[
"children"][1];
228emscripten::val QWasmAccessibility::getDescribedByContainer(QAccessibleInterface *iface)
230 return getDescribedByContainer(getWindow(iface));
233emscripten::val QWasmAccessibility::getElementContainer(QWindow *window)
235 auto a11yContainer = getA11yContainer(window);
236 if (a11yContainer.isUndefined())
237 return emscripten ::val::undefined();
239 Q_ASSERT(a11yContainer[
"childElementCount"].as<
unsigned>() == 2);
240 Q_ASSERT(!a11yContainer[
"children"][0].isUndefined());
241 return a11yContainer[
"children"][0];
244emscripten::val QWasmAccessibility::getElementContainer(QAccessibleInterface *iface)
251 if (!getWindow(iface))
252 return emscripten::val::undefined();
254 if (isWindowNode(iface))
255 return emscripten::val::undefined();
257 if (isWindowNode(iface->parent()))
258 return getElementContainer(getWindow(iface->parent()));
261 return getHtmlElement(iface->parent());
264QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
269 QWindow *window = iface->window();
271 if (!window && iface->parent())
272 window = iface->parent()->window();
276emscripten::val QWasmAccessibility::getDocument(
const emscripten::val &container)
278 if (container.isUndefined())
279 return emscripten::val::global(
"document");
280 return container[
"ownerDocument"];
283emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface)
285 return getDocument(getA11yContainer(iface));
288void QWasmAccessibility::setAttribute(emscripten::val element,
const std::string &attr,
289 const std::string &val)
292 element.call<
void>(
"setAttribute", attr, val);
294 element.call<
void>(
"removeAttribute", attr);
297void QWasmAccessibility::setAttribute(emscripten::val element,
const std::string &attr,
300 setAttribute(element, attr, std::string(val));
303void QWasmAccessibility::setAttribute(emscripten::val element,
const std::string &attr,
bool val)
306 element.call<
void>(
"setAttribute", attr, val);
308 element.call<
void>(
"removeAttribute", attr);
311void QWasmAccessibility::setProperty(emscripten::val element,
const std::string &property,
312 const std::string &val)
314 element.set(property, val);
317void QWasmAccessibility::setProperty(emscripten::val element,
const std::string &property,
const char *val)
319 setProperty(element, property, std::string(val));
322void QWasmAccessibility::setProperty(emscripten::val element,
const std::string &property,
bool val)
324 element.set(property, val);
327void QWasmAccessibility::setNamedAttribute(QAccessibleInterface *iface,
const std::string &attribute, QAccessible::Text text)
329 const emscripten::val element = getHtmlElement(iface);
330 setAttribute(element, attribute, iface->text(text).toStdString());
332void QWasmAccessibility::setNamedProperty(QAccessibleInterface *iface,
const std::string &property, QAccessible::Text text)
334 const emscripten::val element = getHtmlElement(iface);
335 setProperty(element, property, iface->text(text).toStdString());
338void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripten::val element,
const char *eventType)
340 element.set(
"data-qta11yinterface",
reinterpret_cast<size_t>(iface));
341 element.call<
void>(
"addEventListener", emscripten::val(eventType),
342 QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex),
346void QWasmAccessibility::sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType)
348 if (iface->object()) {
349 QAccessibleEvent event(iface->object(), eventType);
350 handleUpdateByInterfaceRole(&event);
352 QAccessibleEvent event(iface, eventType);
353 handleUpdateByInterfaceRole(&event);
357emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
362 emscripten::val container = getElementContainer(iface);
366 emscripten::val document = getDocument(container);
371 emscripten::val element = [
this, iface, document] {
373 emscripten::val element = emscripten::val::undefined();
375 switch (iface->role()) {
377 case QAccessible::Button: {
378 element = document.call<emscripten::val>(
"createElement", std::string(
"button"));
379 addEventListener(iface, element,
"click");
382 case QAccessible::Grouping:
383 case QAccessible::CheckBox: {
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");
398 if (iface->childCount() > 0 || iface->role() == QAccessible::Grouping) {
399 auto label = document.call<emscripten::val>(
"createElement", std::string(
"span"));
401 const std::string id = QString::asprintf(
"lbid%p", iface).toStdString();
402 setAttribute(label,
"id", id);
404 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
405 element.call<
void>(
"appendChild", label);
407 setAttribute(element,
"role",
"group");
408 setAttribute(element,
"aria-labelledby", id);
410 if (!checkbox.isUndefined()) {
411 element.call<
void>(
"appendChild", checkbox);
412 addEventListener(iface, checkbox,
"focus");
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");
426 setAttribute(element,
"aria-checked",
"false");
427 addEventListener(iface, element,
"click");
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");
438 case QAccessible::Dial:
439 case QAccessible::SpinBox:
440 case QAccessible::Slider: {
441 element = document.call<emscripten::val>(
"createElement", std::string(
"input"));
442 setAttribute(element,
"type",
"number");
443 if (QAccessibleValueInterface *valueInterface = iface->valueInterface()) {
444 setAttribute(element,
"min", valueInterface->minimumValue().toString().toStdString());
445 setAttribute(element,
"max", valueInterface->maximumValue().toString().toStdString());
446 setAttribute(element,
"step",
447 valueInterface->minimumStepSize().toString().toStdString());
448 setProperty(element,
"value", valueInterface->currentValue().toString().toStdString());
450 addEventListener(iface, element,
"change");
453 case QAccessible::PageTabList:{
454 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
455 setAttribute(element,
"role",
"tablist");
456 setHtmlElementOrientation(element, iface);
458 m_elements[iface] = element;
460 for (
int i = 0; i < iface->childCount(); ++i)
461 createHtmlElement(iface->child(i));
465 case QAccessible::PageTab:{
466 const QString text = iface->text(QAccessible::Name);
467 element = document.call<emscripten::val>(
"createElement", std::string(
"button"));
468 setAttribute(element,
"role",
"tab");
469 setAttribute(element,
"title", text.toStdString());
470 addEventListener(iface, element,
"click");
473 case QAccessible::ScrollBar: {
476 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
477 setAttribute(element,
"tabindex",
"0");
478 setAttribute(element,
"role",
"scrollbar");
479 if (QAccessibleValueInterface *valueInterface = iface->valueInterface()) {
480 setAttribute(element,
"aria-valuemin",
481 valueInterface->minimumValue().toString().toStdString());
482 setAttribute(element,
"aria-valuemax",
483 valueInterface->maximumValue().toString().toStdString());
484 setAttribute(element,
"aria-valuenow",
485 valueInterface->currentValue().toString().toStdString());
487 setHtmlElementOrientation(element, iface);
488 addEventListener(iface, element,
"change");
491 case QAccessible::StaticText: {
492 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
494 case QAccessible::Dialog: {
495 element = document.call<emscripten::val>(
"createElement", std::string(
"dialog"));
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");
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");
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",
519 iface->role() == QAccessible::PopupMenu ?
"menu" :
"menubar");
520 setAttribute(element,
"title", text.toStdString());
521 setHtmlElementOrientation(element, iface);
522 m_elements[iface] = element;
526 for (
int i = 0; i < iface->childCount(); ++i)
527 createHtmlElement(iface->child(i));
529 case QAccessible::EditableText: {
530 element = document.call<emscripten::val>(
"createElement", std::string(
"input"));
531 setAttribute(element,
"type",
"text");
532 setAttribute(element,
"contenteditable",
"true");
533 setAttribute(element,
"readonly", iface->state().readOnly);
534 setAttribute(element,
"autocorrect",
"off");
535 setProperty(element,
"inputMode",
"text");
537 case QAccessible::List: {
538 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
539 setAttribute(element,
"tabindex",
"0");
540 setAttribute(element,
"role",
"list");
542 case QAccessible::ListItem: {
543 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
544 setAttribute(element,
"tabindex",
"0");
545 setAttribute(element,
"role",
"listitem");
547 case QAccessible::ProgressBar: {
548 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
549 setAttribute(element,
"tabindex",
"0");
550 setAttribute(element,
"role",
"progressbar");
551 setAttribute(element,
"aria-live",
"polite");
552 if (
auto valueInterface = iface->valueInterface()) {
553 const auto name = iface->text(QAccessible::Name).toStdString();
554 const auto min = valueInterface->minimumValue().toInt();
555 const auto max = valueInterface->maximumValue().toInt();
556 const auto cur = valueInterface->currentValue().toInt();
559 const auto totalSteps = qint64(max) - min;
560 const auto progress = (totalSteps == 0) ? 0 :
static_cast<
int>((qint64(cur) - min) * 100.0 / totalSteps);
563 setAttribute(element,
"aria-valuenow",
"0");
565 setAttribute(element,
"aria-valuenow",
"100");
567 setAttribute(element,
"aria-valuenow", std::to_string(progress));
568 setAttribute(element,
"aria-valuemin",
"0");
569 setAttribute(element,
"aria-valuemax",
"100");
570 setAttribute(element,
"aria-label", name);
574 qCDebug(lcQpaAccessibility) <<
"TODO: createHtmlElement() handle" << iface->role();
575 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
578 addEventListener(iface, element,
"focus");
583 m_elements[iface] = element;
585 setHtmlElementGeometry(iface);
586 setHtmlElementDisabled(iface);
587 setHtmlElementVisibility(iface, !iface->state().invisible);
588 handleIdentifierUpdate(iface);
589 handleDescriptionChanged(iface);
590 sendEvent(iface, QAccessible::NameChanged);
594 for (
int i = 0; i < iface->childCount(); ++i) {
595 if (!getHtmlElement(iface->child(i)).isUndefined())
596 linkToParent(iface->child(i));
602void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
605 qCDebug(lcQpaAccessibility) <<
"TODO destroyHtmlElement";
608emscripten::val QWasmAccessibility::getHtmlElement(QAccessibleInterface *iface)
610 auto it = m_elements.find(iface);
611 if (it != m_elements.end())
614 return emscripten::val::undefined();
617void QWasmAccessibility::repairLinks(QAccessibleInterface *iface)
622 for (
int i = 0; i < iface->childCount(); ++i) {
623 const auto elementI = getHtmlElement(iface->child(i));
624 const auto containerI = getElementContainer(iface->child(i));
626 if (!elementI.isUndefined() &&
627 !containerI.isUndefined() &&
628 !elementI[
"parentElement"].isUndefined() &&
629 !elementI[
"parentElement"].isNull() &&
630 elementI[
"parentElement"] != containerI) {
636 for (
int i = 0; i < iface->childCount(); ++i) {
637 const auto elementI = getHtmlElement(iface->child(i));
638 const auto containerI = getElementContainer(iface->child(i));
639 if (!elementI.isUndefined() && !containerI.isUndefined())
640 containerI.call<
void>(
"appendChild", elementI);
645void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
647 emscripten::val element = getHtmlElement(iface);
648 emscripten::val container = getElementContainer(iface);
650 if (container.isUndefined() || element.isUndefined())
654 const auto activeElementBefore = emscripten::val::take_ownership(
655 getActiveElement_js(emscripten::val::undefined().as_handle()));
658 repairLinks(iface->parent());
660 emscripten::val next = emscripten::val::undefined();
661 const int thisIndex = iface->parent()->indexOfChild(iface);
662 if (thisIndex >= 0) {
663 Q_ASSERT(thisIndex < iface->parent()->childCount());
664 for (
int i = thisIndex + 1; i < iface->parent()->childCount(); ++i) {
665 const auto elementI = getHtmlElement(iface->parent()->child(i));
666 if (!elementI.isUndefined() &&
667 elementI[
"parentElement"] == container) {
672 if (next.isUndefined())
673 container.call<
void>(
"appendChild", element);
675 container.call<
void>(
"insertBefore", element, next);
677 const auto activeElementAfter = emscripten::val::take_ownership(
678 getActiveElement_js(emscripten::val::undefined().as_handle()));
679 if (activeElementBefore != activeElementAfter) {
680 if (!activeElementBefore.isUndefined() && !activeElementBefore.isNull())
681 activeElementBefore.call<
void>(
"focus");
685void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface,
bool visible)
687 emscripten::val element = getHtmlElement(iface);
688 setAttribute(element,
"inert", !visible);
691void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
693 const emscripten::val element = getHtmlElement(iface);
695 QRect windowGeometry = iface->rect();
696 if (iface->parent()) {
700 const QRect parentRect = iface->parent()->rect();
701 const QRect thisRect = iface->rect();
702 const QRect result(thisRect.topLeft() - parentRect.topLeft(), thisRect.size());
703 windowGeometry = result;
707 Q_ASSERT(!getWindow(iface));
709 setHtmlElementGeometry(element, windowGeometry);
712void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
716 emscripten::val style = element[
"style"];
717 style.set(
"position", std::string(
"absolute"));
718 style.set(
"z-index", std::string(
"-1"));
720 style.set(
"left", std::to_string(geometry.x()) +
"px");
721 style.set(
"top", std::to_string(geometry.y()) +
"px");
722 style.set(
"width", std::to_string(geometry.width()) +
"px");
723 style.set(
"height", std::to_string(geometry.height()) +
"px");
726void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
728 const auto element = getHtmlElement(iface);
729 element.call<
void>(
"focus");
732void QWasmAccessibility::setHtmlElementDisabled(QAccessibleInterface *iface)
734 auto element = getHtmlElement(iface);
735 setAttribute(element,
"aria-disabled", iface->state().disabled);
738void QWasmAccessibility::setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface)
741 if (QAccessibleAttributesInterface *attributesIface = iface->attributesInterface()) {
742 const QVariant orientationVariant =
743 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
744 if (orientationVariant.isValid()) {
745 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
746 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
747 const std::string value = orientation == Qt::Horizontal ?
"horizontal" :
"vertical";
748 setAttribute(element,
"aria-orientation", value);
753void QWasmAccessibility::handleListItemUpdate(QAccessibleEvent *event)
755 auto iface = event->accessibleInterface();
757 switch (event->type()) {
758 case QAccessible::NameChanged: {
760 setNamedProperty(iface,
"innerText", QAccessible::Name);
763 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleListItemUpdate for event" << event->type();
768void QWasmAccessibility::handleDialUpdate(QAccessibleEvent *event)
770 switch (event->type()) {
771 case QAccessible::ObjectCreated:
772 case QAccessible::StateChanged: {
774 case QAccessible::Focus:
775 case QAccessible::NameChanged: {
776 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
778 case QAccessible::ValueChanged: {
779 QAccessibleInterface *accessible = event->accessibleInterface();
780 if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
781 setProperty(getHtmlElement(accessible),
"value",
782 valueInterface->currentValue().toString().toStdString());
786 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleDialUpdate for event" << event->type();
791void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
793 switch (event->type()) {
794 case QAccessible::NameChanged: {
796 setNamedProperty(event->accessibleInterface(),
"innerText", QAccessible::Name);
799 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleStaticTextUpdate for event" << event->type();
804void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
806 switch (event->type()) {
807 case QAccessible::StateChanged: {
808 auto iface = event->accessibleInterface();
809 auto element = getHtmlElement(iface);
810 setAttribute(element,
"readonly", iface->state().readOnly);
811 if (iface->state().passwordEdit)
812 setProperty(element,
"type",
"password");
814 setProperty(element,
"type",
"text");
816 case QAccessible::ValueChanged:
817 case QAccessible::NameChanged: {
818 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
820 case QAccessible::ObjectShow:
821 case QAccessible::Focus: {
822 auto iface = event->accessibleInterface();
823 auto element = getHtmlElement(iface);
824 if (!element.isUndefined()) {
825 setAttribute(element,
"readonly", iface->state().readOnly);
826 if (iface->state().passwordEdit)
827 setProperty(element,
"type",
"password");
829 setProperty(element,
"type",
"text");
831 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
833 case QAccessible::TextRemoved:
834 case QAccessible::TextInserted: {
835 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
837 case QAccessible::TextCaretMoved: {
839 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
842 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleLineEditUpdate for event" << event->type();
847void QWasmAccessibility::handleEventFromHtmlElement(
const emscripten::val event)
849 if (event[
"target"].isNull() || event[
"target"].isUndefined())
852 if (event[
"target"][
"data-qta11yinterface"].isNull() || event[
"target"][
"data-qta11yinterface"].isUndefined())
855 auto iface =
reinterpret_cast<QAccessibleInterface *>(event[
"target"][
"data-qta11yinterface"].as<size_t>());
856 if (m_elements.find(iface) == m_elements.end())
859 const auto eventType = QString::fromStdString(event[
"type"].as<std::string>());
860 const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
861 bool handled =
false;
863 if (eventType ==
"blur") {
865 }
else if (eventType ==
"keydown") {
867 }
else if (eventType ==
"keyup") {
869 }
else if (eventType ==
"focus") {
870 if (actionNames.contains(QAccessibleActionInterface::setFocusAction()))
871 iface->actionInterface()->doAction(QAccessibleActionInterface::setFocusAction());
872 }
else if (eventType ==
"click") {
874 if (actionNames.contains(QAccessibleActionInterface::pressAction()))
875 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
876 else if (actionNames.contains(QAccessibleActionInterface::showMenuAction()))
877 iface->actionInterface()->doAction(QAccessibleActionInterface::showMenuAction());
878 }
else if (eventType ==
"change") {
882 if (actionNames.contains(QAccessibleActionInterface::pressAction()))
883 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
884 else if (actionNames.contains(QAccessibleActionInterface::toggleAction()))
885 iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
887 qCDebug(lcQpaAccessibility) <<
"Unknown event" << eventType;
891 event.call<
void>(
"preventDefault");
892 event.call<
void>(
"stopPropagation");
896void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
898 switch (event->type()) {
899 case QAccessible::Focus:
900 case QAccessible::NameChanged: {
901 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
904 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
909void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
911 switch (event->type()) {
912 case QAccessible::Focus:
913 case QAccessible::NameChanged: {
914 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
916 case QAccessible::StateChanged: {
917 QAccessibleInterface *accessible = event->accessibleInterface();
918 const emscripten::val element = getHtmlElement(accessible);
919 setAttribute(element,
"checked", accessible->state().checked);
920 setProperty(element,
"indeterminate", accessible->state().checkStateMixed);
923 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
928void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event)
930 QAccessibleInterface *iface = event->accessibleInterface();
932 emscripten::val parent = getHtmlElement(iface);
933 emscripten::val label = parent[
"children"][0];
934 emscripten::val checkbox = emscripten::val::undefined();
935 if (iface->role() == QAccessible::CheckBox)
936 checkbox = parent[
"children"][1];
938 switch (event->type()) {
939 case QAccessible::Focus:
940 case QAccessible::NameChanged: {
941 setProperty(label,
"innerText", iface->text(QAccessible::Name).toStdString());
942 if (!checkbox.isUndefined())
943 setAttribute(checkbox,
"aria-label", iface->text(QAccessible::Name).toStdString());
945 case QAccessible::StateChanged: {
946 if (!checkbox.isUndefined()) {
947 setAttribute(checkbox,
"checked", iface->state().checked);
948 setProperty(checkbox,
"indeterminate", iface->state().checkStateMixed);
952 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
957void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
959 switch (event->type()) {
960 case QAccessible::Focus:
961 case QAccessible::NameChanged: {
963 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
965 case QAccessible::StateChanged: {
966 QAccessibleInterface *accessible = event->accessibleInterface();
967 const emscripten::val element = getHtmlElement(accessible);
968 if (accessible->state().checked)
969 setAttribute(element,
"aria-checked",
"true");
971 setAttribute(element,
"aria-checked",
"false");
974 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleSwitchUpdate for event" << event->type();
979void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
981 QAccessibleInterface *iface = event->accessibleInterface();
982 QString text = iface->text(QAccessible::Name);
983 QString desc = iface->text(QAccessible::Description);
984 switch (event->type()) {
985 case QAccessible::NameChanged:
986 case QAccessible::StateChanged:{
987 const emscripten::val element = getHtmlElement(iface);
988 setAttribute(element,
"title", text.toStdString());
991 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleToolUpdate for event" << event->type();
995void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
997 QAccessibleInterface *iface = event->accessibleInterface();
998 QString text = iface->text(QAccessible::Name);
999 QString desc = iface->text(QAccessible::Description);
1000 switch (event->type()) {
1001 case QAccessible::Focus:
1002 case QAccessible::NameChanged:
1003 case QAccessible::MenuStart :
1004 case QAccessible::StateChanged:{
1005 const emscripten::val element = getHtmlElement(iface);
1006 setAttribute(element,
"title", text.toStdString());
1008 case QAccessible::PopupMenuStart: {
1009 if (iface->childCount() > 0) {
1010 const auto childElement = getHtmlElement(iface->child(0));
1011 childElement.call<
void>(
"focus");
1015 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleMenuUpdate for event" << event->type();
1019void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
1021 switch (event->type()) {
1022 case QAccessible::NameChanged:
1023 case QAccessible::Focus:
1024 case QAccessible::DialogStart:
1025 case QAccessible::StateChanged: {
1026 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1029 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleLineEditUpdate for event" << event->type();
1034void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
1041 const QWindow *window1 = getWindow(iface);
1042 const QWindow *window0 = (iface->parent()) ? getWindow(iface->parent()) :
nullptr;
1044 if (window1 && window0 == window1) {
1046 bool exists = !getHtmlElement(iface).isUndefined();
1048 exists = !createHtmlElement(iface).isUndefined();
1051 linkToParent(iface);
1052 setHtmlElementVisibility(iface, !iface->state().invisible);
1053 setHtmlElementGeometry(iface);
1054 setHtmlElementDisabled(iface);
1055 handleIdentifierUpdate(iface);
1056 handleDescriptionChanged(iface);
1057 sendEvent(iface, QAccessible::NameChanged);
1060 for (
int i = 0; i < iface->childCount(); ++i)
1061 populateAccessibilityTree(iface->child(i));
1064void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
1066 switch (event->type()) {
1067 case QAccessible::Focus:
1068 case QAccessible::NameChanged: {
1069 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1071 case QAccessible::StateChanged: {
1072 QAccessibleInterface *accessible = event->accessibleInterface();
1073 const emscripten::val element = getHtmlElement(accessible);
1074 setAttribute(element,
"checked", accessible->state().checked);
1077 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleRadioButtonUpdate for event" << event->type();
1082void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
1084 switch (event->type()) {
1085 case QAccessible::ObjectCreated:
1086 case QAccessible::StateChanged: {
1088 case QAccessible::Focus:
1089 case QAccessible::NameChanged: {
1090 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1092 case QAccessible::ValueChanged: {
1093 QAccessibleInterface *accessible = event->accessibleInterface();
1094 if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
1095 setProperty(getHtmlElement(accessible),
"value",
1096 valueInterface->currentValue().toString().toStdString());
1100 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleSpinBoxUpdate for event" << event->type();
1105void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
1107 switch (event->type()) {
1108 case QAccessible::ObjectCreated:
1109 case QAccessible::StateChanged: {
1111 case QAccessible::Focus:
1112 case QAccessible::NameChanged: {
1113 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1115 case QAccessible::ValueChanged: {
1116 QAccessibleInterface *accessible = event->accessibleInterface();
1117 if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
1118 setProperty(getHtmlElement(accessible),
"value",
1119 valueInterface->currentValue().toString().toStdString());
1123 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleSliderUpdate for event" << event->type();
1128void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
1130 switch (event->type()) {
1131 case QAccessible::Focus:
1132 case QAccessible::NameChanged: {
1133 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1135 case QAccessible::ValueChanged: {
1136 QAccessibleInterface *accessible = event->accessibleInterface();
1137 if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
1138 setAttribute(getHtmlElement(accessible),
"aria-valuenow",
1139 valueInterface->currentValue().toString().toStdString());
1143 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleScrollBarUpdate for event" << event->type();
1148void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
1150 switch (event->type()) {
1151 case QAccessible::NameChanged: {
1152 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1154 case QAccessible::Focus: {
1155 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1158 qCDebug(lcQpaAccessibility) <<
"TODO: implement handlePageTabUpdate for event" << event->type();
1163void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
1165 switch (event->type()) {
1166 case QAccessible::NameChanged: {
1167 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1169 case QAccessible::Focus: {
1170 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1173 qCDebug(lcQpaAccessibility) <<
"TODO: implement handlePageTabListUpdate for event" << event->type();
1178void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
1182 const emscripten::val element = getHtmlElement(iface);
1183 const QString id = iface->text(QAccessible::Identifier).replace(
" ",
"_");
1184 setAttribute(element,
"id", id.toStdString());
1187void QWasmAccessibility::handleDescriptionChanged(QAccessibleInterface *iface)
1189 const auto desc = iface->text(QAccessible::Description).toStdString();
1190 auto element = getHtmlElement(iface);
1191 auto container = getDescribedByContainer(iface);
1192 if (!container.isUndefined()) {
1193 std::ostringstream oss;
1194 oss <<
"dbid_" << (
void *)iface;
1195 auto id = oss.str();
1197 auto describedBy = container.call<emscripten::val>(
"querySelector",
"#" + std::string(id));
1199 if (!describedBy.isUndefined() && !describedBy.isNull()) {
1200 container.call<
void>(
"removeChild", describedBy);
1202 setAttribute(element,
"aria-describedby",
"");
1204 if (describedBy.isUndefined() || describedBy.isNull()) {
1205 auto document = getDocument(container);
1206 describedBy = document.call<emscripten::val>(
"createElement", std::string(
"p"));
1208 container.call<
void>(
"appendChild", describedBy);
1210 setAttribute(describedBy,
"id", id);
1211 setAttribute(describedBy,
"inert",
true);
1212 setAttribute(element,
"aria-describedby", id);
1213 setProperty(describedBy,
"innerText", desc);
1218void QWasmAccessibility::handleAnnouncement(QAccessibleInterface *iface,
1219 QAccessibleAnnouncementEvent *announcementEvent)
1221 emscripten::val element = getHtmlElement(iface);
1222 if (element[
"ariaNotify"].isUndefined())
1225 const std::string message = announcementEvent->message().toStdString();
1226 const std::string prio(announcementEvent->politeness()
1227 == QAccessible::AnnouncementPoliteness::Assertive
1230 emscripten::val options = emscripten::val::object();
1231 options.set(
"priority", prio);
1232 element.call<
void>(
"ariaNotify", message, options);
1235void QWasmAccessibility::handleProgressBarUpdate(QAccessibleEvent *event)
1237 auto iface = event->accessibleInterface();
1238 auto valueInterface = iface->valueInterface();
1239 auto element = getHtmlElement(iface);
1241 switch (event->type()) {
1242 case QAccessible::Focus:
1243 case QAccessible::NameChanged: {
1244 const auto name = iface->text(QAccessible::Name).toStdString();
1245 setAttribute(element,
"aria-label", name);
1247 case QAccessible::ValueChanged: {
1248 if (valueInterface) {
1249 const auto min = valueInterface->minimumValue().toInt();
1250 const auto max = valueInterface->maximumValue().toInt();
1251 const auto cur = valueInterface->currentValue().toInt();
1254 const auto totalSteps = qint64(max) - min;
1255 const auto progress = (totalSteps == 0) ? 0 :
static_cast<
int>((qint64(cur) - min) * 100.0 / totalSteps);
1258 setAttribute(element,
"aria-valuenow",
"0");
1259 else if (cur >= max)
1260 setAttribute(element,
"aria-valuenow",
"100");
1262 setAttribute(element,
"aria-valuenow", std::to_string(progress));
1270void QWasmAccessibility::createObject(QAccessibleInterface *iface)
1272 if (getHtmlElement(iface).isUndefined())
1273 createHtmlElement(iface);
1276void QWasmAccessibility::removeObject(QAccessibleInterface *iface)
1283 const auto it = m_elements.find(iface);
1284 if (it != m_elements.end()) {
1285 auto element = it.value();
1286 auto container = getDescribedByContainer(iface);
1287 if (!container.isUndefined()) {
1288 std::ostringstream oss;
1289 oss <<
"dbid_" << (
void *)iface;
1290 auto id = oss.str();
1291 auto describedBy = container.call<emscripten::val>(
"querySelector",
"#" + std::string(id));
1292 if (!describedBy.isUndefined() && !describedBy.isNull() &&
1293 !describedBy[
"parentElement"].isUndefined() && !describedBy[
"parentElement"].isNull())
1294 describedBy[
"parentElement"].call<
void>(
"removeChild", describedBy);
1296 if (!element[
"parentElement"].isUndefined() && !element[
"parentElement"].isNull())
1297 element[
"parentElement"].call<
void>(
"removeChild", element);
1298 m_elements.erase(it);
1302void QWasmAccessibility::unlinkParentForChildren(QAccessibleInterface *iface)
1304 auto element = getHtmlElement(iface);
1305 if (!element.isUndefined()) {
1306 auto oldContainer = element[
"parentElement"];
1307 auto newContainer = getElementContainer(iface);
1308 if (!oldContainer.isUndefined() &&
1309 !oldContainer.isNull() &&
1310 oldContainer != newContainer) {
1311 oldContainer.call<
void>(
"removeChild", element);
1314 for (
int i = 0; i < iface->childCount(); ++i)
1315 unlinkParentForChildren(iface->child(i));
1318void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
1320 auto element = getHtmlElement(iface);
1321 if (!element.isUndefined()) {
1322 if (element[
"parentElement"].isUndefined() ||
1323 element[
"parentElement"].isNull()) {
1324 linkToParent(iface);
1327 for (
int i = 0; i < iface->childCount(); ++i)
1328 relinkParentForChildren(iface->child(i));
1331void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
1333 if (handleUpdateByEventType(event))
1334 handleUpdateByInterfaceRole(event);
1337bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
1339 if (!m_accessibilityEnabled)
1342 QAccessibleInterface *iface = event->accessibleInterface();
1344 qWarning() <<
"handleUpdateByEventType with null a11y interface" << event->type() << event->object();
1349 switch (event->type()) {
1350 case QAccessible::ObjectCreated:
1355 case QAccessible::ObjectDestroyed:
1358 removeObject(iface);
1361 case QAccessible::ObjectShow:
1362 createObject(iface);
1365 case QAccessible::ParentChanged:
1366 unlinkParentForChildren(iface);
1367 relinkParentForChildren(iface);
1374 if (getHtmlElement(iface).isUndefined())
1379 switch (event->type()) {
1380 case QAccessible::Announcement:
1381 handleAnnouncement(iface,
static_cast<QAccessibleAnnouncementEvent *>(event));
1384 case QAccessible::StateChanged: {
1385 QAccessibleStateChangeEvent *stateChangeEvent = (QAccessibleStateChangeEvent *)event;
1386 if (stateChangeEvent->changedStates().disabled)
1387 setHtmlElementDisabled(iface);
1390 case QAccessible::DescriptionChanged:
1391 handleDescriptionChanged(iface);
1394 case QAccessible::Focus:
1397 setHtmlElementGeometry(iface);
1398 setHtmlElementFocus(iface);
1401 case QAccessible::IdentifierChanged:
1402 handleIdentifierUpdate(iface);
1405 case QAccessible::ObjectShow:
1406 linkToParent(iface);
1407 setHtmlElementVisibility(iface,
true);
1410 setHtmlElementGeometry(iface);
1411 sendEvent(iface, QAccessible::NameChanged);
1414 case QAccessible::ObjectHide:
1416 setHtmlElementVisibility(iface,
false);
1419 case QAccessible::LocationChanged:
1420 setHtmlElementGeometry(iface);
1431void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
1433 if (!m_accessibilityEnabled)
1436 QAccessibleInterface *iface = event->accessibleInterface();
1438 qWarning() <<
"handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
1444 switch (iface->role()) {
1445 case QAccessible::List:
1447 case QAccessible::ListItem:
1448 handleListItemUpdate(event);
1450 case QAccessible::StaticText:
1451 handleStaticTextUpdate(event);
1453 case QAccessible::Button:
1454 handleButtonUpdate(event);
1456 case QAccessible::CheckBox:
1457 if (iface->childCount() > 0)
1458 handleGroupBoxUpdate(event);
1460 handleCheckBoxUpdate(event);
1462 case QAccessible::Switch:
1463 handleSwitchUpdate(event);
1465 case QAccessible::EditableText:
1466 handleLineEditUpdate(event);
1468 case QAccessible::Dialog:
1469 handleDialogUpdate(event);
1471 case QAccessible::MenuItem:
1472 case QAccessible::MenuBar:
1473 case QAccessible::PopupMenu:
1474 handleMenuUpdate(event);
1476 case QAccessible::ToolBar:
1477 case QAccessible::ButtonMenu:
1478 handleToolUpdate(event);
1480 case QAccessible::RadioButton:
1481 handleRadioButtonUpdate(event);
1483 case QAccessible::Dial:
1484 handleDialUpdate(event);
1486 case QAccessible::SpinBox:
1487 handleSpinBoxUpdate(event);
1489 case QAccessible::Slider:
1490 handleSliderUpdate(event);
1492 case QAccessible::PageTab:
1493 handlePageTabUpdate(event);
1495 case QAccessible::PageTabList:
1496 handlePageTabListUpdate(event);
1498 case QAccessible::ScrollBar:
1499 handleScrollBarUpdate(event);
1501 case QAccessible::Grouping:
1502 handleGroupBoxUpdate(event);
1504 case QAccessible::ProgressBar:
1505 handleProgressBarUpdate(event);
1508 qCDebug(lcQpaAccessibility) <<
"TODO: implement notifyAccessibilityUpdate for role" << iface->role();
1512void QWasmAccessibility::setRootObject(QObject *root)
1514 m_rootObject = root;
1517void QWasmAccessibility::initialize()
1522void QWasmAccessibility::cleanup()
void QWasmAccessibilityEnable()