Home | History | Annotate | Download | only in accessibility
      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.
     18 class 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 
    184 scoped_nsobject<NSMutableArray> AccessibilityMacUITest::expectedEvents;
    185 scoped_nsobject<NSArray> AccessibilityMacUITest::observedNotifications;
    186 
    187 TEST_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