a11y: reduce the number of activation observer calls

Reduce the number of activation calls by calling the
observer callbacks on active state change only, instead
of on each setActive() call.

The accessibility backends typically make setActive()
calls on each accessibility event, which can result
in a large number (100k+) of activation calls if there
are many (hundreds) of installed activation observers.

This change keeps the following behavior compatibilities:

- Each observer is called at least once on setActive(),
  even if the active state has not changed since it was
  installed.
- Going from active to inactive is supported, though
  Qt currently does not appear to do this.

As a drive-by refactor, make it possible to activate
accessibility by calling QAccessible::setActive(), by
making it call QPlatformAccessible::setActive().

Change-Id: Iee17160f153b825be032b441923e3529c2f00242
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Morten Sørvig 2025-04-02 12:07:35 +02:00 committed by Morten Johan Sørvig
parent e57fff6341
commit 51c7503568
3 changed files with 39 additions and 6 deletions

View File

@ -688,6 +688,11 @@ void QAccessible::installActivationObserver(QAccessible::ActivationObserver *obs
if (qAccessibleActivationObservers()->contains(observer))
return;
qAccessibleActivationObservers()->append(observer);
// Make sure the newly added observer gets a callback on the next
// QPlatformAccessibility::setActive() callback
if (QPlatformAccessibility *pfAccessibility = platformAccessibility())
pfAccessibility->clearActiveNotificationState();
}
/*!
@ -701,6 +706,17 @@ void QAccessible::removeActivationObserver(ActivationObserver *observer)
qAccessibleActivationObservers()->removeAll(observer);
}
/*!
\internal
Sends accessibility activation notifications to all registered observers.
*/
void qAccessibleNotifyActivationObservers(bool active)
{
for (int i = 0; i < qAccessibleActivationObservers()->size(); ++i)
qAccessibleActivationObservers()->at(i)->accessibilityActiveChanged(active);
}
/*!
If a QAccessibleInterface implementation exists for the given \a object,
this function returns a pointer to the implementation; otherwise it
@ -870,11 +886,10 @@ bool QAccessible::isActive()
*/
void QAccessible::setActive(bool active)
{
for (int i = 0; i < qAccessibleActivationObservers()->size() ;++i)
qAccessibleActivationObservers()->at(i)->accessibilityActiveChanged(active);
if (QPlatformAccessibility *pfAccessibility = platformAccessibility())
pfAccessibility->setActive(active);
}
/*!
Sets the root object of the accessible objects of this application
to \a object. All other accessible objects are reachable using object

View File

@ -35,7 +35,6 @@ Q_GLOBAL_STATIC(QList<QAccessibleBridge *>, bridges)
\sa QAccessible
*/
QPlatformAccessibility::QPlatformAccessibility()
: m_active(false)
{
}
@ -99,10 +98,25 @@ void QPlatformAccessibility::cleanup()
qDeleteAll(*bridges());
}
void qAccessibleNotifyActivationObservers(bool active); // qaccessible.cpp
void QPlatformAccessibility::setActive(bool active)
{
m_active = active;
QAccessible::setActive(active);
// Send activeChanged notifications if the new active status differs from
// the notifed one.
if ((active && m_activeNotificationState != std::optional<bool>{true}) ||
(!active && m_activeNotificationState != std::optional<bool>{false})) {
qAccessibleNotifyActivationObservers(active);
}
m_activeNotificationState = active;
}
void QPlatformAccessibility::clearActiveNotificationState()
{
m_activeNotificationState = std::nullopt;
}
#endif // QT_CONFIG(accessibility)

View File

@ -19,6 +19,8 @@
#include <QtCore/qobject.h>
#include <QtGui/qaccessible.h>
#include <optional>
QT_BEGIN_NAMESPACE
@ -35,9 +37,11 @@ public:
inline bool isActive() const { return m_active; }
void setActive(bool active);
void clearActiveNotificationState();
private:
bool m_active;
bool m_active = false;
std::optional<bool> m_activeNotificationState = std::nullopt;
};
QT_END_NAMESPACE