a11y on macOS: return a valid window element

Our accessibilityWindow implementation asks the parent for it's window,
expecting that it will always be the same. This is conceptually correct.

However, as we don't represent windows through QAccessibilityInterface
and instead rely on the natively provided element, the filtering out of
ignored elements result in accessibilityParent return a null object once
the parent is the window.

Instead, check if we get an interface that represents a Window, and
if so fall through to the code returning the NSView (after going through
QAcessibilityInterface::window call, which was so far missing).

And if we then get a Window element as the parent, then we don't have
to call accessibilityWindow on that parent again. Instead, return the
result directly and only keep going if we got some other element.

Add a test case that confirms that we now get a valid result for the
window attribute.

Pick-to: 6.9 6.8 6.5
Fixes: QTBUG-137157
Change-Id: Ifa485734b290284bd5a1286e3b3c18454442fa10
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
(cherry picked from commit 5dc261357e44ac9e15423997748e28ae659a3623)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2025-06-11 17:07:22 +02:00 committed by Qt Cherry-pick Bot
parent 282ce8fbbc
commit c750468766
2 changed files with 43 additions and 8 deletions

View File

@ -549,8 +549,11 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (id) accessibilityWindow {
// We're in the same window as our parent.
return [self.accessibilityParent accessibilityWindow];
// Go up until we find a parent that is a window
NSAccessibilityElement *parent = self.accessibilityParent;
if (parent && parent.accessibilityRole == NSAccessibilityWindowRole)
return parent;
return [parent accessibilityWindow];
}
- (id) accessibilityTopLevelUIElementAttribute {
@ -621,11 +624,6 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId:axid]);
}
// macOS expects that the hierarchy is:
// App -> Window -> Children
// We don't actually have the window reflected properly in QAccessibility.
// Check if the parent is the application and then instead return the native window.
if (QAccessibleInterface *parent = iface->parent()) {
if (parent->tableInterface()) {
QMacAccessibilityElement *tableElement =
@ -643,7 +641,12 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
return NSAccessibilityUnignoredAncestor(rowElement);
}
if (parent->role() != QAccessible::Application)
// macOS expects that the hierarchy is:
// App -> Window -> Children
// We don't actually have the window reflected properly in QAccessibility;
// the native framework does that for us. Check if the parent is the
// Application or a window, and if so return the native NSView instead.
if (parent->role() != QAccessible::Application && parent->role() != QAccessible::Window)
return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]);
}

View File

@ -382,6 +382,7 @@ QDebug operator<<(QDebug dbg, AXErrorTag err)
return rect;
}
- (AXUIElementRef) parent { return (AXUIElementRef)[self _attributeValue:kAXParentAttribute]; }
- (AXUIElementRef) window { return (AXUIElementRef)[self _attributeValue:kAXWindowAttribute]; }
- (BOOL) focused { return [self _boolAttributeValue:kAXFocusedAttribute]; }
- (NSInteger) numberOfCharacters { return [self _numberAttributeValue:kAXNumberOfCharactersAttribute]; }
- (NSString*) selectedText { return [self _stringAttributeValue:kAXSelectedTextAttribute]; }
@ -449,6 +450,7 @@ private Q_SLOTS:
void tableViewTest();
void treeViewTest();
void tabBarTest();
void windowTest();
private:
AccessibleTestWindow *m_window;
@ -933,5 +935,35 @@ void tst_QAccessibilityMac::tabBarTest()
}
}
void tst_QAccessibilityMac::windowTest()
{
QTextEdit *textEdit = new QTextEdit;
m_window->addWidget(textEdit);
QVERIFY(QTest::qWaitForWindowExposed(m_window));
QCoreApplication::processEvents();
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
QVERIFY(appObject);
NSArray *windowList = [appObject windowList];
// one window
QVERIFY([windowList count] == 1);
AXUIElementRef windowRef = (AXUIElementRef)[windowList objectAtIndex:0];
QVERIFY(windowRef != nil);
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef:windowRef];
QVERIFY(window.valid);
AXUIElementRef axTextEdit = [window findDirectChildByRole:kAXTextAreaRole];
QVERIFY(axTextEdit != nil);
[appObject release];
[window release];
TestAXObject *edit = [[TestAXObject alloc] initWithAXUIElementRef:axTextEdit];
QVERIFY(edit.valid);
AXUIElementRef axWindow = edit.window;
QVERIFY(axWindow);
}
QTEST_MAIN(tst_QAccessibilityMac)
#include "tst_qaccessibilitymac.moc"