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::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");
454 case QAccessible::PageTabList:{
455 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
456 setAttribute(element,
"role",
"tablist");
457 setHtmlElementOrientation(element, iface);
459 m_elements[iface] = element;
461 for (
int i = 0; i < iface->childCount(); ++i)
462 createHtmlElement(iface->child(i));
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");
474 case QAccessible::ScrollBar: {
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");
490 case QAccessible::StaticText: {
491 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
493 case QAccessible::Dialog: {
494 element = document.call<emscripten::val>(
"createElement", std::string(
"dialog"));
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");
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");
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;
522 for (
int i = 0; i < iface->childCount(); ++i) {
523 emscripten::val childElement = createHtmlElement(iface->child(i));
524 setAttribute(childElement,
"aria-owns", text.toStdString());
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");
535 case QAccessible::List: {
536 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
537 setAttribute(element,
"tabindex",
"0");
538 setAttribute(element,
"role",
"list");
540 case QAccessible::ListItem: {
541 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
542 setAttribute(element,
"tabindex",
"0");
543 setAttribute(element,
"role",
"listitem");
546 qCDebug(lcQpaAccessibility) <<
"TODO: createHtmlElement() handle" << iface->role();
547 element = document.call<emscripten::val>(
"createElement", std::string(
"div"));
550 addEventListener(iface, element,
"focus");
555 m_elements[iface] = element;
557 setHtmlElementGeometry(iface);
558 setHtmlElementDisabled(iface);
559 setHtmlElementVisibility(iface, !iface->state().invisible);
560 handleIdentifierUpdate(iface);
561 handleDescriptionChanged(iface);
562 sendEvent(iface, QAccessible::NameChanged);
566 for (
int i = 0; i < iface->childCount(); ++i) {
567 if (!getHtmlElement(iface->child(i)).isUndefined())
568 linkToParent(iface->child(i));
574void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
577 qCDebug(lcQpaAccessibility) <<
"TODO destroyHtmlElement";
580emscripten::val QWasmAccessibility::getHtmlElement(QAccessibleInterface *iface)
582 auto it = m_elements.find(iface);
583 if (it != m_elements.end())
586 return emscripten::val::undefined();
589void QWasmAccessibility::repairLinks(QAccessibleInterface *iface)
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));
598 if (!elementI.isUndefined() &&
599 !containerI.isUndefined() &&
600 !elementI[
"parentElement"].isUndefined() &&
601 !elementI[
"parentElement"].isNull() &&
602 elementI[
"parentElement"] != containerI) {
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);
617void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
619 emscripten::val element = getHtmlElement(iface);
620 emscripten::val container = getElementContainer(iface);
622 if (container.isUndefined() || element.isUndefined())
626 const auto activeElementBefore = emscripten::val::take_ownership(
627 getActiveElement_js(emscripten::val::undefined().as_handle()));
630 repairLinks(iface->parent());
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) {
644 if (next.isUndefined())
645 container.call<
void>(
"appendChild", element);
647 container.call<
void>(
"insertBefore", element, next);
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");
657void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface,
bool visible)
659 emscripten::val element = getHtmlElement(iface);
660 setAttribute(element,
"inert", !visible);
663void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
665 const emscripten::val element = getHtmlElement(iface);
667 QRect windowGeometry = iface->rect();
668 if (iface->parent()) {
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;
679 Q_ASSERT(!getWindow(iface));
681 setHtmlElementGeometry(element, windowGeometry);
684void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
688 emscripten::val style = element[
"style"];
689 style.set(
"position", std::string(
"absolute"));
690 style.set(
"z-index", std::string(
"-1"));
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");
698void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
700 const auto element = getHtmlElement(iface);
701 element.call<
void>(
"focus");
704void QWasmAccessibility::setHtmlElementDisabled(QAccessibleInterface *iface)
706 auto element = getHtmlElement(iface);
707 setAttribute(element,
"aria-disabled", iface->state().disabled);
710void QWasmAccessibility::setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *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);
725void QWasmAccessibility::handleListItemUpdate(QAccessibleEvent *event)
727 auto iface = event->accessibleInterface();
729 switch (event->type()) {
730 case QAccessible::NameChanged: {
732 setNamedProperty(iface,
"innerText", QAccessible::Name);
735 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleListItemUpdate for event" << event->type();
740void QWasmAccessibility::handleDialUpdate(QAccessibleEvent *event)
742 switch (event->type()) {
743 case QAccessible::ObjectCreated:
744 case QAccessible::StateChanged: {
746 case QAccessible::Focus:
747 case QAccessible::NameChanged: {
748 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
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);
757 qDebug() <<
"TODO: implement handleSpinBoxUpdate for event" << event->type();
762void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
764 switch (event->type()) {
765 case QAccessible::NameChanged: {
767 setNamedProperty(event->accessibleInterface(),
"innerText", QAccessible::Name);
770 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleStaticTextUpdate for event" << event->type();
775void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
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");
785 setProperty(element,
"type",
"text");
787 case QAccessible::ValueChanged:
788 case QAccessible::NameChanged: {
789 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
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");
800 setProperty(element,
"type",
"text");
802 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
804 case QAccessible::TextRemoved:
805 case QAccessible::TextInserted: {
806 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
808 case QAccessible::TextCaretMoved: {
810 setNamedProperty(event->accessibleInterface(),
"value", QAccessible::Value);
813 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleLineEditUpdate for event" << event->type();
818void QWasmAccessibility::handleEventFromHtmlElement(
const emscripten::val event)
820 if (event[
"target"].isNull() || event[
"target"].isUndefined())
823 if (event[
"target"][
"data-qta11yinterface"].isNull() || event[
"target"][
"data-qta11yinterface"].isUndefined())
826 auto iface =
reinterpret_cast<QAccessibleInterface *>(event[
"target"][
"data-qta11yinterface"].as<size_t>());
827 if (m_elements.find(iface) == m_elements.end())
830 const auto eventType = QString::fromStdString(event[
"type"].as<std::string>());
831 const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
832 bool handled =
false;
834 if (eventType ==
"blur") {
836 }
else if (eventType ==
"keydown") {
838 }
else if (eventType ==
"keyup") {
840 }
else if (eventType ==
"focus") {
841 if (actionNames.contains(QAccessibleActionInterface::setFocusAction()))
842 iface->actionInterface()->doAction(QAccessibleActionInterface::setFocusAction());
843 }
else if (eventType ==
"click") {
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") {
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());
858 qWarning() <<
" Unknown event" << eventType;
862 event.call<
void>(
"preventDefault");
863 event.call<
void>(
"stopPropagation");
867void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
869 switch (event->type()) {
870 case QAccessible::Focus:
871 case QAccessible::NameChanged: {
872 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
875 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
880void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
882 switch (event->type()) {
883 case QAccessible::Focus:
884 case QAccessible::NameChanged: {
885 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
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);
894 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
899void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event)
901 QAccessibleInterface *iface = event->accessibleInterface();
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];
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());
916 case QAccessible::StateChanged: {
917 if (!checkbox.isUndefined()) {
918 setAttribute(checkbox,
"checked", iface->state().checked);
919 setProperty(checkbox,
"indeterminate", iface->state().checkStateMixed);
923 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleCheckBoxUpdate for event" << event->type();
928void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
930 switch (event->type()) {
931 case QAccessible::Focus:
932 case QAccessible::NameChanged: {
934 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
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");
942 setAttribute(element,
"aria-checked",
"false");
945 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleSwitchUpdate for event" << event->type();
950void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
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());
962 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleToolUpdate for event" << event->type();
966void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
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 :
975 case QAccessible::StateChanged:{
976 const emscripten::val element = getHtmlElement(iface);
977 setAttribute(element,
"title", text.toStdString());
979 case QAccessible::PopupMenuStart: {
980 if (iface->childCount() > 0) {
981 const auto childElement = getHtmlElement(iface->child(0));
982 childElement.call<
void>(
"focus");
986 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleMenuUpdate for event" << event->type();
990void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
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);
1000 qCDebug(lcQpaAccessibility) <<
"TODO: implement handleLineEditUpdate for event" << event->type();
1005void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
1012 const QWindow *window1 = getWindow(iface);
1013 const QWindow *window0 = (iface->parent()) ? getWindow(iface->parent()) :
nullptr;
1015 if (window1 && window0 == window1) {
1017 bool exists = !getHtmlElement(iface).isUndefined();
1019 exists = !createHtmlElement(iface).isUndefined();
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);
1031 for (
int i = 0; i < iface->childCount(); ++i)
1032 populateAccessibilityTree(iface->child(i));
1035void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
1037 switch (event->type()) {
1038 case QAccessible::Focus:
1039 case QAccessible::NameChanged: {
1040 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1042 case QAccessible::StateChanged: {
1043 QAccessibleInterface *accessible = event->accessibleInterface();
1044 const emscripten::val element = getHtmlElement(accessible);
1045 setAttribute(element,
"checked", accessible->state().checked);
1048 qDebug() <<
"TODO: implement handleRadioButtonUpdate for event" << event->type();
1053void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
1055 switch (event->type()) {
1056 case QAccessible::ObjectCreated:
1057 case QAccessible::StateChanged: {
1059 case QAccessible::Focus:
1060 case QAccessible::NameChanged: {
1061 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
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);
1070 qDebug() <<
"TODO: implement handleSpinBoxUpdate for event" << event->type();
1075void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
1077 switch (event->type()) {
1078 case QAccessible::ObjectCreated:
1079 case QAccessible::StateChanged: {
1081 case QAccessible::Focus:
1082 case QAccessible::NameChanged: {
1083 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
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);
1092 qDebug() <<
"TODO: implement handleSliderUpdate for event" << event->type();
1097void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
1099 switch (event->type()) {
1100 case QAccessible::Focus:
1101 case QAccessible::NameChanged: {
1102 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
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);
1111 qDebug() <<
"TODO: implement handleSliderUpdate for event" << event->type();
1117void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
1119 switch (event->type()) {
1120 case QAccessible::NameChanged: {
1121 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1123 case QAccessible::Focus: {
1124 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1127 qDebug() <<
"TODO: implement handlePageTabUpdate for event" << event->type();
1132void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
1134 switch (event->type()) {
1135 case QAccessible::NameChanged: {
1136 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1138 case QAccessible::Focus: {
1139 setNamedAttribute(event->accessibleInterface(),
"aria-label", QAccessible::Name);
1142 qDebug() <<
"TODO: implement handlePageTabUpdate for event" << event->type();
1147void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
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());
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());
1168void QWasmAccessibility::handleDescriptionChanged(QAccessibleInterface *iface)
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();
1178 auto describedBy = container.call<emscripten::val>(
"querySelector",
"#" + std::string(id));
1180 if (!describedBy.isUndefined() && !describedBy.isNull()) {
1181 container.call<
void>(
"removeChild", describedBy);
1183 setAttribute(element,
"aria-describedby",
"");
1185 if (describedBy.isUndefined() || describedBy.isNull()) {
1186 auto document = getDocument(container);
1187 describedBy = document.call<emscripten::val>(
"createElement", std::string(
"p"));
1189 container.call<
void>(
"appendChild", describedBy);
1191 setAttribute(describedBy,
"id", id);
1192 setAttribute(describedBy,
"inert",
true);
1193 setAttribute(element,
"aria-describedby", id);
1194 setProperty(describedBy,
"innerText", desc);
1199void QWasmAccessibility::createObject(QAccessibleInterface *iface)
1201 if (getHtmlElement(iface).isUndefined())
1202 createHtmlElement(iface);
1205void QWasmAccessibility::removeObject(QAccessibleInterface *iface)
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);
1225 if (!element[
"parentElement"].isUndefined() && !element[
"parentElement"].isNull())
1226 element[
"parentElement"].call<
void>(
"removeChild", element);
1227 m_elements.erase(it);
1231void QWasmAccessibility::unlinkParentForChildren(QAccessibleInterface *iface)
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);
1243 for (
int i = 0; i < iface->childCount(); ++i)
1244 unlinkParentForChildren(iface->child(i));
1247void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
1249 auto element = getHtmlElement(iface);
1250 if (!element.isUndefined()) {
1251 if (element[
"parentElement"].isUndefined() ||
1252 element[
"parentElement"].isNull()) {
1253 linkToParent(iface);
1256 for (
int i = 0; i < iface->childCount(); ++i)
1257 relinkParentForChildren(iface->child(i));
1260void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
1262 if (handleUpdateByEventType(event))
1263 handleUpdateByInterfaceRole(event);
1266bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
1268 if (!m_accessibilityEnabled)
1271 QAccessibleInterface *iface = event->accessibleInterface();
1273 qWarning() <<
"handleUpdateByEventType with null a11y interface" << event->type() << event->object();
1278 switch (event->type()) {
1279 case QAccessible::ObjectCreated:
1284 case QAccessible::ObjectDestroyed:
1287 removeObject(iface);
1290 case QAccessible::ObjectShow:
1291 createObject(iface);
1294 case QAccessible::ParentChanged:
1295 unlinkParentForChildren(iface);
1296 relinkParentForChildren(iface);
1303 if (getHtmlElement(iface).isUndefined())
1308 switch (event->type()) {
1309 case QAccessible::StateChanged: {
1310 QAccessibleStateChangeEvent *stateChangeEvent = (QAccessibleStateChangeEvent *)event;
1311 if (stateChangeEvent->changedStates().disabled)
1312 setHtmlElementDisabled(iface);
1315 case QAccessible::DescriptionChanged:
1316 handleDescriptionChanged(iface);
1319 case QAccessible::Focus:
1322 setHtmlElementGeometry(iface);
1323 setHtmlElementFocus(iface);
1326 case QAccessible::IdentifierChanged:
1327 handleIdentifierUpdate(iface);
1330 case QAccessible::ObjectShow:
1331 linkToParent(iface);
1332 setHtmlElementVisibility(iface,
true);
1335 setHtmlElementGeometry(iface);
1336 sendEvent(iface, QAccessible::NameChanged);
1339 case QAccessible::ObjectHide:
1341 setHtmlElementVisibility(iface,
false);
1344 case QAccessible::LocationChanged:
1345 setHtmlElementGeometry(iface);
1356void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
1358 if (!m_accessibilityEnabled)
1361 QAccessibleInterface *iface = event->accessibleInterface();
1363 qWarning() <<
"handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
1369 switch (iface->role()) {
1370 case QAccessible::List:
1372 case QAccessible::ListItem:
1373 handleListItemUpdate(event);
1375 case QAccessible::StaticText:
1376 handleStaticTextUpdate(event);
1378 case QAccessible::Button:
1379 handleButtonUpdate(event);
1381 case QAccessible::CheckBox:
1382 if (iface->childCount() > 0)
1383 handleGroupBoxUpdate(event);
1385 handleCheckBoxUpdate(event);
1387 case QAccessible::Switch:
1388 handleSwitchUpdate(event);
1390 case QAccessible::EditableText:
1391 handleLineEditUpdate(event);
1393 case QAccessible::Dialog:
1394 handleDialogUpdate(event);
1396 case QAccessible::MenuItem:
1397 case QAccessible::MenuBar:
1398 case QAccessible::PopupMenu:
1399 handleMenuUpdate(event);
1401 case QAccessible::ToolBar:
1402 case QAccessible::ButtonMenu:
1403 handleToolUpdate(event);
1404 case QAccessible::RadioButton:
1405 handleRadioButtonUpdate(event);
1407 case QAccessible::Dial:
1408 handleDialUpdate(event);
1410 case QAccessible::SpinBox:
1411 handleSpinBoxUpdate(event);
1413 case QAccessible::Slider:
1414 handleSliderUpdate(event);
1416 case QAccessible::PageTab:
1417 handlePageTabUpdate(event);
1419 case QAccessible::PageTabList:
1420 handlePageTabListUpdate(event);
1422 case QAccessible::ScrollBar:
1423 handleScrollBarUpdate(event);
1425 case QAccessible::Grouping:
1426 handleGroupBoxUpdate(event);
1429 qCDebug(lcQpaAccessibility) <<
"TODO: implement notifyAccessibilityUpdate for role" << iface->role();
1433void QWasmAccessibility::setRootObject(QObject *root)
1435 m_rootObject = root;
1438void QWasmAccessibility::initialize()
1443void QWasmAccessibility::cleanup()
void QWasmAccessibilityEnable()