diff options
author | Jens Trillmann <[email protected]> | 2024-03-13 14:00:38 +0100 |
---|---|---|
committer | Jens Trillmann <[email protected]> | 2024-06-01 22:58:20 +0200 |
commit | 9ec1de2528b871099d416d15592fcc5ef9242a64 (patch) | |
tree | cd16f1254e0685f410012dd72605ba133ae9450f | |
parent | 3c93dedc063bf453bcda581b4e9ccab5a810c80f (diff) |
Add Identifier role to QAccessible and use it in OS interfaces
* Unify the default identifier creation for QAccessibleInterface on
all platforms to be the same as the previous identifier on Linux.
This may change some identifiers on Windows.
[ChangeLog][QAccessible][QAccessibleInterface] Add possibility to
add unique identifier to QAccessibleInterface to give a11y elements
consistent identifiers.
Task-number: QTBUG-123361
Change-Id: I8c42956a4c497e71909d71dcb27bc87433937b69
Reviewed-by: Volker Hilsheimer <[email protected]>
11 files changed, 74 insertions, 50 deletions
diff --git a/src/gui/accessible/linux/atspiadaptor.cpp b/src/gui/accessible/linux/atspiadaptor.cpp index 5e7a452a335..722723207a3 100644 --- a/src/gui/accessible/linux/atspiadaptor.cpp +++ b/src/gui/accessible/linux/atspiadaptor.cpp @@ -1352,6 +1352,7 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) case QAccessible::HelpChanged: case QAccessible::DefaultActionChanged: case QAccessible::AcceleratorChanged: + case QAccessible::IdentifierChanged: case QAccessible::InvalidEvent: break; } @@ -1555,26 +1556,6 @@ void AtSpiAdaptor::registerApplication() delete registry; } -namespace { -QString accessibleIdForAccessible(QAccessibleInterface *accessible) -{ - QString result; - while (accessible) { - if (!result.isEmpty()) - result.prepend(u'.'); - if (auto obj = accessible->object()) { - const QString name = obj->objectName(); - if (!name.isEmpty()) - result.prepend(name); - else - result.prepend(QString::fromUtf8(obj->metaObject()->className())); - } - accessible = accessible->parent(); - } - return result; -} -} // namespace - // Accessible bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) { @@ -1665,7 +1646,7 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS connection.send(message.createReply(QVariant::fromValue(children))); } else if (function == "GetAccessibleId"_L1) { sendReply(connection, message, - QVariant::fromValue(QDBusVariant(accessibleIdForAccessible(interface)))); + QVariant::fromValue(QDBusVariant(QAccessibleBridgeUtils::accessibleId(interface)))); } else { qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::accessibleInterface does not implement" << function << message.path(); return false; diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp index eec779efb10..b75712c3e86 100644 --- a/src/gui/accessible/qaccessible.cpp +++ b/src/gui/accessible/qaccessible.cpp @@ -206,6 +206,7 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core"); clicked or via a key press. \value HypertextLinkSelected A hypertext link has been selected. \value HypertextNLinksChanged + \value [since 6.8] IdentifierChanged The identifier of an object has changed. \value LocationChanged An object's location on the screen has changed. \value MenuCommand A menu item is triggered. \value MenuEnd A menu has been closed (Qt uses PopupMenuEnd for all @@ -389,14 +390,15 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core"); This enum specifies string information that an accessible object returns. - \value Name The name of the object. This can be used both - as an identifier or a short description by - accessible clients. - \value Description A short text describing the object. - \value Value The value of the object. - \value Help A longer text giving information about how to use the object. - \value Accelerator The keyboard shortcut that executes the object's default action. - \value UserText The first value to be used for user defined text. + \value Name The name of the object. This can be used both + as an identifier or a short description by + accessible clients. + \value Description A short text describing the object. + \value Value The value of the object. + \value Help A longer text giving information about how to use the object. + \value Accelerator The keyboard shortcut that executes the object's default action. + \value UserText The first value to be used for user defined text. + \value [since 6.8] Identifier An identifier for the object for e.g. UI tests. \omitvalue DebugDescription */ @@ -1249,6 +1251,13 @@ QAccessibleInterface *QAccessibleInterface::focusChild() const tool buttons also have shortcut keys and usually display them in their tooltip. + The \l QAccessible::Identifier can be explicitly set to provide an + ID to assistive technologies. This can be especially useful for UI tests. + If no identifier has been explicitly set, the identifier is set by the + respective interface to an ID based on \l QObject::objectName or its + class name and \l QObject::objectName or class name of the parents + in its parents chain. + All objects provide a string for \l QAccessible::Name. \sa role(), state() diff --git a/src/gui/accessible/qaccessible_base.h b/src/gui/accessible/qaccessible_base.h index 1ca3dadc36d..e164a2ad6bb 100644 --- a/src/gui/accessible/qaccessible_base.h +++ b/src/gui/accessible/qaccessible_base.h @@ -102,6 +102,7 @@ public: DefaultActionChanged = 0x80B0, AcceleratorChanged = 0x80C0, Announcement = 0x80D0, + IdentifierChanged = 0x80E0, InvalidEvent }; @@ -324,6 +325,7 @@ public: Help, Accelerator, DebugDescription, + Identifier, UserText = 0x0000ffff }; diff --git a/src/gui/accessible/qaccessiblebridgeutils.cpp b/src/gui/accessible/qaccessiblebridgeutils.cpp index 994f95fee9f..0f91927d4c1 100644 --- a/src/gui/accessible/qaccessiblebridgeutils.cpp +++ b/src/gui/accessible/qaccessiblebridgeutils.cpp @@ -72,6 +72,28 @@ bool performEffectiveAction(QAccessibleInterface *iface, const QString &actionNa return true; } +QString accessibleId(QAccessibleInterface *accessible) { + QString result; + if (!accessible) + return result; + result = accessible->text(QAccessible::Identifier); + if (!result.isEmpty()) + return result; + while (accessible) { + if (!result.isEmpty()) + result.prepend(u'.'); + if (auto obj = accessible->object()) { + const QString name = obj->objectName(); + if (!name.isEmpty()) + result.prepend(name); + else + result.prepend(QString::fromUtf8(obj->metaObject()->className())); + } + accessible = accessible->parent(); + } + return result; +} + } //namespace QT_END_NAMESPACE diff --git a/src/gui/accessible/qaccessiblebridgeutils_p.h b/src/gui/accessible/qaccessiblebridgeutils_p.h index b65a4d0b6cb..d7b88eaa58e 100644 --- a/src/gui/accessible/qaccessiblebridgeutils_p.h +++ b/src/gui/accessible/qaccessiblebridgeutils_p.h @@ -17,6 +17,7 @@ #include <QtGui/private/qtguiglobal_p.h> +#include <QtCore/qstring.h> #include <QtCore/qstringlist.h> #include <QtGui/qaccessible.h> @@ -27,6 +28,7 @@ QT_BEGIN_NAMESPACE namespace QAccessibleBridgeUtils { Q_GUI_EXPORT QStringList effectiveActionNames(QAccessibleInterface *iface); Q_GUI_EXPORT bool performEffectiveAction(QAccessibleInterface *iface, const QString &actionName); + Q_GUI_EXPORT QString accessibleId(QAccessibleInterface *accessible); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index 805616e481a..2010a9e03b2 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -470,6 +470,7 @@ namespace QtAndroidAccessibility QAccessible::Role role; QStringList actions; QString description; + QString identifier; bool hasTextSelection = false; int selectionStart = 0; int selectionEnd = 0; @@ -485,6 +486,7 @@ namespace QtAndroidAccessibility info.role = iface->role(); info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface); info.description = descriptionForInterface(iface); + info.identifier = QAccessibleBridgeUtils::accessibleId(iface); QAccessibleTextInterface *textIface = iface->textInterface(); if (textIface && (textIface->selectionCount() > 0)) { info.hasTextSelection = true; @@ -550,6 +552,8 @@ namespace QtAndroidAccessibility //CALL_METHOD(node, "setText", "(Ljava/lang/CharSequence;)V", jdesc) env->CallVoidMethod(node, m_setContentDescriptionMethodID, jdesc); + QJniObject(node).callMethod<void>("setViewIdResourceName", info.identifier); + return true; } diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index 8d4d6d683d6..b319dd072e8 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -520,6 +520,12 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return nil; } +- (NSString*) accessibilityIdentifier { + if (QAccessibleInterface *iface = self.qtInterface) + return QAccessibleBridgeUtils::accessibleId(iface).toNSString(); + return nil; +} + - (BOOL) isAccessibilityEnabled { if (QAccessibleInterface *iface = self.qtInterface) return !iface->state().disabled; diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm index 39b2cb8a50f..fa54f61967e 100644 --- a/src/plugins/platforms/ios/quiaccessibilityelement.mm +++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm @@ -10,6 +10,8 @@ #include "uistrings_p.h" #include "qioswindow.h" +#include <QtGui/private/qaccessiblebridgeutils_p.h> + QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement); @implementation QMacAccessibilityElement @@ -70,6 +72,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement); return iface->text(QAccessible::Name).toNSString(); } + +- (NSString*)accessibilityIdentifier +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return @""; + } + return QAccessibleBridgeUtils::accessibleId(iface).toNSString(); +} + - (NSString*)accessibilityHint { QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp index 4c3cb46ba3d..2e430176bec 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -284,6 +284,10 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac element = document.call<emscripten::val>("createElement", std::string("div")); } + QString id = QAccessibleBridgeUtils::accessibleId(iface); + if (iface->role() != QAccessible::PageTabList) + element.call<void>("setAttribute", std::string("id"), id.toStdString()); + return element; }(); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index be88ab4ae8d..07cd5227463 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -23,6 +23,7 @@ #include "qwindowsuiaprovidercache.h" #include <QtCore/qloggingcategory.h> +#include <QtGui/private/qaccessiblebridgeutils_p.h> #include <QtGui/qaccessible.h> #include <QtGui/qguiapplication.h> #include <QtGui/qwindow.h> @@ -503,7 +504,7 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR break; case UIA_AutomationIdPropertyId: // Automation ID, which can be used by tools to select a specific control in the UI. - setVariantString(automationIdForAccessible(accessible), pRetVal); + setVariantString(QAccessibleBridgeUtils::accessibleId(accessible), pRetVal); break; case UIA_ClassNamePropertyId: // Class name. @@ -610,25 +611,6 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR return S_OK; } -// Generates an ID based on the name of the controls and their parents. -QString QWindowsUiaMainProvider::automationIdForAccessible(const QAccessibleInterface *accessible) -{ - QString result; - if (accessible) { - QObject *obj = accessible->object(); - while (obj) { - QString name = obj->objectName(); - if (name.isEmpty()) - return result; - if (!result.isEmpty()) - result.prepend(u'.'); - result.prepend(name); - obj = obj->parent(); - } - } - return result; -} - HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h index dafe8779741..8ea343e4253 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h @@ -59,7 +59,6 @@ public: HRESULT STDMETHODCALLTYPE GetFocus(IRawElementProviderFragment **pRetVal) override; private: - QString automationIdForAccessible(const QAccessibleInterface *accessible); static void fillVariantArrayForRelation(QAccessibleInterface *accessible, QAccessible::Relation relation, VARIANT *pRetVal); static void setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal); static void setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal); |