| // Copyright (c) 2014 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. |
| |
| #include "content/browser/accessibility/accessibility_event_recorder.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "content/browser/accessibility/browser_accessibility_manager.h" |
| |
| namespace content { |
| |
| // Implementation of AccessibilityEventRecorder that uses AXObserver to |
| // watch for NSAccessibility events. |
| class AccessibilityEventRecorderMac : public AccessibilityEventRecorder { |
| public: |
| AccessibilityEventRecorderMac(BrowserAccessibilityManager* manager, |
| base::ProcessId pid); |
| ~AccessibilityEventRecorderMac() override; |
| |
| // Callback executed every time we receive an event notification. |
| void EventReceived(AXUIElementRef element, CFStringRef notification); |
| |
| private: |
| // Add one notification to the list of notifications monitored by our |
| // observer. |
| void AddNotification(NSString* notification); |
| |
| // Convenience function to get the value of an AX attribute from |
| // an AXUIElementRef as a string. |
| std::string GetAXAttributeValue( |
| AXUIElementRef element, NSString* attribute_name); |
| |
| // The AXUIElement for the Chrome application. |
| base::ScopedCFTypeRef<AXUIElementRef> application_; |
| |
| // The AXObserver we use to monitor AX notifications. |
| base::ScopedCFTypeRef<AXObserverRef> observer_ref_; |
| CFRunLoopSourceRef observer_run_loop_source_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderMac); |
| }; |
| |
| // Callback function registered using AXObserverCreate. |
| static void EventReceivedThunk( |
| AXObserverRef observer_ref, |
| AXUIElementRef element, |
| CFStringRef notification, |
| void *refcon) { |
| AccessibilityEventRecorderMac* this_ptr = |
| static_cast<AccessibilityEventRecorderMac*>(refcon); |
| this_ptr->EventReceived(element, notification); |
| } |
| |
| // static |
| std::unique_ptr<AccessibilityEventRecorder> AccessibilityEventRecorder::Create( |
| BrowserAccessibilityManager* manager, |
| base::ProcessId pid, |
| const base::StringPiece& application_name_match_pattern) { |
| if (!application_name_match_pattern.empty()) { |
| LOG(ERROR) << "Recording accessibility events from an application name " |
| "match pattern not supported on this platform yet."; |
| NOTREACHED(); |
| } |
| |
| return std::make_unique<AccessibilityEventRecorderMac>(manager, pid); |
| } |
| |
| AccessibilityEventRecorderMac::AccessibilityEventRecorderMac( |
| BrowserAccessibilityManager* manager, |
| base::ProcessId pid) |
| : AccessibilityEventRecorder(manager), observer_run_loop_source_(NULL) { |
| if (kAXErrorSuccess != AXObserverCreate(pid, EventReceivedThunk, |
| observer_ref_.InitializeInto())) { |
| LOG(FATAL) << "Failed to create AXObserverRef"; |
| } |
| |
| // Get an AXUIElement for the Chrome application. |
| application_.reset(AXUIElementCreateApplication(pid)); |
| if (!application_.get()) |
| LOG(FATAL) << "Failed to create AXUIElement for application."; |
| |
| // Add the notifications we care about to the observer. |
| AddNotification(@"AXAutocorrectionOccurred"); |
| AddNotification(@"AXExpandedChanged"); |
| AddNotification(@"AXInvalidStatusChanged"); |
| AddNotification(@"AXLiveRegionChanged"); |
| AddNotification(@"AXLiveRegionCreated"); |
| AddNotification(@"AXLoadComplete"); |
| AddNotification(@"AXMenuItemSelected"); |
| AddNotification(@"AXRowCollapsed"); |
| AddNotification(@"AXRowExpanded"); |
| AddNotification(NSAccessibilityFocusedUIElementChangedNotification); |
| AddNotification(NSAccessibilityRowCollapsedNotification); |
| AddNotification(NSAccessibilityRowCountChangedNotification); |
| AddNotification(NSAccessibilitySelectedChildrenChangedNotification); |
| AddNotification(NSAccessibilitySelectedRowsChangedNotification); |
| AddNotification(NSAccessibilitySelectedTextChangedNotification); |
| AddNotification(NSAccessibilityValueChangedNotification); |
| |
| // Add the observer to the current message loop. |
| observer_run_loop_source_ = AXObserverGetRunLoopSource(observer_ref_.get()); |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), observer_run_loop_source_, |
| kCFRunLoopDefaultMode); |
| } |
| |
| AccessibilityEventRecorderMac::~AccessibilityEventRecorderMac() { |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), observer_run_loop_source_, |
| kCFRunLoopDefaultMode); |
| } |
| |
| void AccessibilityEventRecorderMac::AddNotification(NSString* notification) { |
| AXObserverAddNotification(observer_ref_, application_, |
| base::mac::NSToCFCast(notification), this); |
| } |
| |
| std::string AccessibilityEventRecorderMac::GetAXAttributeValue( |
| AXUIElementRef element, |
| NSString* attribute_name) { |
| base::ScopedCFTypeRef<CFTypeRef> value; |
| AXError err = AXUIElementCopyAttributeValue( |
| element, base::mac::NSToCFCast(attribute_name), value.InitializeInto()); |
| if (err != kAXErrorSuccess) |
| return std::string(); |
| |
| CFStringRef value_string = base::mac::CFCast<CFStringRef>(value.get()); |
| if (value_string) |
| return base::SysCFStringRefToUTF8(value_string); |
| |
| // TODO(dmazzoni): And if it's not a string, can we return something better? |
| return {}; |
| } |
| |
| void AccessibilityEventRecorderMac::EventReceived(AXUIElementRef element, |
| CFStringRef notification) { |
| std::string notification_str = base::SysCFStringRefToUTF8(notification); |
| std::string role = GetAXAttributeValue(element, NSAccessibilityRoleAttribute); |
| if (role.empty()) |
| return; |
| std::string log = |
| base::StringPrintf("%s on %s", notification_str.c_str(), role.c_str()); |
| |
| std::string title = |
| GetAXAttributeValue(element, NSAccessibilityTitleAttribute); |
| if (!title.empty()) |
| log += base::StringPrintf(" AXTitle=\"%s\"", title.c_str()); |
| |
| std::string description = |
| GetAXAttributeValue(element, NSAccessibilityDescriptionAttribute); |
| if (!description.empty()) |
| log += base::StringPrintf(" AXDescription=\"%s\"", description.c_str()); |
| |
| std::string value = |
| GetAXAttributeValue(element, NSAccessibilityValueAttribute); |
| if (!value.empty()) |
| log += base::StringPrintf(" AXValue=\"%s\"", value.c_str()); |
| |
| OnEvent(log); |
| } |
| |
| } // namespace content |