chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.h

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_ELEMENT_WRAPPER_MAC_H_
#define UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_ELEMENT_WRAPPER_MAC_H_

#import <Cocoa/Cocoa.h>

#include <optional>

#include "base/component_export.h"
#include "base/functional/callback_forward.h"
#include "ui/accessibility/platform/inspect/ax_inspect.h"
#include "ui/accessibility/platform/inspect/ax_optional.h"

namespace ui {

// Optional tri-state id object.
using AXOptionalNSObject = AXOptional<id>;

// A wrapper around either AXUIElement or NSAccessibilityElement object.
class COMPONENT_EXPORT(AX_PLATFORM) AXElementWrapper final {
 public:
  // Returns true if the object is either NSAccessibilityElement or
  // AXUIElement.
  static bool IsValidElement(const id node);

  // Return true if the object is internal BrowserAccessibilityCocoa.
  static bool IsNSAccessibilityElement(const id node);

  // Returns true if the object is AXUIElement.
  static bool IsAXUIElement(const id node);

  // Returns the children of an accessible object, either AXUIElement or
  // BrowserAccessibilityCocoa.
  static NSArray* ChildrenOf(const id node);

  // Returns the DOM id of a given node (either AXUIElement or
  // BrowserAccessibilityCocoa).
  static std::string DOMIdOf(const id node);

  explicit AXElementWrapper(const id node) : node_(node) {}

  // Returns true if the object is either an NSAccessibilityElement or
  // AXUIElement.
  bool IsValidElement() const;

  // Return true if the object is an internal BrowserAccessibilityCocoa.
  bool IsNSAccessibilityElement() const;

  // Returns true if the object is an AXUIElement.
  bool IsAXUIElement() const;

  // Returns the wrapped object.
  id AsId() const;

  // Returns the DOM id of the object.
  std::string DOMId() const;

  // Returns the children of the object.
  NSArray* Children() const;

  // Returns the AXSize and AXPosition attributes for the object.
  NSSize Size() const;
  NSPoint Position() const;

  // Returns the (parameterized) attributes of the object.
  NSArray* AttributeNames() const;
  NSArray* ParameterizedAttributeNames() const;

  // Returns (parameterized) attribute value on the object.
  AXOptionalNSObject GetAttributeValue(NSString* attribute) const;
  AXOptionalNSObject GetParameterizedAttributeValue(NSString* attribute,
                                                    id parameter) const;

  // Performs the given selector on the object and returns the result. If
  // the object does not conform to the NSAccessibility protocol or the selector
  // is not found, then returns nullopt.
  std::optional<id> PerformSelector(const std::string& selector) const;

  // Performs the given selector on the object with exactly one string
  // argument and returns the result. If the object does not conform to the
  // NSAccessibility protocol or the selector is not found, then returns
  // nullopt.
  std::optional<id> PerformSelector(const std::string& selector_string,
                                    const std::string& argument_string) const;

  // Sets attribute value on the object.
  void SetAttributeValue(NSString* attribute, id value) const;

  // Returns the list of actions supported on the object.
  NSArray* ActionNames() const;

  // Performs the given action on the object.
  void PerformAction(NSString* action) const;

  // Returns true if the object responds to the given selector.
  bool RespondsToSelector(SEL selector) const {
    return [node_ respondsToSelector:selector];
  }

  // Invokes a method of the given signature.
  template <typename ReturnType, typename... Args>
  typename std::enable_if<!std::is_same<ReturnType, void>::value,
                          ReturnType>::type
  Invoke(SEL selector, Args... args) const {
    NSInvocation* invocation = InvokeInternal(selector, args...);
    ReturnType return_value;
    [invocation getReturnValue:&return_value];
    return return_value;
  }

  template <typename ReturnType, typename... Args>
  typename std::enable_if<std::is_same<ReturnType, void>::value, void>::type
  Invoke(SEL selector, Args... args) const {
    InvokeInternal(selector, args...);
  }

 private:
  template <typename... Args>
  NSInvocation* InvokeInternal(SEL selector, Args... args) const {
    NSInvocation* invocation = [NSInvocation
        invocationWithMethodSignature:
            [[node_ class] instanceMethodSignatureForSelector:selector]];
    [invocation setSelector:selector];
    [invocation setTarget:node_];
    SetInvocationArguments<Args...>(invocation, 2, args...);
    [invocation invoke];
    return invocation;
  }

  template <typename Arg, typename... Args>
  void SetInvocationArguments(NSInvocation* invocation,
                              int argument_index,
                              Arg& arg,
                              Args&... args) const {
    [invocation setArgument:&arg atIndex:argument_index];
    SetInvocationArguments<Args...>(invocation, argument_index + 1, args...);
  }

  template <typename... Args>
  void SetInvocationArguments(NSInvocation*, int) const {}

  // Generates an error message from the given error.
  std::string AXErrorMessage(AXError, const std::string& message) const;

  // Returns true on success, otherwise returns false and logs error.
  bool AXSuccess(AXError result, const std::string& message) const;

  // Converts the given value and the error object into AXOptional object.
  AXOptionalNSObject ToOptional(id, AXError, const std::string& message) const;

  id __strong node_;
};

}  // namespace ui

#endif  // UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_ELEMENT_WRAPPER_MAC_H_