blob: 5a7905774d0d907c6dbda28d144e49cdbae53f14 [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#include "base/scoped_nsobject.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/ui/ui_test.h"
// The following tests exercise the Chrome Mac accessibility implementation
// similarly to the way in which VoiceOver would.
// We achieve this by utilizing the same carbon API (HIServices) as do
// other assistive technologies.
// Note that the tests must be UITests since these API's only work if not
// called within the same process begin examined.
class AccessibilityMacUITest : public UITest {
public:
AccessibilityMacUITest() {
// TODO(dtseng): fake the VoiceOver defaults value?
launch_arguments_.AppendSwitch(switches::kForceRendererAccessibility);
}
virtual void SetUp() {
UITest::SetUp();
SetupObservedNotifications();
Initialize();
}
// Called to insert an event for validation.
// This is a order sensitive expectation.
void AddExpectedEvent(NSString* notificationName) {
[AccessibilityMacUITest::expectedEvents addObject:notificationName];
}
// Assert that there are no remaining expected events.
// CFRunLoop necessary to receive AX callbacks.
// Assumes that there is at least one expected event.
// The runloop stops only if we receive all expected notifications.
void WaitAndAssertAllEventsObserved() {
ASSERT_GE([expectedEvents count], 1U);
CFRunLoopRunInMode(
kCFRunLoopDefaultMode, action_max_timeout_ms()/1000, false);
ASSERT_EQ(0U, [AccessibilityMacUITest::expectedEvents count]);
}
// The Callback handler added to each AXUIElement.
static void EventReceiver(
AXObserverRef observerRef,
AXUIElementRef element,
CFStringRef notificationName,
void *refcon) {
if ([[AccessibilityMacUITest::expectedEvents objectAtIndex:0]
isEqualToString:(NSString*)notificationName]) {
[AccessibilityMacUITest::expectedEvents removeObjectAtIndex:0];
}
if ([AccessibilityMacUITest::expectedEvents count] == 0) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
// TODO(dtseng): currently refreshing on all notifications; scope later.
AccessibilityMacUITest::SetAllObserversOnDescendants(
element, observerRef);
}
private:
// Perform AX setup.
void Initialize() {
AccessibilityMacUITest::expectedEvents.reset([[NSMutableArray alloc] init]);
// Construct the Chrome AXUIElementRef.
ASSERT_NE(-1, browser_process_id());
AXUIElementRef browserUiElement =
AXUIElementCreateApplication(browser_process_id());
ASSERT_TRUE(browserUiElement);
// Setup our callbacks.
AXObserverRef observerRef;
ASSERT_EQ(kAXErrorSuccess,
AXObserverCreate(browser_process_id(),
AccessibilityMacUITest::EventReceiver,
&observerRef));
SetAllObserversOnDescendants(browserUiElement, observerRef);
// Add the observer to the current message loop.
CFRunLoopAddSource(
[[NSRunLoop currentRunLoop] getCFRunLoop],
AXObserverGetRunLoopSource(observerRef),
kCFRunLoopDefaultMode);
}
// Taken largely from AXNotificationConstants.h
// (substituted NSAccessibility* to avoid casting).
static void SetupObservedNotifications() {
AccessibilityMacUITest::observedNotifications.reset(
[[NSArray alloc] initWithObjects:
// focus notifications
NSAccessibilityMainWindowChangedNotification,
NSAccessibilityFocusedWindowChangedNotification,
NSAccessibilityFocusedUIElementChangedNotification,
// application notifications
NSAccessibilityApplicationActivatedNotification,
NSAccessibilityApplicationDeactivatedNotification,
NSAccessibilityApplicationHiddenNotification,
NSAccessibilityApplicationShownNotification,
// window notifications
NSAccessibilityWindowCreatedNotification,
NSAccessibilityWindowMovedNotification,
NSAccessibilityWindowResizedNotification,
NSAccessibilityWindowMiniaturizedNotification,
NSAccessibilityWindowDeminiaturizedNotification,
// new drawer, sheet, and help tag notifications
NSAccessibilityDrawerCreatedNotification,
NSAccessibilitySheetCreatedNotification,
NSAccessibilityHelpTagCreatedNotification,
// element notifications
NSAccessibilityValueChangedNotification,
NSAccessibilityUIElementDestroyedNotification,
// menu notifications
(NSString*)kAXMenuOpenedNotification,
(NSString*)kAXMenuClosedNotification,
(NSString*)kAXMenuItemSelectedNotification,
// table/outline notifications
NSAccessibilityRowCountChangedNotification,
// other notifications
NSAccessibilitySelectedChildrenChangedNotification,
NSAccessibilityResizedNotification,
NSAccessibilityMovedNotification,
NSAccessibilityCreatedNotification,
NSAccessibilitySelectedRowsChangedNotification,
NSAccessibilitySelectedColumnsChangedNotification,
NSAccessibilitySelectedTextChangedNotification,
NSAccessibilityTitleChangedNotification,
// Webkit specific notifications.
@"AXLoadComplete",
nil]);
}
// Observe AX notifications on element and all descendants.
static void SetAllObserversOnDescendants(
AXUIElementRef element,
AXObserverRef observerRef) {
SetAllObservers(element, observerRef);
CFTypeRef childrenRef;
if ((AXUIElementCopyAttributeValue(
element, kAXChildrenAttribute, &childrenRef)) == kAXErrorSuccess) {
NSArray* children = (NSArray*)childrenRef;
for (uint32 i = 0; i < [children count]; ++i) {
SetAllObserversOnDescendants(
(AXUIElementRef)[children objectAtIndex:i], observerRef);
}
}
}
// Add observers for all notifications we know about.
static void SetAllObservers(
AXUIElementRef element,
AXObserverRef observerRef) {
for (NSString* notification in
AccessibilityMacUITest::observedNotifications.get()) {
AXObserverAddNotification(
observerRef, element, (CFStringRef)notification, nil);
}
}
// Used to keep track of events received during the lifetime of the tests.
static scoped_nsobject<NSMutableArray> expectedEvents;
// NSString collection of all AX notifications.
static scoped_nsobject<NSArray> observedNotifications;
};
scoped_nsobject<NSMutableArray> AccessibilityMacUITest::expectedEvents;
scoped_nsobject<NSArray> AccessibilityMacUITest::observedNotifications;
TEST_F(AccessibilityMacUITest, TestInitialPageNotifications) {
// Browse to a new page.
GURL tree_url(
"data:text/html,<html><head><title>Accessibility Mac Test</title></head>"
"<body><input type='button' value='push' /><input type='checkbox' />"
"</body></html>");
NavigateToURLAsync(tree_url);
// Test for navigation.
AddExpectedEvent(@"AXLoadComplete");
// Check all the expected Mac notifications.
WaitAndAssertAllEventsObserved();
}