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();
59 Q_ASSERT(suspendResume);
60 m_eventHandlerIndex = suspendResume->registerEventHandler([
this](
const emscripten::val event){
61 this->handleEventFromHtmlElement(event);
65QWasmAccessibility::~QWasmAccessibility()
68 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
69 Q_ASSERT(suspendResume);
70 suspendResume->removeEventHandler(m_eventHandlerIndex);
75QWasmAccessibility *QWasmAccessibility::s_instance =
nullptr;
77QWasmAccessibility* QWasmAccessibility::get()
82void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window)
84 get()->addAccessibilityEnableButtonImpl(window);
87void QWasmAccessibility::onShowWindow(QWindow *window)
89 get()->onShowWindowImpl(window);
92void QWasmAccessibility::onRemoveWindow(QWindow *window)
94 get()->onRemoveWindowImpl(window);
97bool QWasmAccessibility::isEnabled()
99 return get()->m_accessibilityEnabled;
101void QWasmAccessibility::enable()
104 get()->enableAccessibility();
107void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
109 if (m_accessibilityEnabled)
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);
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)));
124void QWasmAccessibility::onShowWindowImpl(QWindow *window)
126 if (!m_accessibilityEnabled)
128 populateAccessibilityTree(window->accessibleRoot());
131void QWasmAccessibility::onRemoveWindowImpl(QWindow *window)
134 const auto it = m_enableButtons.find(window);
135 if (it != m_enableButtons.end()) {
137 auto [element, callback] = it->second;
139 element[
"parentElement"].call<
void>(
"removeChild", element);
140 m_enableButtons.erase(it);
144 auto a11yContainer = getA11yContainer(window);
145 auto describedByContainer =
146 getDescribedByContainer(window);
147 auto elementContainer = getElementContainer(window);
148 auto document = getDocument(a11yContainer);
151 if (!describedByContainer.isUndefined()) {
152 a11yContainer.call<
void>(
"removeChild", describedByContainer);
153 describedByContainer = document.call<emscripten::val>(
"createElement", std::string(
"div"));
155 a11yContainer.call<
void>(
"appendChild", elementContainer);
156 a11yContainer.call<
void>(
"appendChild", describedByContainer);
161void QWasmAccessibility::enableAccessibility()
166 Q_ASSERT(!m_accessibilityEnabled);
167 m_accessibilityEnabled =
true;
169 for (
const auto& [key, value] : m_enableButtons) {
170 const auto &[element, callback] = value;
172 if (
auto wasmWindow = QWasmWindow::fromWindow(key))
173 wasmWindow->onAccessibilityEnable();
174 onShowWindowImpl(key);
175 element[
"parentElement"].call<
void>(
"removeChild", element);
177 m_enableButtons.clear();
180bool QWasmAccessibility::isWindowNode(QAccessibleInterface *iface)
182 return (iface && !getWindow(iface->parent()) && getWindow(iface));
185emscripten::val QWasmAccessibility::getA11yContainer(QWindow *window)
187 const auto wasmWindow = QWasmWindow::fromWindow(window);
189 return emscripten::val::undefined();
191 auto a11yContainer = wasmWindow->a11yContainer();
192 if (a11yContainer[
"childElementCount"].as<
unsigned>() == 2)
193 return a11yContainer;
195 Q_ASSERT(a11yContainer[
"childElementCount"].as<
unsigned>() == 0);
197 const auto document = getDocument(a11yContainer);
198 if (document.isUndefined())
199 return emscripten::val::undefined();
201 auto elementContainer = document.call<emscripten::val>(
"createElement", std::string(
"div"));
202 auto describedByContainer = document.call<emscripten::val>(
"createElement", std::string(
"div"));
204 a11yContainer.call<
void>(
"appendChild", elementContainer);
205 a11yContainer.call<
void>(
"appendChild", describedByContainer);
207 return a11yContainer;
210emscripten::val QWasmAccessibility::getA11yContainer(QAccessibleInterface *iface)
212 return getA11yContainer(getWindow(iface));
215emscripten::val QWasmAccessibility::getDescribedByContainer(QWindow *window)
217 auto a11yContainer = getA11yContainer(window);
218 if (a11yContainer.isUndefined())
219 return emscripten ::val::undefined();
221 Q_ASSERT(a11yContainer[
"childElementCount"].as<
unsigned>() == 2);
222 Q_ASSERT(!a11yContainer[
"children"][1].isUndefined());
224 return a11yContainer[
"children"][1];
227emscripten::val QWasmAccessibility::getDescribedByContainer(QAccessibleInterface *iface)
229 return getDescribedByContainer(getWindow(iface));
232emscripten::val QWasmAccessibility::getElementContainer(QWindow *window)
234 auto a11yContainer = getA11yContainer(window);
235 if (a11yContainer.isUndefined())
236 return emscripten ::val::undefined();
238 Q_ASSERT(a11yContainer[
"childElementCount"].as<
unsigned>() == 2);
239 Q_ASSERT(!a11yContainer[
"children"][0].isUndefined());
240 return a11yContainer[
"children"][0];
243emscripten::val QWasmAccessibility::getElementContainer(QAccessibleInterface *iface)
250 if (!getWindow(iface))
251 return emscripten::val::undefined();
253 if (isWindowNode(iface))
254 return emscripten::val::undefined();
256 if (isWindowNode(iface->parent()))
257 return getElementContainer(getWindow(iface->parent()));
260 return getHtmlElement(iface->parent());
263QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
268 QWindow *window = iface->window();
270 if (!window && iface->parent())
271 window = iface->parent()->window();
275emscripten::val QWasmAccessibility::getDocument(
const emscripten::val &container)
277 if (container.isUndefined())
278 return emscripten::val::global(
"document");
279 return container[
"ownerDocument"];
282emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface)
284 return getDocument(getA11yContainer(iface));
287void QWasmAccessibility::setAttribute(emscripten::val element,
const std::string &attr,
288 const std::string &val)
291 element.call<
void>(
"setAttribute", attr, val);
293 element.call<
void>(
"removeAttribute", attr);
296void QWasmAccessibility::setAttribute(emscripten::val element,
const std::string &attr,
299 setAttribute(element, attr, std::string(val));
302void QWasmAccessibility::setAttribute(emscripten::val element,
const std::string &attr,
bool val)
305 element.call<
void>(
"setAttribute", attr, val);
307 element.call<
void>(
"removeAttribute", attr);
310void QWasmAccessibility::setProperty(emscripten::val element,
const std::string &property,
311 const std::string &val)
313 element.set(property, val);
316void QWasmAccessibility::setProperty(emscripten::val element,
const std::string &property,
const char *val)
318 setProperty(element, property, std::string(val));
321void QWasmAccessibility::setProperty(emscripten::val element,
const std::string &property,
bool val)
323 element.set(property, val);
326void QWasmAccessibility::setNamedAttribute(QAccessibleInterface *iface,
const std::string &attribute, QAccessible::Text text)
328 const emscripten::val element = getHtmlElement(iface);
329 setAttribute(element, attribute, iface->text(text).toStdString());
331void QWasmAccessibility::setNamedProperty(QAccessibleInterface *iface,
const std::string &property, QAccessible::Text text)
333 const emscripten::val element = getHtmlElement(iface);
334 setProperty(element, property, iface->text(text).toStdString());
337void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripten::val element,
const char *eventType)
339 element.set(
"data-qta11yinterface",
reinterpret_cast<size_t>(iface));
340 element.call<
void>(
"addEventListener", emscripten::val(eventType),
341 QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex),
345void QWasmAccessibility::sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType)
347 if (iface->object()) {
348 QAccessibleEvent event(iface->object(), eventType);
349 handleUpdateByInterfaceRole(&event);
351 QAccessibleEvent event(iface, eventType);
352 handleUpdateByInterfaceRole(&event);
356emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
361 emscripten::val container = getElementContainer(iface);
365 emscripten::val document = getDocument(container);
370 emscripten::val element = [
this, iface, document] {
372 emscripten::val element = emscripten::val::undefined();
374 switch (iface->role()) {
376 case QAccessible::Button: {
377 element = document.call<emscripten::val>(
"createElement", std::string(
"button"));
378 addEventListener(iface, element,
"click");
381 case QAccessible::Grouping:
382 case QAccessible::CheckBox: {
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");
397 if (iface->childCount() > 0 || iface->role() == QAccessible::Grouping) {
398 auto label = document.call<emscripten::val>(
"createElement", std::string(
"span"));
400 const std::string id = QString::asprintf(
"lbid%p", iface).toStdString();
401 setAttribute(label,
"id", id);
403 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
404 element.call<
void>(
"appendChild", label);
406 setAttribute(element,
"role",
"group");
407 setAttribute(element,
"aria-labelledby", id);
409 if (!checkbox.isUndefined()) {
410 element.call<
void>(
"appendChild", checkbox);
411 addEventListener(iface, checkbox,
"focus");
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");
425 setAttribute(element,
"aria-checked",
"false");
426 addEventListener(iface, element,
"change");
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");
437 case QAccessible::SpinBox:
438 case QAccessible::Slider: {
439 const auto minValue = iface->valueInterface()->minimumValue().toString().toStdString();
440 const auto maxValue = iface->valueInterface()->maximumValue().toString().toStdString();
441 const auto stepValue =
442 iface->valueInterface()->minimumStepSize().toString().toStdString();
443 const auto value = iface->valueInterface()->currentValue().toString().toStdString();
444 element = document.call<emscripten::val>(
"createElement", std::string(
"input"));
445 setAttribute(element,
"type",
"number");
446 setAttribute(element,
"min", minValue);
447 setAttribute(element,
"max", maxValue);
448 setAttribute(element,
"step", stepValue);
449 setProperty(element,
"value", value);
452 case QAccessible::PageTabList:{
453 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
454 setAttribute(element,
"role",
"tablist");
455 setHtmlElementOrientation(element, iface);
457 m_elements[iface] = element;
459 for (
int i = 0; i < iface->childCount(); ++i)
460 createHtmlElement(iface->child(i));
464 case QAccessible::PageTab:{
465 const QString text = iface->text(QAccessible::Name);
466 element = document.call<emscripten::val>(
"createElement", std::string(
"button"));
467 setAttribute(element,
"role",
"tab");
468 setAttribute(element,
"title", text.toStdString());
469 addEventListener(iface, element,
"click");
472 case QAccessible::ScrollBar: {
473 const std::string valueString =
474 iface->valueInterface()->currentValue().toString().toStdString();
475 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
476 setAttribute(element,
"role",
"scrollbar");
477 setAttribute(element,
"aria-valuenow", valueString);
478 setHtmlElementOrientation(element, iface);
479 addEventListener(iface, element,
"change");
482 case QAccessible::StaticText: {
483 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
485 case QAccessible::Dialog: {
486 element = document.call<emscripten::val>(
"createElement", std::string(
"dialog"));
488 case QAccessible::ToolBar:{
489 const QString text = iface->text(QAccessible::Name);
490 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
491 setAttribute(element,
"role",
"toolbar");
492 setAttribute(element,
"title", text.toStdString());
493 setHtmlElementOrientation(element, iface);
494 addEventListener(iface, element,
"click");
496 case QAccessible::MenuItem:
497 case QAccessible::ButtonMenu: {
498 const QString text = iface->text(QAccessible::Name);
499 element = document.call<emscripten::val>(
"createElement", std::string(
"button"));
500 setAttribute(element,
"role",
"menuitem");
501 setAttribute(element,
"title", text.toStdString());
502 addEventListener(iface, element,
"click");
504 case QAccessible::MenuBar:
505 case QAccessible::PopupMenu: {
506 const QString text = iface->text(QAccessible::Name);
507 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
508 setAttribute(element,
"role",
"menubar");
509 setAttribute(element,
"title", text.toStdString());
510 setHtmlElementOrientation(element, iface);
511 m_elements[iface] = element;
513 for (
int i = 0; i < iface->childCount(); ++i) {
514 emscripten::val childElement = createHtmlElement(iface->child(i));
515 setAttribute(childElement,
"aria-owns", text.toStdString());
518 case QAccessible::EditableText: {
519 element = document.call<emscripten::val>(
"createElement", std::string(
"input"));
520 setAttribute(element,
"type",
"text");
521 setAttribute(element,
"contenteditable",
"true");
522 setAttribute(element,
"readonly", iface->state().readOnly);
523 setAttribute(element,
"autocorrect",
"off");
524 setProperty(element,
"inputMode",
"text");
527 qCDebug(lcQpaAccessibility) <<
"TODO: createHtmlElement() handle" << iface->role();
528 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
531 addEventListener(iface, element,
"focus");
536 m_elements[iface] = element;
538 setHtmlElementGeometry(iface);
539 setHtmlElementDisabled(iface);
540 setHtmlElementVisibility(iface, !iface->state().invisible);
541 handleIdentifierUpdate(iface);
542 handleDescriptionChanged(iface);
543 sendEvent(iface, QAccessible::NameChanged);
547 for (
int i = 0; i < iface->childCount(); ++i) {
548 if (!getHtmlElement(iface->child(i)).isUndefined())
549 linkToParent(iface->child(i));
555void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
558 qCDebug(lcQpaAccessibility) <<
"TODO destroyHtmlElement";
561emscripten::val QWasmAccessibility::getHtmlElement(QAccessibleInterface *iface)
563 auto it = m_elements.find(iface);
564 if (it != m_elements.end())
567 return emscripten::val::undefined();
570void QWasmAccessibility::repairLinks(QAccessibleInterface *iface)
575 for (
int i = 0; i < iface->childCount(); ++i) {
576 const auto elementI = getHtmlElement(iface->child(i));
577 const auto containerI = getElementContainer(iface->child(i));
579 if (!elementI.isUndefined() &&
580 !containerI.isUndefined() &&
581 !elementI[
"parentElement"].isUndefined() &&
582 !elementI[
"parentElement"].isNull() &&
583 elementI[
"parentElement"] != containerI) {
589 for (
int i = 0; i < iface->childCount(); ++i) {
590 const auto elementI = getHtmlElement(iface->child(i));
591 const auto containerI = getElementContainer(iface->child(i));
592 if (!elementI.isUndefined() && !containerI.isUndefined())
593 containerI.call<
void>(
"appendChild", elementI);
598void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
600 emscripten::val element = getHtmlElement(iface);
601 emscripten::val container = getElementContainer(iface);
603 if (container.isUndefined() || element.isUndefined())
607 const auto activeElementBefore = emscripten::val::take_ownership(
608 getActiveElement_js(emscripten::val::undefined().as_handle()));
611 repairLinks(iface->parent());
613 emscripten::val next = emscripten::val::undefined();
614 const int thisIndex = iface->parent()->indexOfChild(iface);
615 if (thisIndex >= 0) {
616 Q_ASSERT(thisIndex < iface->parent()->childCount());
617 for (
int i = thisIndex + 1; i < iface->parent()->childCount(); ++i) {
618 const auto elementI = getHtmlElement(iface->parent()->child(i));
619 if (!elementI.isUndefined() &&
620 elementI[
"parentElement"] == container) {
625 if (next.isUndefined())
626 container.call<
void>(
"appendChild", element);
628 container.call<
void>(
"insertBefore", element, next);
630 const auto activeElementAfter = emscripten::val::take_ownership(
631 getActiveElement_js(emscripten::val::undefined().as_handle()));
632 if (activeElementBefore != activeElementAfter) {
633 if (!activeElementBefore.isUndefined() && !activeElementBefore.isNull())
634 activeElementBefore.call<
void>(
"focus");
638void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface,
bool visible)
640 emscripten::val element = getHtmlElement(iface);
641 setAttribute(element,
"aria-hidden", !visible);
644void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
646 const emscripten::val element = getHtmlElement(iface);
648 QRect windowGeometry = iface->rect();
649 if (iface->parent()) {
653 const QRect parentRect = iface->parent()->rect();
654 const QRect thisRect = iface->rect();
655 const QRect result(thisRect.topLeft() - parentRect.topLeft(), thisRect.size());
656 windowGeometry = result;
660 Q_ASSERT(!getWindow(iface));
662 setHtmlElementGeometry(element, windowGeometry);
665void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
669 emscripten::val style = element[
"style"];
670 style.set(
"position", std::string(
"absolute"));
671 style.set(
"z-index", std::string(
"-1"));
673 style.set(
"left", std::to_string(geometry.x()) +
"px");
674 style.set(
"top", std::to_string(geometry.y()) +
"px");
675 style.set(
"width", std::to_string(geometry.width()) +
"px");
676 style.set(
"height", std::to_string(geometry.height()) +
"px");
679void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
681 const auto element = getHtmlElement(iface);
682 element.call<
void>(
"focus");
685void QWasmAccessibility::setHtmlElementDisabled(QAccessibleInterface *iface)
687 auto element = getHtmlElement(iface);
688 setAttribute(element,
"aria-disabled", iface->state().disabled);
691void QWasmAccessibility::setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface)
694 if (QAccessibleAttributesInterface *attributesIface = iface->attributesInterface()) {
695 const QVariant orientationVariant =
696 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
697 if (orientationVariant.isValid()) {
698 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
699 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
700 const std::string value = orientation == Qt::Horizontal ?
"horizontal" :
"vertical";
701 setAttribute(element,
"aria-orientation", value);
706void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
708 switch (event->type()) {
709 case QAccessible::NameChanged: {
711 setNamedProperty(event->accessibleInterface(),
"innerText", QAccessible::Name);
714 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleStaticTextUpdate for event" << event->type();
719void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
721 switch (event->type()) {
722 case QAccessible::StateChanged: {
723 auto iface = event->accessibleInterface();
724 auto element = getHtmlElement(iface);
725 setAttribute(element,
"readonly", iface->state().readOnly);
726 if (iface->state().passwordEdit)
727 setProperty(element,
"type",
"password");
729 setProperty(element,
"type",
"text");
731 case QAccessible::ValueChanged:
732 case QAccessible::NameChanged: {
733 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
735 case QAccessible::ObjectShow:
736 case QAccessible::Focus: {
737 auto iface = event->accessibleInterface();
738 auto element = getHtmlElement(iface);
739 if (!element.isUndefined()) {
740 setAttribute(element,
"readonly", iface->state().readOnly);
741 if (iface->state().passwordEdit)
742 setProperty(element,
"type",
"password");
744 setProperty(element,
"type",
"text");
746 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
748 case QAccessible::TextRemoved:
749 case QAccessible::TextInserted: {
750 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
752 case QAccessible::TextCaretMoved: {
754 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
757 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleLineEditUpdate for event" << event->type();
762void QWasmAccessibility::handleEventFromHtmlElement(
const emscripten::val event)
764 if (event[
"target"].isNull() || event[
"target"].isUndefined())
767 if (event[
"target"][
"data-qta11yinterface"].isNull() || event[
"target"][
"data-qta11yinterface"].isUndefined())
770 auto iface =
reinterpret_cast<QAccessibleInterface *>(event[
"target"][
"data-qta11yinterface"].as<size_t>());
771 if (m_elements.find(iface) == m_elements.end())
774 const QString eventType = QString::fromStdString(event[
"type"].as<std::string>());
775 const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
777 if (eventType ==
"focus") {
778 if (actionNames.contains(QAccessibleActionInterface::setFocusAction()))
779 iface->actionInterface()->doAction(QAccessibleActionInterface::setFocusAction());
780 }
else if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
781 iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
782 }
else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) {
783 iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
787void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
789 switch (event->type()) {
790 case QAccessible::Focus:
791 case QAccessible::NameChanged: {
792 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
795 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
800void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
802 switch (event->type()) {
803 case QAccessible::Focus:
804 case QAccessible::NameChanged: {
805 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
807 case QAccessible::StateChanged: {
808 QAccessibleInterface *accessible = event->accessibleInterface();
809 const emscripten::val element = getHtmlElement(accessible);
810 setAttribute(element,
"checked", accessible->state().checked);
811 setProperty(element,
"indeterminate", accessible->state().checkStateMixed);
814 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
819void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event)
821 QAccessibleInterface *iface = event->accessibleInterface();
823 emscripten::val parent = getHtmlElement(iface);
824 emscripten::val label = parent[
"children"][0];
825 emscripten::val checkbox = emscripten::val::undefined();
826 if (iface->role() == QAccessible::CheckBox)
827 checkbox = parent[
"children"][1];
829 switch (event->type()) {
830 case QAccessible::Focus:
831 case QAccessible::NameChanged: {
832 setProperty(label,
"innerText", iface->text(QAccessible::Name).toStdString());
833 if (!checkbox.isUndefined())
834 setAttribute(checkbox,
"aria-label", iface->text(QAccessible::Name).toStdString());
836 case QAccessible::StateChanged: {
837 if (!checkbox.isUndefined()) {
838 setAttribute(checkbox,
"checked", iface->state().checked);
839 setProperty(checkbox,
"indeterminate", iface->state().checkStateMixed);
843 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
848void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
850 switch (event->type()) {
851 case QAccessible::Focus:
852 case QAccessible::NameChanged: {
854 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
856 case QAccessible::StateChanged: {
857 QAccessibleInterface *accessible = event->accessibleInterface();
858 const emscripten::val element = getHtmlElement(accessible);
859 if (accessible->state().checked)
860 setAttribute(element,
"aria-checked",
"true");
862 setAttribute(element,
"aria-checked",
"false");
865 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleSwitchUpdate for event" << event->type();
870void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
872 QAccessibleInterface *iface = event->accessibleInterface();
873 QString text = iface->text(QAccessible::Name);
874 QString desc = iface->text(QAccessible::Description);
875 switch (event->type()) {
876 case QAccessible::NameChanged:
877 case QAccessible::StateChanged:{
878 const emscripten::val element = getHtmlElement(iface);
879 setAttribute(element,
"title", text.toStdString());
882 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleToolUpdate for event" << event->type();
886void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
888 QAccessibleInterface *iface = event->accessibleInterface();
889 QString text = iface->text(QAccessible::Name);
890 QString desc = iface->text(QAccessible::Description);
891 switch (event->type()) {
892 case QAccessible::Focus:
893 case QAccessible::NameChanged:
894 case QAccessible::MenuStart :
895 case QAccessible::StateChanged:{
896 const emscripten::val element = getHtmlElement(iface);
897 setAttribute(element,
"title", text.toStdString());
899 case QAccessible::PopupMenuStart: {
900 if (iface->childCount() > 0) {
901 const auto childElement = getHtmlElement(iface->child(0));
902 childElement.call<
void>(
"focus");
906 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleMenuUpdate for event" << event->type();
910void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
912 switch (event->type()) {
913 case QAccessible::NameChanged:
914 case QAccessible::Focus:
915 case QAccessible::DialogStart:
916 case QAccessible::StateChanged: {
917 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
920 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleLineEditUpdate for event" << event->type();
925void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
932 const QWindow *window1 = getWindow(iface);
933 const QWindow *window0 = (iface->parent()) ? getWindow(iface->parent()) :
nullptr;
935 if (window1 && window0 == window1) {
937 bool exists = !getHtmlElement(iface).isUndefined();
939 exists = !createHtmlElement(iface).isUndefined();
943 setHtmlElementVisibility(iface, !iface->state().invisible);
944 setHtmlElementGeometry(iface);
945 setHtmlElementDisabled(iface);
946 handleIdentifierUpdate(iface);
947 handleDescriptionChanged(iface);
948 sendEvent(iface, QAccessible::NameChanged);
951 for (
int i = 0; i < iface->childCount(); ++i)
952 populateAccessibilityTree(iface->child(i));
955void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
957 switch (event->type()) {
958 case QAccessible::Focus:
959 case QAccessible::NameChanged: {
960 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
962 case QAccessible::StateChanged: {
963 QAccessibleInterface *accessible = event->accessibleInterface();
964 const emscripten::val element = getHtmlElement(accessible);
965 setAttribute(element,
"checked", accessible->state().checked);
968 qDebug() <<
"TODO: implement handleRadioButtonUpdate for event" << event->type();
973void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
975 switch (event->type()) {
976 case QAccessible::ObjectCreated:
977 case QAccessible::StateChanged: {
979 case QAccessible::Focus:
980 case QAccessible::NameChanged: {
981 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
983 case QAccessible::ValueChanged: {
984 QAccessibleInterface *accessible = event->accessibleInterface();
985 const emscripten::val element = getHtmlElement(accessible);
986 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
987 setProperty(element,
"value", valueString);
990 qDebug() <<
"TODO: implement handleSpinBoxUpdate for event" << event->type();
995void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
997 switch (event->type()) {
998 case QAccessible::ObjectCreated:
999 case QAccessible::StateChanged: {
1001 case QAccessible::Focus:
1002 case QAccessible::NameChanged: {
1003 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1005 case QAccessible::ValueChanged: {
1006 QAccessibleInterface *accessible = event->accessibleInterface();
1007 const emscripten::val element = getHtmlElement(accessible);
1008 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1009 setProperty(element,
"value", valueString);
1012 qDebug() <<
"TODO: implement handleSliderUpdate for event" << event->type();
1017void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
1019 switch (event->type()) {
1020 case QAccessible::Focus:
1021 case QAccessible::NameChanged: {
1022 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1024 case QAccessible::ValueChanged: {
1025 QAccessibleInterface *accessible = event->accessibleInterface();
1026 const emscripten::val element = getHtmlElement(accessible);
1027 std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
1028 setAttribute(element,
"aria-valuenow", valueString);
1031 qDebug() <<
"TODO: implement handleSliderUpdate for event" << event->type();
1037void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
1039 switch (event->type()) {
1040 case QAccessible::NameChanged: {
1041 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1043 case QAccessible::Focus: {
1044 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1047 qDebug() <<
"TODO: implement handlePageTabUpdate for event" << event->type();
1052void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
1054 switch (event->type()) {
1055 case QAccessible::NameChanged: {
1056 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1058 case QAccessible::Focus: {
1059 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1062 qDebug() <<
"TODO: implement handlePageTabUpdate for event" << event->type();
1067void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
1069 const emscripten::val element = getHtmlElement(iface);
1070 QString id = iface->text(QAccessible::Identifier).replace(
" ",
"_");
1071 if (id.isEmpty() && iface->role() == QAccessible::PageTabList) {
1072 std::ostringstream oss;
1073 oss <<
"tabList_0x" << (
void *)iface;
1074 id = QString::fromUtf8(oss.str());
1077 setAttribute(element,
"id", id.toStdString());
1078 if (!id.isEmpty()) {
1079 if (iface->role() == QAccessible::PageTabList) {
1080 for (
int i = 0; i < iface->childCount(); ++i) {
1081 const auto child = getHtmlElement(iface->child(i));
1082 setAttribute(child,
"aria-owns", id.toStdString());
1088void QWasmAccessibility::handleDescriptionChanged(QAccessibleInterface *iface)
1090 const auto desc = iface->text(QAccessible::Description).toStdString();
1091 auto element = getHtmlElement(iface);
1092 auto container = getDescribedByContainer(iface);
1093 if (!container.isUndefined()) {
1094 std::ostringstream oss;
1095 oss <<
"dbid_" << (
void *)iface;
1096 auto id = oss.str();
1098 auto describedBy = container.call<emscripten::val>(
"querySelector",
"#" + std::string(id));
1100 if (!describedBy.isUndefined() && !describedBy.isNull()) {
1101 container.call<
void>(
"removeChild", describedBy);
1103 setAttribute(element,
"aria-describedby",
"");
1105 if (describedBy.isUndefined() || describedBy.isNull()) {
1106 auto document = getDocument(container);
1107 describedBy = document.call<emscripten::val>(
"createElement", std::string(
"p"));
1109 container.call<
void>(
"appendChild", describedBy);
1111 setAttribute(describedBy,
"id", id);
1112 setAttribute(describedBy,
"aria-hidden",
true);
1113 setAttribute(element,
"aria-describedby", id);
1114 setProperty(describedBy,
"innerText", desc);
1119void QWasmAccessibility::createObject(QAccessibleInterface *iface)
1121 if (getHtmlElement(iface).isUndefined())
1122 createHtmlElement(iface);
1125void QWasmAccessibility::removeObject(QAccessibleInterface *iface)
1132 const auto it = m_elements.find(iface);
1133 if (it != m_elements.end()) {
1134 auto element = it.value();
1135 auto container = getDescribedByContainer(iface);
1136 if (!container.isUndefined()) {
1137 std::ostringstream oss;
1138 oss <<
"dbid_" << (
void *)iface;
1139 auto id = oss.str();
1140 auto describedBy = container.call<emscripten::val>(
"querySelector",
"#" + std::string(id));
1141 if (!describedBy.isUndefined() && !describedBy.isNull() &&
1142 !describedBy[
"parentElement"].isUndefined() && !describedBy[
"parentElement"].isNull())
1143 describedBy[
"parentElement"].call<
void>(
"removeChild", describedBy);
1145 if (!element[
"parentElement"].isUndefined() && !element[
"parentElement"].isNull())
1146 element[
"parentElement"].call<
void>(
"removeChild", element);
1147 m_elements.erase(it);
1151void QWasmAccessibility::unlinkParentForChildren(QAccessibleInterface *iface)
1153 auto element = getHtmlElement(iface);
1154 if (!element.isUndefined()) {
1155 auto oldContainer = element[
"parentElement"];
1156 auto newContainer = getElementContainer(iface);
1157 if (!oldContainer.isUndefined() &&
1158 !oldContainer.isNull() &&
1159 oldContainer != newContainer) {
1160 oldContainer.call<
void>(
"removeChild", element);
1163 for (
int i = 0; i < iface->childCount(); ++i)
1164 unlinkParentForChildren(iface->child(i));
1167void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
1169 auto element = getHtmlElement(iface);
1170 if (!element.isUndefined()) {
1171 if (element[
"parentElement"].isUndefined() ||
1172 element[
"parentElement"].isNull()) {
1173 linkToParent(iface);
1176 for (
int i = 0; i < iface->childCount(); ++i)
1177 relinkParentForChildren(iface->child(i));
1180void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
1182 if (handleUpdateByEventType(event))
1183 handleUpdateByInterfaceRole(event);
1186bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
1188 if (!m_accessibilityEnabled)
1191 QAccessibleInterface *iface = event->accessibleInterface();
1193 qWarning() <<
"handleUpdateByEventType with null a11y interface" << event->type() << event->object();
1198 switch (event->type()) {
1199 case QAccessible::ObjectCreated:
1204 case QAccessible::ObjectDestroyed:
1207 removeObject(iface);
1210 case QAccessible::ObjectShow:
1211 createObject(iface);
1214 case QAccessible::ParentChanged:
1215 unlinkParentForChildren(iface);
1216 relinkParentForChildren(iface);
1223 if (getHtmlElement(iface).isUndefined())
1228 switch (event->type()) {
1229 case QAccessible::StateChanged: {
1230 QAccessibleStateChangeEvent *stateChangeEvent = (QAccessibleStateChangeEvent *)event;
1231 if (stateChangeEvent->changedStates().disabled)
1232 setHtmlElementDisabled(iface);
1235 case QAccessible::DescriptionChanged:
1236 handleDescriptionChanged(iface);
1239 case QAccessible::Focus:
1242 setHtmlElementGeometry(iface);
1243 setHtmlElementFocus(iface);
1246 case QAccessible::IdentifierChanged:
1247 handleIdentifierUpdate(iface);
1250 case QAccessible::ObjectShow:
1251 linkToParent(iface);
1252 setHtmlElementVisibility(iface,
true);
1255 setHtmlElementGeometry(iface);
1256 sendEvent(iface, QAccessible::NameChanged);
1259 case QAccessible::ObjectHide:
1260 linkToParent(iface);
1261 setHtmlElementVisibility(iface,
false);
1264 case QAccessible::LocationChanged:
1265 setHtmlElementGeometry(iface);
1276void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
1278 if (!m_accessibilityEnabled)
1281 QAccessibleInterface *iface = event->accessibleInterface();
1283 qWarning() <<
"handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
1289 switch (iface->role()) {
1290 case QAccessible::StaticText:
1291 handleStaticTextUpdate(event);
1293 case QAccessible::Button:
1294 handleButtonUpdate(event);
1296 case QAccessible::CheckBox:
1297 if (iface->childCount() > 0)
1298 handleGroupBoxUpdate(event);
1300 handleCheckBoxUpdate(event);
1302 case QAccessible::Switch:
1303 handleSwitchUpdate(event);
1305 case QAccessible::EditableText:
1306 handleLineEditUpdate(event);
1308 case QAccessible::Dialog:
1309 handleDialogUpdate(event);
1311 case QAccessible::MenuItem:
1312 case QAccessible::MenuBar:
1313 case QAccessible::PopupMenu:
1314 handleMenuUpdate(event);
1316 case QAccessible::ToolBar:
1317 case QAccessible::ButtonMenu:
1318 handleToolUpdate(event);
1319 case QAccessible::RadioButton:
1320 handleRadioButtonUpdate(event);
1322 case QAccessible::SpinBox:
1323 handleSpinBoxUpdate(event);
1325 case QAccessible::Slider:
1326 handleSliderUpdate(event);
1328 case QAccessible::PageTab:
1329 handlePageTabUpdate(event);
1331 case QAccessible::PageTabList:
1332 handlePageTabListUpdate(event);
1334 case QAccessible::ScrollBar:
1335 handleScrollBarUpdate(event);
1337 case QAccessible::Grouping:
1338 handleGroupBoxUpdate(event);
1341 qCDebug(lcQpaAccessibility) <<
"TODO: implement notifyAccessibilityUpdate for role" << iface->role();
1345void QWasmAccessibility::setRootObject(QObject *root)
1347 m_rootObject = root;
1350void QWasmAccessibility::initialize()
1355void QWasmAccessibility::cleanup()
void QWasmAccessibilityEnable()