1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import <Cocoa/Cocoa.h> 6 7#include "base/memory/scoped_nsobject.h" 8#include "base/test/test_timeouts.h" 9#include "chrome/common/chrome_switches.h" 10#include "chrome/test/ui/ui_test.h" 11 12// The following tests exercise the Chrome Mac accessibility implementation 13// similarly to the way in which VoiceOver would. 14// We achieve this by utilizing the same carbon API (HIServices) as do 15// other assistive technologies. 16// Note that the tests must be UITests since these API's only work if not 17// called within the same process begin examined. 18class AccessibilityMacUITest : public UITest { 19 public: 20 AccessibilityMacUITest() { 21 // TODO(dtseng): fake the VoiceOver defaults value? 22 launch_arguments_.AppendSwitch(switches::kForceRendererAccessibility); 23 } 24 25 virtual void SetUp() { 26 UITest::SetUp(); 27 SetupObservedNotifications(); 28 Initialize(); 29 } 30 31 // Called to insert an event for validation. 32 // This is a order sensitive expectation. 33 void AddExpectedEvent(NSString* notificationName) { 34 [AccessibilityMacUITest::expectedEvents addObject:notificationName]; 35 } 36 37 // Assert that there are no remaining expected events. 38 // CFRunLoop necessary to receive AX callbacks. 39 // Assumes that there is at least one expected event. 40 // The runloop stops only if we receive all expected notifications. 41 void WaitAndAssertAllEventsObserved() { 42 ASSERT_GE([expectedEvents count], 1U); 43 CFRunLoopRunInMode( 44 kCFRunLoopDefaultMode, 45 TestTimeouts::action_max_timeout_ms() / 1000, false); 46 ASSERT_EQ(0U, [AccessibilityMacUITest::expectedEvents count]); 47 } 48 49 // The Callback handler added to each AXUIElement. 50 static void EventReceiver( 51 AXObserverRef observerRef, 52 AXUIElementRef element, 53 CFStringRef notificationName, 54 void *refcon) { 55 if ([[AccessibilityMacUITest::expectedEvents objectAtIndex:0] 56 isEqualToString:(NSString*)notificationName]) { 57 [AccessibilityMacUITest::expectedEvents removeObjectAtIndex:0]; 58 } 59 60 if ([AccessibilityMacUITest::expectedEvents count] == 0) { 61 CFRunLoopStop(CFRunLoopGetCurrent()); 62 } 63 64 // TODO(dtseng): currently refreshing on all notifications; scope later. 65 AccessibilityMacUITest::SetAllObserversOnDescendants( 66 element, observerRef); 67 } 68 69 private: 70 // Perform AX setup. 71 void Initialize() { 72 AccessibilityMacUITest::expectedEvents.reset([[NSMutableArray alloc] init]); 73 74 // Construct the Chrome AXUIElementRef. 75 ASSERT_NE(-1, browser_process_id()); 76 AXUIElementRef browserUiElement = 77 AXUIElementCreateApplication(browser_process_id()); 78 ASSERT_TRUE(browserUiElement); 79 80 // Setup our callbacks. 81 AXObserverRef observerRef; 82 ASSERT_EQ(kAXErrorSuccess, 83 AXObserverCreate(browser_process_id(), 84 AccessibilityMacUITest::EventReceiver, 85 &observerRef)); 86 SetAllObserversOnDescendants(browserUiElement, observerRef); 87 88 // Add the observer to the current message loop. 89 CFRunLoopAddSource( 90 [[NSRunLoop currentRunLoop] getCFRunLoop], 91 AXObserverGetRunLoopSource(observerRef), 92 kCFRunLoopDefaultMode); 93 } 94 95 // Taken largely from AXNotificationConstants.h 96 // (substituted NSAccessibility* to avoid casting). 97 static void SetupObservedNotifications() { 98 AccessibilityMacUITest::observedNotifications.reset( 99 [[NSArray alloc] initWithObjects: 100 101 // focus notifications 102 NSAccessibilityMainWindowChangedNotification, 103 NSAccessibilityFocusedWindowChangedNotification, 104 NSAccessibilityFocusedUIElementChangedNotification, 105 106 // application notifications 107 NSAccessibilityApplicationActivatedNotification, 108 NSAccessibilityApplicationDeactivatedNotification, 109 NSAccessibilityApplicationHiddenNotification, 110 NSAccessibilityApplicationShownNotification, 111 112 // window notifications 113 NSAccessibilityWindowCreatedNotification, 114 NSAccessibilityWindowMovedNotification, 115 NSAccessibilityWindowResizedNotification, 116 NSAccessibilityWindowMiniaturizedNotification, 117 NSAccessibilityWindowDeminiaturizedNotification, 118 119 // new drawer, sheet, and help tag notifications 120 NSAccessibilityDrawerCreatedNotification, 121 NSAccessibilitySheetCreatedNotification, 122 NSAccessibilityHelpTagCreatedNotification, 123 124 // element notifications 125 NSAccessibilityValueChangedNotification, 126 NSAccessibilityUIElementDestroyedNotification, 127 128 // menu notifications 129 (NSString*)kAXMenuOpenedNotification, 130 (NSString*)kAXMenuClosedNotification, 131 (NSString*)kAXMenuItemSelectedNotification, 132 133 // table/outline notifications 134 NSAccessibilityRowCountChangedNotification, 135 136 // other notifications 137 NSAccessibilitySelectedChildrenChangedNotification, 138 NSAccessibilityResizedNotification, 139 NSAccessibilityMovedNotification, 140 NSAccessibilityCreatedNotification, 141 NSAccessibilitySelectedRowsChangedNotification, 142 NSAccessibilitySelectedColumnsChangedNotification, 143 NSAccessibilitySelectedTextChangedNotification, 144 NSAccessibilityTitleChangedNotification, 145 146 // Webkit specific notifications. 147 @"AXLoadComplete", 148 nil]); 149 } 150 151 // Observe AX notifications on element and all descendants. 152 static void SetAllObserversOnDescendants( 153 AXUIElementRef element, 154 AXObserverRef observerRef) { 155 SetAllObservers(element, observerRef); 156 CFTypeRef childrenRef; 157 if ((AXUIElementCopyAttributeValue( 158 element, kAXChildrenAttribute, &childrenRef)) == kAXErrorSuccess) { 159 NSArray* children = (NSArray*)childrenRef; 160 for (uint32 i = 0; i < [children count]; ++i) { 161 SetAllObserversOnDescendants( 162 (AXUIElementRef)[children objectAtIndex:i], observerRef); 163 } 164 } 165 } 166 167 // Add observers for all notifications we know about. 168 static void SetAllObservers( 169 AXUIElementRef element, 170 AXObserverRef observerRef) { 171 for (NSString* notification in 172 AccessibilityMacUITest::observedNotifications.get()) { 173 AXObserverAddNotification( 174 observerRef, element, (CFStringRef)notification, nil); 175 } 176 } 177 178 // Used to keep track of events received during the lifetime of the tests. 179 static scoped_nsobject<NSMutableArray> expectedEvents; 180 // NSString collection of all AX notifications. 181 static scoped_nsobject<NSArray> observedNotifications; 182}; 183 184scoped_nsobject<NSMutableArray> AccessibilityMacUITest::expectedEvents; 185scoped_nsobject<NSArray> AccessibilityMacUITest::observedNotifications; 186 187TEST_F(AccessibilityMacUITest, TestInitialPageNotifications) { 188 // Browse to a new page. 189 GURL tree_url( 190 "data:text/html,<html><head><title>Accessibility Mac Test</title></head>" 191 "<body><input type='button' value='push' /><input type='checkbox' />" 192 "</body></html>"); 193 NavigateToURLAsync(tree_url); 194 195 // Test for navigation. 196 AddExpectedEvent(@"AXLoadComplete"); 197 198 // Check all the expected Mac notifications. 199 WaitAndAssertAllEventsObserved(); 200} 201