Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / accessibility / accessibility_event_recorder_mac.mm
blob95967f43b64e4c917effbfa13da24fff41f12624
1 // Copyright (c) 2014 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.
5 #include "content/browser/accessibility/accessibility_event_recorder.h"
7 #include <string>
9 #import <Cocoa/Cocoa.h>
11 #include "base/mac/foundation_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "content/browser/accessibility/browser_accessibility_manager.h"
19 namespace content {
21 // Implementation of AccessibilityEventRecorder that uses AXObserver to
22 // watch for NSAccessibility events.
23 class AccessibilityEventRecorderMac : public AccessibilityEventRecorder {
24  public:
25   explicit AccessibilityEventRecorderMac(BrowserAccessibilityManager* manager);
26   ~AccessibilityEventRecorderMac() override;
28   // Callback executed every time we receive an event notification.
29   void EventReceived(AXUIElementRef element, CFStringRef notification);
31  private:
32   // Add one notification to the list of notifications monitored by our
33   // observer.
34   void AddNotification(NSString* notification);
36   // Convenience function to get the value of an AX attribute from
37   // an AXUIElementRef as a string.
38   std::string GetAXAttributeValue(
39       AXUIElementRef element, NSString* attribute_name);
41   // The AXUIElement for the Chrome application.
42   AXUIElementRef application_;
44   // The AXObserver we use to monitor AX notifications.
45   AXObserverRef observer_ref_;
48 // Callback function registered using AXObserverCreate.
49 static void EventReceivedThunk(
50     AXObserverRef observer_ref,
51     AXUIElementRef element,
52     CFStringRef notification,
53     void *refcon) {
54   AccessibilityEventRecorderMac* this_ptr =
55       static_cast<AccessibilityEventRecorderMac*>(refcon);
56   this_ptr->EventReceived(element, notification);
59 // static
60 AccessibilityEventRecorder* AccessibilityEventRecorder::Create(
61     BrowserAccessibilityManager* manager) {
62   return new AccessibilityEventRecorderMac(manager);
65 AccessibilityEventRecorderMac::AccessibilityEventRecorderMac(
66     BrowserAccessibilityManager* manager)
67     : AccessibilityEventRecorder(manager),
68       observer_ref_(0) {
69   // Get Chrome's process id.
70   int pid = [[NSProcessInfo processInfo] processIdentifier];
71   if (kAXErrorSuccess != AXObserverCreate(
72       pid, EventReceivedThunk, &observer_ref_)) {
73     LOG(FATAL) << "Failed to create AXObserverRef";
74   }
76   // Get an AXUIElement for the Chrome application.
77   application_ = AXUIElementCreateApplication(pid);
78   if (!application_)
79     LOG(FATAL) << "Failed to create AXUIElement for application.";
81   // Add the notifications we care about to the observer.
82   AddNotification(NSAccessibilityFocusedUIElementChangedNotification);
83   AddNotification(NSAccessibilityRowCountChangedNotification);
84   AddNotification(NSAccessibilitySelectedChildrenChangedNotification);
85   AddNotification(NSAccessibilitySelectedRowsChangedNotification);
86   AddNotification(NSAccessibilitySelectedTextChangedNotification);
87   AddNotification(NSAccessibilityValueChangedNotification);
88   AddNotification(@"AXLayoutComplete");
89   AddNotification(@"AXLiveRegionChanged");
90   AddNotification(@"AXLoadComplete");
91   AddNotification(@"AXRowCollapsed");
92   AddNotification(@"AXRowExpanded");
94   // Add the observer to the current message loop.
95   CFRunLoopAddSource(
96       [[NSRunLoop currentRunLoop] getCFRunLoop],
97       AXObserverGetRunLoopSource(observer_ref_),
98       kCFRunLoopDefaultMode);
101 AccessibilityEventRecorderMac::~AccessibilityEventRecorderMac() {
102   CFRelease(application_);
103   CFRelease(observer_ref_);
106 void AccessibilityEventRecorderMac::AddNotification(NSString* notification) {
107   AXObserverAddNotification(observer_ref_,
108                             application_,
109                             static_cast<CFStringRef>(notification),
110                             this);
113 std::string AccessibilityEventRecorderMac::GetAXAttributeValue(
114     AXUIElementRef element, NSString* attribute_name) {
115   CFTypeRef value;
116   AXError err = AXUIElementCopyAttributeValue(
117       element, static_cast<CFStringRef>(attribute_name), &value);
118   if (err != kAXErrorSuccess)
119     return std::string();
120   return base::SysNSStringToUTF8(
121       base::mac::CFToNSCast(static_cast<CFStringRef>(value)));
124 void AccessibilityEventRecorderMac::EventReceived(
125     AXUIElementRef element,
126     CFStringRef notification) {
127   std::string notification_str = base::SysNSStringToUTF8(
128       base::mac::CFToNSCast(notification));
129   std::string role = GetAXAttributeValue(element, NSAccessibilityRoleAttribute);
130   if (role.empty())
131     return;
132   std::string log = base::StringPrintf("%s on %s",
133       notification_str.c_str(), role.c_str());
135   std::string title = GetAXAttributeValue(element,
136                                           NSAccessibilityTitleAttribute);
137   if (!title.empty())
138     log += base::StringPrintf(" AXTitle=\"%s\"", title.c_str());
140   std::string description = GetAXAttributeValue(element,
141                                           NSAccessibilityDescriptionAttribute);
142   if (!description.empty())
143     log += base::StringPrintf(" AXDescription=\"%s\"", description.c_str());
145   std::string value = GetAXAttributeValue(element,
146                                           NSAccessibilityValueAttribute);
147   if (!value.empty())
148     log += base::StringPrintf(" AXValue=\"%s\"", value.c_str());
150   event_logs_.push_back(log);
153 }  // namespace content