iOS: Don't invalidate a11y whenever UI elements are added or removed

The UIAccessibilityScreenChangedNotification will result in iOS resetting
its state, focusing the first a11y element on the screen. We shouldn't
tie clearing the a11y cache to this notification, as those are two
separate actions.

In the case of adding or removing individual elements, we still likely
need to clear the cache, but can inform the system of the more granular
UIAccessibilityLayoutChangedNotification to have it re-read the a11y
tree.

We still handle additions and removal of a11y elements with Window
or Dialog roles as UIAccessibilityScreenChangedNotification, as these
likely involve major UI changes.

The implicit UIAccessibilityScreenChangedNotification on QIOSWindow
destruction has been removed, as it's assumed iOS will automatically
refresh its a11y tree when a UIWindow is destroyed, and in any case
it's up to the individual clients of QAccessible to send the relevant
QAccessibleEvent to inform about the situation.

Pick-to: 6.6 6.5
Fixes: QTBUG-100094
Change-Id: If7d5cb961743e5ca97d45553b05ae5e92f82d275
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Tor Arne Vestbø 2023-06-05 18:41:11 +02:00
parent 0237709e2c
commit b006d6d9de
3 changed files with 14 additions and 5 deletions

View File

@ -25,8 +25,6 @@ void invalidateCache(QAccessibleInterface *iface)
// This will invalidate everything regardless of what window the
// interface belonged to. We might want to revisit this strategy later.
// (Therefore this function still takes the interface as argument)
// It is also responsible for the bug that focus gets temporary lost
// when items get added or removed from the screen
foreach (QWindow *win, QGuiApplication::topLevelWindows()) {
if (win && win->handle()) {
QT_PREPEND_NAMESPACE(QIOSWindow) *window = static_cast<QT_PREPEND_NAMESPACE(QIOSWindow) *>(win->handle());
@ -38,14 +36,25 @@ void invalidateCache(QAccessibleInterface *iface)
void QIOSPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
{
if (!isActive() || !event->accessibleInterface())
auto *accessibleInterface = event->accessibleInterface();
if (!isActive() || !accessibleInterface)
return;
switch (event->type()) {
case QAccessible::ObjectCreated:
case QAccessible::ObjectShow:
case QAccessible::ObjectHide:
case QAccessible::ObjectDestroyed:
invalidateCache(event->accessibleInterface());
invalidateCache(accessibleInterface);
switch (accessibleInterface->role()) {
case QAccessible::Window:
case QAccessible::Dialog:
// Bigger changes to the UI require a full reset of VoiceOver
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
break;
default:
// While smaller changes can be handled by re-reading the layout
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
break;
default:
break;

View File

@ -75,6 +75,7 @@ QIOSWindow::~QIOSWindow()
[m_view touchesCancelled:[NSSet set] withEvent:0];
clearAccessibleCache();
m_view.platformWindow = 0;
[m_view removeFromSuperview];
[m_view release];

View File

@ -54,7 +54,6 @@
- (void)clearAccessibleCache
{
[m_accessibleElements removeAllObjects];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, @"");
}
// this is a container, returning yes here means the functions below will never be called