blob: 127f0b02184c32741e0b050e2ba33e5c34ac72ee [file] [log] [blame]
// Copyright 2020 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_inspect_utils_mac.h"
#include <ostream>
#include "base/containers/fixed_flat_set.h"
#include "base/debug/stack_trace.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/strings/pattern.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/accessibility/platform/ax_private_attributes_mac.h"
#include "ui/accessibility/platform/inspect/ax_element_wrapper_mac.h"
// error: 'accessibilityAttributeNames' is deprecated: first deprecated in
// macOS 10.10 - Use the NSAccessibility protocol methods instead (see
// NSAccessibilityProtocols.h
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
namespace ui {
using base::SysNSStringToUTF8;
const char kChromeTitle[] = "Google Chrome";
const char kChromiumTitle[] = "Chromium";
const char kFirefoxTitle[] = "Firefox";
const char kSafariTitle[] = "Safari";
struct NSStringComparator {
bool operator()(NSString* lhs, NSString* rhs) const {
return [lhs compare:rhs] == NSOrderedAscending;
}
};
bool IsValidAXAttribute(const std::string& attribute) {
// static local to avoid a global static constructor.
static auto kValidAttributes = base::MakeFixedFlatSet<NSString*>(
{NSAccessibilityAccessKeyAttribute,
NSAccessibilityARIAAtomicAttribute,
NSAccessibilityARIABusyAttribute,
NSAccessibilityARIAColumnCountAttribute,
NSAccessibilityARIAColumnIndexAttribute,
NSAccessibilityARIACurrentAttribute,
NSAccessibilityARIALiveAttribute,
NSAccessibilityARIAPosInSetAttribute,
NSAccessibilityARIARelevantAttribute,
NSAccessibilityARIARowCountAttribute,
NSAccessibilityARIARowIndexAttribute,
NSAccessibilityARIASetSizeAttribute,
NSAccessibilityAutocompleteValueAttribute,
NSAccessibilityBlockQuoteLevelAttribute,
NSAccessibilityBrailleLabelAttribute,
NSAccessibilityBrailleRoleDescription,
NSAccessibilityChromeAXNodeIdAttribute,
NSAccessibilityColumnHeaderUIElementsAttribute,
NSAccessibilityDescriptionAttribute,
NSAccessibilityDetailsElementsAttribute,
NSAccessibilityDOMClassList,
NSAccessibilityDropEffectsAttribute,
NSAccessibilityElementBusyAttribute,
NSAccessibilityFocusableAncestorAttribute,
NSAccessibilityGrabbedAttribute,
NSAccessibilityHasPopupAttribute,
NSAccessibilityInvalidAttribute,
NSAccessibilityIsMultiSelectable,
NSAccessibilityKeyShortcutsValueAttribute,
NSAccessibilityLoadedAttribute,
NSAccessibilityLoadingProgressAttribute,
NSAccessibilityMathFractionNumeratorAttribute,
NSAccessibilityMathFractionDenominatorAttribute,
NSAccessibilityMathRootRadicandAttribute,
NSAccessibilityMathRootIndexAttribute,
NSAccessibilityMathBaseAttribute,
NSAccessibilityMathSubscriptAttribute,
NSAccessibilityMathSuperscriptAttribute,
NSAccessibilityMathUnderAttribute,
NSAccessibilityMathOverAttribute,
NSAccessibilityMathPostscriptsAttribute,
NSAccessibilityMathPrescriptsAttribute,
NSAccessibilityOwnsAttribute,
NSAccessibilityPopupValueAttribute,
NSAccessibilityRequiredAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilitySelectedAttribute,
NSAccessibilitySizeAttribute,
NSAccessibilityTitleAttribute,
NSAccessibilityTitleUIElementAttribute,
NSAccessibilityURLAttribute,
NSAccessibilityVisitedAttribute},
NSStringComparator());
return kValidAttributes.contains(base::SysUTF8ToNSString(attribute));
}
NSArray* AXChildrenOf(const id node) {
return AXElementWrapper(node).Children();
}
std::string GetDOMId(const id node) {
return AXElementWrapper(node).DOMId();
}
AXUIElementRef FindAXUIElement(const AXUIElementRef node,
const AXFindCriteria& criteria) {
if (criteria.Run(node))
return node;
NSArray* children = AXChildrenOf(static_cast<id>(node));
for (id child in children) {
AXUIElementRef found =
FindAXUIElement(static_cast<AXUIElementRef>(child), criteria);
if (found != nil)
return found;
}
return nil;
}
std::pair<AXUIElementRef, int> FindAXUIElement(const AXTreeSelector& selector) {
if (selector.widget) {
return {AXUIElementCreateApplication(selector.widget), selector.widget};
}
NSArray* windows = static_cast<NSArray*>(CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
kCGNullWindowID));
std::string title;
if (selector.types & AXTreeSelector::Chrome)
title = kChromeTitle;
else if (selector.types & AXTreeSelector::Chromium)
title = kChromiumTitle;
else if (selector.types & AXTreeSelector::Firefox)
title = kFirefoxTitle;
else if (selector.types & AXTreeSelector::Safari)
title = kSafariTitle;
else
return {nil, 0};
for (NSDictionary* window_info in windows) {
int pid =
[static_cast<NSNumber*>([window_info objectForKey:@"kCGWindowOwnerPID"])
intValue];
std::string window_name = SysNSStringToUTF8(static_cast<NSString*>(
[window_info objectForKey:@"kCGWindowOwnerName"]));
AXUIElementRef node = nil;
// Application pre-defined selectors match or application title exact match.
bool appTitleMatch = window_name == selector.pattern;
if (window_name == title || appTitleMatch)
node = AXUIElementCreateApplication(pid);
// Window title match. Application contain an AXWindow accessible object as
// a first child, which accessible name contain a window title. For example:
// 'Inbox (2) - asurkov@igalia.com - Gmail'.
if (!selector.pattern.empty() && !appTitleMatch) {
if (!node)
node = AXUIElementCreateApplication(pid);
AXUIElementRef window = FindAXWindowChild(node, selector.pattern);
if (window)
node = window;
}
// ActiveTab selector.
if (node && selector.types & AXTreeSelector::ActiveTab) {
node = FindAXUIElement(
node, base::BindRepeating([](const AXUIElementRef node) {
// Only active tab in exposed in browsers, thus find first
// AXWebArea role.
AXElementWrapper ax_node(static_cast<id>(node));
NSString* role =
*ax_node.GetAttributeValue(NSAccessibilityRoleAttribute);
return SysNSStringToUTF8(role) == "AXWebArea";
}));
}
// Found a match.
if (node)
return {node, pid};
}
return {nil, 0};
}
AXUIElementRef FindAXWindowChild(AXUIElementRef parent,
const std::string& pattern) {
NSArray* children = AXChildrenOf(static_cast<id>(parent));
if ([children count] == 0)
return nil;
id window = [children objectAtIndex:0];
AXElementWrapper ax_window(window);
NSString* role = *ax_window.GetAttributeValue(NSAccessibilityRoleAttribute);
if (SysNSStringToUTF8(role) != "AXWindow")
return nil;
NSString* window_title =
*ax_window.GetAttributeValue(NSAccessibilityTitleAttribute);
if (base::MatchPattern(SysNSStringToUTF8(window_title), pattern))
return static_cast<AXUIElementRef>(window);
return nil;
}
} // namespace ui
#pragma clang diagnostic pop