// Copyright (c) 2011 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/memory/scoped_nsobject.h" #include "base/test/test_timeouts.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, TestTimeouts::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(); }