chromium/ui/accessibility/platform/ax_utils_mac.mm

// Copyright 2021 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/ax_utils_mac.h"

#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>

#include "base/apple/scoped_cftyperef.h"
#include "ui/accessibility/ax_range.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_node_cocoa.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"

namespace ui {

bool IsAXTextMarker(id object) {
  if (object == nil) {
    return false;
  }
  return CFGetTypeID((__bridge CFTypeRef)object) == AXTextMarkerGetTypeID();
}

bool IsAXTextMarkerRange(id object) {
  if (object == nil) {
    return false;
  }
  return CFGetTypeID((__bridge CFTypeRef)object) ==
         AXTextMarkerRangeGetTypeID();
}

AXPlatformNodeDelegate::AXPosition AXTextMarkerToAXPosition(id text_marker) {
  if (!IsAXTextMarker(text_marker)) {
    return AXNodePosition::CreateNullPosition();
  }

  AXTextMarkerRef cf_text_marker = (__bridge AXTextMarkerRef)text_marker;
  if (AXTextMarkerGetLength(cf_text_marker) !=
      sizeof(AXPlatformNodeDelegate::SerializedPosition)) {
    return AXNodePosition::CreateNullPosition();
  }

  const UInt8* source_buffer = AXTextMarkerGetBytePtr(cf_text_marker);
  if (!source_buffer) {
    return AXNodePosition::CreateNullPosition();
  }

  return AXNodePosition::Unserialize(
      *reinterpret_cast<const AXPlatformNodeDelegate::SerializedPosition*>(
          source_buffer));
}

AXPlatformNodeDelegate::AXRange AXTextMarkerRangeToAXRange(
    id text_marker_range) {
  if (!IsAXTextMarkerRange(text_marker_range)) {
    return AXPlatformNodeDelegate::AXRange();
  }

  AXTextMarkerRangeRef cf_marker_range =
      (__bridge AXTextMarkerRangeRef)text_marker_range;

  id start_marker =
      CFBridgingRelease(AXTextMarkerRangeCopyStartMarker(cf_marker_range));
  id end_marker =
      CFBridgingRelease(AXTextMarkerRangeCopyEndMarker(cf_marker_range));
  if (!start_marker || !end_marker) {
    return AXPlatformNodeDelegate::AXRange();
  }

  // |AXPlatformNodeDelegate::AXRange| takes ownership of its anchor and focus.
  AXPlatformNodeDelegate::AXPosition anchor =
      AXTextMarkerToAXPosition(start_marker);
  AXPlatformNodeDelegate::AXPosition focus =
      AXTextMarkerToAXPosition(end_marker);
  return AXPlatformNodeDelegate::AXRange(std::move(anchor), std::move(focus));
}

id AXPositionToAXTextMarker(AXPlatformNodeDelegate::AXPosition position) {
  // AXTextMarkerCreate is a system function that makes a copy of the data
  // buffer given to it.
  AXPlatformNodeDelegate::SerializedPosition serialized = position->Serialize();
  return CFBridgingRelease(AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&serialized),
      sizeof(AXPlatformNodeDelegate::SerializedPosition)));
}

id AXRangeToAXTextMarkerRange(AXPlatformNodeDelegate::AXRange range) {
  AXPlatformNodeDelegate::SerializedPosition serialized_anchor =
      range.anchor()->Serialize();
  AXPlatformNodeDelegate::SerializedPosition serialized_focus =
      range.focus()->Serialize();

  base::apple::ScopedCFTypeRef<AXTextMarkerRef> start_marker(AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&serialized_anchor),
      sizeof(AXPlatformNodeDelegate::SerializedPosition)));
  base::apple::ScopedCFTypeRef<AXTextMarkerRef> end_marker(AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&serialized_focus),
      sizeof(AXPlatformNodeDelegate::SerializedPosition)));

  return CFBridgingRelease(AXTextMarkerRangeCreate(
      kCFAllocatorDefault, start_marker.get(), end_marker.get()));
}

id AXTextMarkerFrom(AXPlatformNodeCocoa* anchor,
                    int offset,
                    ax::mojom::TextAffinity affinity) {
  AXPlatformNode* anchor_platform_node = anchor.node;
  AXPlatformNodeDelegate* anchor_node = anchor_platform_node->GetDelegate();
  AXPlatformNodeDelegate::AXPosition position =
      anchor_node->CreateTextPositionAt(offset, affinity);
  return AXPositionToAXTextMarker(std::move(position));
}

id AXTextMarkerRangeFrom(id start_textmarker, id end_textmarker) {
  return CFBridgingRelease(AXTextMarkerRangeCreate(
      kCFAllocatorDefault, (__bridge AXTextMarkerRef)start_textmarker,
      (__bridge AXTextMarkerRef)end_textmarker));
}

id AXTextMarkerRangeStart(id text_marker_range) {
  return CFBridgingRelease(AXTextMarkerRangeCopyStartMarker(
      (__bridge AXTextMarkerRangeRef)text_marker_range));
}

id AXTextMarkerRangeEnd(id text_marker_range) {
  return CFBridgingRelease(AXTextMarkerRangeCopyEndMarker(
      (__bridge AXTextMarkerRangeRef)text_marker_range));
}

}  // namespace ui