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"
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"
21 // Implementation of AccessibilityEventRecorder that uses AXObserver to
22 // watch for NSAccessibility events.
23 class AccessibilityEventRecorderMac : public AccessibilityEventRecorder {
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);
32 // Add one notification to the list of notifications monitored by our
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,
54 AccessibilityEventRecorderMac* this_ptr =
55 static_cast<AccessibilityEventRecorderMac*>(refcon);
56 this_ptr->EventReceived(element, notification);
60 AccessibilityEventRecorder* AccessibilityEventRecorder::Create(
61 BrowserAccessibilityManager* manager) {
62 return new AccessibilityEventRecorderMac(manager);
65 AccessibilityEventRecorderMac::AccessibilityEventRecorderMac(
66 BrowserAccessibilityManager* manager)
67 : AccessibilityEventRecorder(manager),
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";
76 // Get an AXUIElement for the Chrome application.
77 application_ = AXUIElementCreateApplication(pid);
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.
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_,
109 static_cast<CFStringRef>(notification),
113 std::string AccessibilityEventRecorderMac::GetAXAttributeValue(
114 AXUIElementRef element, NSString* attribute_name) {
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);
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);
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);
148 log += base::StringPrintf(" AXValue=\"%s\"", value.c_str());
150 event_logs_.push_back(log);
153 } // namespace content