| // 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(); |
| } |