• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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