wasm: a11y - refactor set/get attribute/property

Add setAttribute, setProperty, addEventHandler functions to reduce
linelength and improve readability.

Task-number: QTBUG-134657
Change-Id: I7deb1ac13872291b848c191198d741669d852e3a
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Even Oscar Andersen 2025-03-27 14:43:31 +01:00
parent 591dccddbb
commit d817747f8d
2 changed files with 126 additions and 90 deletions

View File

@ -81,7 +81,7 @@ void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
emscripten::val container = getContainer(window); emscripten::val container = getContainer(window);
emscripten::val document = getDocument(container); emscripten::val document = getDocument(container);
emscripten::val button = document.call<emscripten::val>("createElement", std::string("button")); emscripten::val button = document.call<emscripten::val>("createElement", std::string("button"));
button.set("innerText", std::string("Enable Screen Reader")); setProperty(button, "innerText", "Enable Screen Reader");
button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader")); button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader"));
container.call<void>("appendChild", button); container.call<void>("appendChild", button);
@ -163,6 +163,53 @@ emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface)
return getDocument(getContainer(iface)); return getDocument(getContainer(iface));
} }
void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr,
const std::string &val)
{
if (val != "")
element.call<void>("setAttribute", attr, val);
else
element.call<void>("removeAttribute", attr);
}
void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr,
const char *val)
{
setAttribute(element, attr, std::string(val));
}
void QWasmAccessibility::setAttribute(emscripten::val element, const std::string &attr, bool val)
{
if (val)
element.call<void>("setAttribute", attr, val);
else
element.call<void>("removeAttribute", attr);
}
void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property,
const std::string &val)
{
element.set(property, val);
}
void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property, const char *val)
{
setProperty(element, property, std::string(val));
}
void QWasmAccessibility::setProperty(emscripten::val element, const std::string &property, bool val)
{
element.set(property, val);
}
void QWasmAccessibility::addEventListener(emscripten::val element, const char *eventType)
{
element.call<void>("addEventListener", emscripten::val(eventType),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex),
true);
}
emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface) emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
{ {
// Get the html container element for the interface; this depends on which // Get the html container element for the interface; this depends on which
@ -185,54 +232,45 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
case QAccessible::Button: { case QAccessible::Button: {
element = document.call<emscripten::val>("createElement", std::string("button")); element = document.call<emscripten::val>("createElement", std::string("button"));
addEventListener(element, "click");
element.call<void>("addEventListener", emscripten::val("click"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::CheckBox: { case QAccessible::CheckBox: {
element = document.call<emscripten::val>("createElement", std::string("input")); element = document.call<emscripten::val>("createElement", std::string("input"));
element.call<void>("setAttribute", std::string("type"), std::string("checkbox")); setAttribute(element, "type", "checkbox");
if (iface->state().checked) { setAttribute(element, "checked", iface->state().checked);
element.call<void>("setAttribute", std::string("checked"), std::string("true")); addEventListener(element, "change");
}
element.call<void>("addEventListener", emscripten::val("change"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::RadioButton: { case QAccessible::RadioButton: {
element = document.call<emscripten::val>("createElement", std::string("input")); element = document.call<emscripten::val>("createElement", std::string("input"));
element.call<void>("setAttribute", std::string("type"), std::string("radio")); setAttribute(element, "type", "radio");
if (iface->state().checked) { setAttribute(element, "checked", iface->state().checked);
element.call<void>("setAttribute", std::string("checked"), std::string("true")); setProperty(element, "name", "buttonGroup");
} addEventListener(element, "change");
element.set(std::string("name"), std::string("buttonGroup"));
element.call<void>("addEventListener", emscripten::val("change"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::SpinBox: { case QAccessible::SpinBox: {
const std::string valueString =
iface->valueInterface()->currentValue().toString().toStdString();
element = document.call<emscripten::val>("createElement", std::string("input")); element = document.call<emscripten::val>("createElement", std::string("input"));
element.call<void>("setAttribute", std::string("type"), std::string("number")); setAttribute(element, "type", "number");
std::string valueString = iface->valueInterface()->currentValue().toString().toStdString(); setAttribute(element, "value", valueString);
element.call<void>("setAttribute", std::string("value"), valueString); addEventListener(element, "change");
element.call<void>("addEventListener", emscripten::val("change"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::Slider: { case QAccessible::Slider: {
const std::string valueString =
iface->valueInterface()->currentValue().toString().toStdString();
element = document.call<emscripten::val>("createElement", std::string("input")); element = document.call<emscripten::val>("createElement", std::string("input"));
element.call<void>("setAttribute", std::string("type"), std::string("range")); setAttribute(element, "type", "range");
std::string valueString = iface->valueInterface()->currentValue().toString().toStdString(); setAttribute(element, "value", valueString);
element.call<void>("setAttribute", std::string("value"), valueString); addEventListener(element, "change");
element.call<void>("addEventListener", emscripten::val("change"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::PageTabList:{ case QAccessible::PageTabList:{
element = document.call<emscripten::val>("createElement", std::string("div")); element = document.call<emscripten::val>("createElement", std::string("div"));
element.call<void>("setAttribute", std::string("role"), std::string("tablist")); setAttribute(element, "role", "tablist");
for (int i = 0; i < iface->childCount(); ++i) { for (int i = 0; i < iface->childCount(); ++i) {
if (iface->child(i)->role() == QAccessible::PageTab){ if (iface->child(i)->role() == QAccessible::PageTab){
@ -244,79 +282,70 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
} break; } break;
case QAccessible::PageTab:{ case QAccessible::PageTab:{
const QString text = iface->text(QAccessible::Name);
element = document.call<emscripten::val>("createElement", std::string("button")); element = document.call<emscripten::val>("createElement", std::string("button"));
element.call<void>("setAttribute", std::string("role"), std::string("tab")); setAttribute(element, "role", "tab");
QString text = iface->text(QAccessible::Name); setAttribute(element, "title", text.toStdString());
element.call<void>("setAttribute", std::string("title"), text.toStdString()); addEventListener(element, "click");
element.call<void>("addEventListener", emscripten::val("click"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::ScrollBar: { case QAccessible::ScrollBar: {
const std::string valueString =
iface->valueInterface()->currentValue().toString().toStdString();
element = document.call<emscripten::val>("createElement", std::string("div")); element = document.call<emscripten::val>("createElement", std::string("div"));
element.call<void>("setAttribute", std::string("role"), std::string("scrollbar")); setAttribute(element, "role", "scrollbar");
std::string valueString = iface->valueInterface()->currentValue().toString().toStdString(); setAttribute(element, "aria-valuenow", valueString);
element.call<void>("setAttribute", std::string("aria-valuenow"), valueString); addEventListener(element, "change");
element.call<void>("addEventListener", emscripten::val("change"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
case QAccessible::StaticText: { case QAccessible::StaticText: {
element = document.call<emscripten::val>("createElement", std::string("textarea")); element = document.call<emscripten::val>("createElement", std::string("textarea"));
element.call<void>("setAttribute", std::string("readonly"), std::string("true")); setAttribute(element, "readonly", "true");
} break; } break;
case QAccessible::Dialog: { case QAccessible::Dialog: {
element = document.call<emscripten::val>("createElement", std::string("dialog")); element = document.call<emscripten::val>("createElement", std::string("dialog"));
}break; }break;
case QAccessible::ToolBar:{ case QAccessible::ToolBar:{
const QString text = iface->text(QAccessible::Name);
element = document.call<emscripten::val>("createElement", std::string("div")); element = document.call<emscripten::val>("createElement", std::string("div"));
QString text = iface->text(QAccessible::Name); setAttribute(element, "role", "toolbar");
setAttribute(element, "title", text.toStdString());
element.call<void>("setAttribute", std::string("role"), std::string("toolbar")); addEventListener(element, "click");
element.call<void>("setAttribute", std::string("title"), text.toStdString());
element.call<void>("addEventListener", emscripten::val("click"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
}break; }break;
case QAccessible::MenuItem: case QAccessible::MenuItem:
case QAccessible::ButtonMenu: { case QAccessible::ButtonMenu: {
const QString text = iface->text(QAccessible::Name);
element = document.call<emscripten::val>("createElement", std::string("button")); element = document.call<emscripten::val>("createElement", std::string("button"));
QString text = iface->text(QAccessible::Name); setAttribute(element, "role", "menuitem");
setAttribute(element, "title", text.toStdString());
element.call<void>("setAttribute", std::string("role"), std::string("menuitem")); addEventListener(element, "click");
element.call<void>("setAttribute", std::string("title"), text.toStdString());
element.call<void>("addEventListener", emscripten::val("click"),
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
}break; }break;
case QAccessible::MenuBar: case QAccessible::MenuBar:
case QAccessible::PopupMenu: { case QAccessible::PopupMenu: {
element = document.call<emscripten::val>("createElement",std::string("div")); const QString text = iface->text(QAccessible::Name);
QString text = iface->text(QAccessible::Name); element = document.call<emscripten::val>("createElement", std::string("div"));
element.call<void>("setAttribute", std::string("role"), std::string("menubar")); setAttribute(element, "role", "menubar");
element.call<void>("setAttribute", std::string("title"), text.toStdString()); setAttribute(element, "title", text.toStdString());
for (int i = 0; i < iface->childCount(); ++i) { for (int i = 0; i < iface->childCount(); ++i) {
emscripten::val childElement = emscripten::val::undefined(); emscripten::val childElement = emscripten::val::undefined();
childElement= ensureHtmlElement(iface->child(i)); childElement= ensureHtmlElement(iface->child(i));
childElement.call<void>("setAttribute", std::string("aria-owns"), text.toStdString()); setAttribute(childElement, "aria-owns", text.toStdString());
setHtmlElementTextName(iface->child(i)); setHtmlElementTextName(iface->child(i));
setHtmlElementGeometry(iface->child(i)); setHtmlElementGeometry(iface->child(i));
} }
}break; }break;
case QAccessible::EditableText: { case QAccessible::EditableText: {
element = document.call<emscripten::val>("createElement", std::string("input")); element = document.call<emscripten::val>("createElement", std::string("input"));
element.call<void>("setAttribute", std::string("type"),std::string("text")); setAttribute(element, "type", "text");
element.call<void>("addEventListener", emscripten::val("input"), addEventListener(element, "input");
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex), true);
} break; } break;
default: default:
qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role(); qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role();
element = document.call<emscripten::val>("createElement", std::string("div")); element = document.call<emscripten::val>("createElement", std::string("div"));
} }
element.call<void>("addEventListener", emscripten::val("focus"), addEventListener(element, "focus");
QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_eventHandlerIndex),
true);
return element; return element;
}(); }();
@ -360,7 +389,7 @@ void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, b
container.call<void>("appendChild", element); container.call<void>("appendChild", element);
visible = visible && !iface->state().invisible && !iface->state().disabled; visible = visible && !iface->state().invisible && !iface->state().disabled;
element.set("ariaHidden", !visible); // ariaHidden mean completely hidden; maybe some sort of soft-hidden should be used. setProperty(element, "ariaHidden", !visible); // ariaHidden mean completely hidden; maybe some sort of soft-hidden should be used.
} }
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface) void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
@ -397,24 +426,21 @@ void QWasmAccessibility::setHtmlElementTextName(QAccessibleInterface *iface)
{ {
emscripten::val element = ensureHtmlElement(iface); emscripten::val element = ensureHtmlElement(iface);
const QString name = iface->text(QAccessible::Name); const QString name = iface->text(QAccessible::Name);
if (name.isEmpty()) setAttribute(element, "aria-label", name.toStdString());
element.call<void>("removeAttribute", std::string("aria-label"));
else
element.call<void>("setAttribute", std::string("aria-label"), name.toStdString());
} }
void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) { void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) {
emscripten::val element = ensureHtmlElement(iface); emscripten::val element = ensureHtmlElement(iface);
QString text = iface->text(QAccessible::Name); QString text = iface->text(QAccessible::Name);
element.call<void>("setAttribute", std::string("name"), text.toStdString()); setAttribute(element, "name", text.toStdString());
QString value = iface->text(QAccessible::Value); QString value = iface->text(QAccessible::Value);
element.set("innerHTML", value.toStdString()); setProperty(element, "innerHTML", value.toStdString());
} }
void QWasmAccessibility::setHtmlElementDescription(QAccessibleInterface *iface) { void QWasmAccessibility::setHtmlElementDescription(QAccessibleInterface *iface) {
emscripten::val element = ensureHtmlElement(iface); emscripten::val element = ensureHtmlElement(iface);
QString desc = iface->text(QAccessible::Description); QString desc = iface->text(QAccessible::Description);
element.call<void>("setAttribute", std::string("aria-description"), desc.toStdString()); setAttribute(element, "aria-description", desc.toStdString());
} }
void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface) void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
@ -438,8 +464,8 @@ void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
} }
} }
void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) { void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
{
switch (event->type()) { switch (event->type()) {
case QAccessible::NameChanged: { case QAccessible::NameChanged: {
setHtmlElementTextName(event->accessibleInterface()); setHtmlElementTextName(event->accessibleInterface());
@ -515,8 +541,8 @@ void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
case QAccessible::StateChanged: { case QAccessible::StateChanged: {
QAccessibleInterface *accessible = event->accessibleInterface(); QAccessibleInterface *accessible = event->accessibleInterface();
emscripten::val element = ensureHtmlElement(accessible); emscripten::val element = ensureHtmlElement(accessible);
bool checkedString = accessible->state().checked ? true : false;
element.call<void>("setAttribute", std::string("checked"), checkedString); setAttribute(element, "checked", accessible->state().checked);
} break; } break;
case QAccessible::DescriptionChanged: { case QAccessible::DescriptionChanged: {
setHtmlElementDescription(event->accessibleInterface()); setHtmlElementDescription(event->accessibleInterface());
@ -535,7 +561,7 @@ void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
case QAccessible::NameChanged: case QAccessible::NameChanged:
case QAccessible::StateChanged:{ case QAccessible::StateChanged:{
emscripten::val element = ensureHtmlElement(iface); emscripten::val element = ensureHtmlElement(iface);
element.call<void>("setAttribute", std::string("title"), text.toStdString()); setAttribute(element, "title", text.toStdString());
} break; } break;
case QAccessible::DescriptionChanged: { case QAccessible::DescriptionChanged: {
setHtmlElementDescription(event->accessibleInterface()); setHtmlElementDescription(event->accessibleInterface());
@ -556,7 +582,7 @@ void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
case QAccessible::MenuStart ://"TODO: To implement later case QAccessible::MenuStart ://"TODO: To implement later
case QAccessible::StateChanged:{ case QAccessible::StateChanged:{
emscripten::val element = ensureHtmlElement(iface); emscripten::val element = ensureHtmlElement(iface);
element.call<void>("setAttribute", std::string("title"), text.toStdString()); setAttribute(element, "title", text.toStdString());
} break; } break;
case QAccessible::PopupMenuStart: { case QAccessible::PopupMenuStart: {
ensureHtmlElement(iface); ensureHtmlElement(iface);
@ -622,8 +648,7 @@ void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
case QAccessible::StateChanged: { case QAccessible::StateChanged: {
QAccessibleInterface *accessible = event->accessibleInterface(); QAccessibleInterface *accessible = event->accessibleInterface();
emscripten::val element = ensureHtmlElement(accessible); emscripten::val element = ensureHtmlElement(accessible);
std::string checkedString = accessible->state().checked ? "true" : "false"; setAttribute(element, "checked", accessible->state().checked);
element.call<void>("setAttribute", std::string("checked"), checkedString);
} break; } break;
case QAccessible::DescriptionChanged: { case QAccessible::DescriptionChanged: {
setHtmlElementDescription(event->accessibleInterface()); setHtmlElementDescription(event->accessibleInterface());
@ -645,11 +670,12 @@ void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
QAccessibleInterface *accessible = event->accessibleInterface(); QAccessibleInterface *accessible = event->accessibleInterface();
emscripten::val element = ensureHtmlElement(accessible); emscripten::val element = ensureHtmlElement(accessible);
std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString(); std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
element.call<void>("setAttribute", std::string("value"), valueString); setAttribute(element, "value", valueString);
} break; } break;
case QAccessible::DescriptionChanged: { case QAccessible::DescriptionChanged: {
setHtmlElementDescription(event->accessibleInterface()); setHtmlElementDescription(event->accessibleInterface());
} break; } break;
default: default:
qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type(); qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
break; break;
@ -667,7 +693,7 @@ void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
QAccessibleInterface *accessible = event->accessibleInterface(); QAccessibleInterface *accessible = event->accessibleInterface();
emscripten::val element = ensureHtmlElement(accessible); emscripten::val element = ensureHtmlElement(accessible);
std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString(); std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
element.call<void>("setAttribute", std::string("value"), valueString); setAttribute(element, "value", valueString);
} break; } break;
case QAccessible::DescriptionChanged: { case QAccessible::DescriptionChanged: {
setHtmlElementDescription(event->accessibleInterface()); setHtmlElementDescription(event->accessibleInterface());
@ -689,7 +715,7 @@ void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
QAccessibleInterface *accessible = event->accessibleInterface(); QAccessibleInterface *accessible = event->accessibleInterface();
emscripten::val element = ensureHtmlElement(accessible); emscripten::val element = ensureHtmlElement(accessible);
std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString(); std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
element.call<void>("setAttribute", std::string("aria-valuenow"), valueString); setAttribute(element, "aria-valuenow", valueString);
} break; } break;
case QAccessible::DescriptionChanged: { case QAccessible::DescriptionChanged: {
setHtmlElementDescription(event->accessibleInterface()); setHtmlElementDescription(event->accessibleInterface());
@ -747,17 +773,14 @@ void QWasmAccessibility::handleIdentifierUpdate(QAccessibleInterface *iface)
id = QString::fromUtf8(oss.str()); id = QString::fromUtf8(oss.str());
} }
setAttribute(element, "id", id.toStdString());
if (!id.isEmpty()) { if (!id.isEmpty()) {
element.call<void>("setAttribute", std::string("id"), id.toStdString());
if (iface->role() == QAccessible::PageTabList) { if (iface->role() == QAccessible::PageTabList) {
for (int i = 0; i < iface->childCount(); ++i) { for (int i = 0; i < iface->childCount(); ++i) {
auto child = ensureHtmlElement(iface->child(i)); auto child = ensureHtmlElement(iface->child(i));
child.call<void>("setAttribute", std::string("aria-owns"), id.toStdString()); setAttribute(child, "aria-owns", id.toStdString());
} }
} }
} else {
element.call<void>("removeAttribute", std::string("id"));
} }
} }

View File

@ -79,6 +79,19 @@ private:
void initialize() override; void initialize() override;
void cleanup() override; void cleanup() override;
void setAttribute(emscripten::val element, const std::string &attr, const std::string &val);
void setAttribute(emscripten::val element, const std::string &attr, const char *val);
void setAttribute(emscripten::val element, const std::string &attr, bool val);
void setProperty(emscripten::val element, const std::string &attr, const std::string &val);
void setProperty(emscripten::val element, const std::string &attr, const char *val);
void setProperty(emscripten::val element, const std::string &attr, bool val);
void addEventListener(emscripten::val element, const char *eventType);
public: // public for EMSCRIPTEN_BINDINGS
static void onHtmlEventReceived(emscripten::val event);
private: private:
static QWasmAccessibility *s_instance; static QWasmAccessibility *s_instance;
QObject *m_rootObject = nullptr; QObject *m_rootObject = nullptr;