[go: nahoru, domu]

blob: 9df34057e37543b95ea0580f11da841041cbf977 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/accessibility/platform/inspect/ax_event_recorder_mac.h"
#import <Cocoa/Cocoa.h>
#include <algorithm>
#include <string>
#include "base/apple/bridging.h"
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/accessibility/platform/ax_private_webkit_constants_mac.h"
#include "ui/accessibility/platform/inspect/ax_inspect_utils_mac.h"
#include "ui/accessibility/platform/inspect/ax_tree_formatter_mac.h"
namespace ui {
// Callback function registered using AXObserverCreate.
static void EventReceivedThunk(AXObserverRef observer_ref,
AXUIElementRef element,
CFStringRef notification,
CFDictionaryRef user_info,
void* refcon) {
AXEventRecorderMac* this_ptr = static_cast<AXEventRecorderMac*>(refcon);
this_ptr->EventReceived(element, notification, user_info);
}
AXEventRecorderMac::AXEventRecorderMac(base::ProcessId pid,
const AXTreeSelector& selector)
: observer_run_loop_source_(nullptr) {
base::ScopedCFTypeRef<AXUIElementRef> node;
if (pid) {
node.reset(AXUIElementCreateApplication(pid));
if (!node) {
LOG(FATAL) << "Failed to get AXUIElement for pid " << pid;
}
} else {
std::tie(node, pid) = FindAXUIElement(selector);
if (!node) {
LOG(FATAL) << "Failed to get AXUIElement for selector";
}
}
if (kAXErrorSuccess !=
AXObserverCreateWithInfoCallback(pid, EventReceivedThunk,
observer_ref_.InitializeInto())) {
LOG(FATAL) << "Failed to create AXObserverRef";
}
// Get an AXUIElement for the Chrome application.
application_ = std::move(node);
if (!application_.get())
LOG(FATAL) << "Failed to create AXUIElement for application.";
// Add the notifications we care about to the observer.
static NSArray* notifications = @[
@"AXAutocorrectionOccurred",
@"AXElementBusyChanged",
@"AXExpandedChanged",
@"AXInvalidStatusChanged",
@"AXLiveRegionChanged",
@"AXLiveRegionCreated",
@"AXLoadComplete",
@"AXMenuItemSelected",
(NSString*)kAXMenuClosedNotification,
(NSString*)kAXMenuOpenedNotification,
NSAccessibilityAnnouncementRequestedNotification,
NSAccessibilityApplicationActivatedNotification,
NSAccessibilityApplicationDeactivatedNotification,
NSAccessibilityApplicationHiddenNotification,
NSAccessibilityApplicationShownNotification,
NSAccessibilityCreatedNotification,
NSAccessibilityDrawerCreatedNotification,
NSAccessibilityFocusedUIElementChangedNotification,
NSAccessibilityFocusedWindowChangedNotification,
NSAccessibilityHelpTagCreatedNotification,
NSAccessibilityLayoutChangedNotification,
NSAccessibilityMainWindowChangedNotification,
NSAccessibilityMovedNotification,
NSAccessibilityResizedNotification,
NSAccessibilityRowCollapsedNotification,
NSAccessibilityRowCountChangedNotification,
NSAccessibilityRowExpandedNotification,
NSAccessibilitySelectedCellsChangedNotification,
NSAccessibilitySelectedChildrenChangedNotification,
NSAccessibilitySelectedChildrenMovedNotification,
NSAccessibilitySelectedColumnsChangedNotification,
NSAccessibilitySelectedRowsChangedNotification,
NSAccessibilitySelectedTextChangedNotification,
NSAccessibilitySheetCreatedNotification,
NSAccessibilityTitleChangedNotification,
NSAccessibilityUIElementDestroyedNotification,
NSAccessibilityUnitsChangedNotification,
NSAccessibilityValueChangedNotification,
NSAccessibilityWindowCreatedNotification,
NSAccessibilityWindowDeminiaturizedNotification,
NSAccessibilityWindowMiniaturizedNotification,
NSAccessibilityWindowMovedNotification,
NSAccessibilityWindowResizedNotification,
];
for (NSString* notification : notifications) {
AddNotification(notification);
}
// Add the observer to the current message loop.
observer_run_loop_source_ = AXObserverGetRunLoopSource(observer_ref_.get());
CFRunLoopAddSource(CFRunLoopGetCurrent(), observer_run_loop_source_,
kCFRunLoopDefaultMode);
}
AXEventRecorderMac::~AXEventRecorderMac() {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), observer_run_loop_source_,
kCFRunLoopDefaultMode);
}
void AXEventRecorderMac::AddNotification(NSString* notification) {
AXObserverAddNotification(observer_ref_, application_,
base::apple::NSToCFPtrCast(notification), this);
}
void AXEventRecorderMac::EventReceived(AXUIElementRef element,
CFStringRef notification,
CFDictionaryRef user_info) {
std::string notification_str = base::SysCFStringRefToUTF8(notification);
auto formatter = ui::AXTreeFormatterMac();
formatter.SetPropertyFilters(property_filters_,
AXTreeFormatter::kFiltersDefaultSet);
std::string element_str =
formatter.FormatTree(formatter.BuildNode((__bridge id)element));
// Element dumps contain a new line character at the end, remove it.
if (!element_str.empty() && element_str.back() == '\n') {
element_str.pop_back();
}
std::string log = base::StringPrintf("%s on %s", notification_str.c_str(),
element_str.c_str());
if (notification_str ==
base::SysNSStringToUTF8(NSAccessibilitySelectedTextChangedNotification))
log += " " + SerializeTextSelectionChangedProperties(user_info);
OnEvent(log);
}
std::string AXEventRecorderMac::SerializeTextSelectionChangedProperties(
CFDictionaryRef user_info) {
if (user_info == nil) {
return {};
}
NSDictionary* ns_user_info = base::apple::CFToNSPtrCast(user_info);
std::vector<std::string> serialized_info;
for (NSString* key in ns_user_info) {
NSNumber* value = base::apple::ObjCCast<NSNumber>(ns_user_info[key]);
std::string value_string;
if ([key isEqual:NSAccessibilityTextStateChangeTypeKey]) {
value_string =
ToString(static_cast<AXTextStateChangeType>(value.intValue));
} else if ([key isEqual:NSAccessibilityTextSelectionDirection]) {
value_string =
ToString(static_cast<AXTextSelectionDirection>(value.intValue));
} else if ([key isEqual:NSAccessibilityTextSelectionGranularity]) {
value_string =
ToString(static_cast<AXTextSelectionGranularity>(value.intValue));
} else if ([key isEqual:NSAccessibilityTextEditType]) {
value_string = ToString(static_cast<AXTextEditType>(value.intValue));
} else {
continue;
}
serialized_info.push_back(base::SysNSStringToUTF8(key) + "=" +
value_string);
}
// Always sort the info so that we don't depend on CFDictionary for
// consistent output ordering.
std::sort(serialized_info.begin(), serialized_info.end());
return base::JoinString(serialized_info, " ");
}
} // namespace ui