chromium/ui/accessibility/platform/ax_system_caret_win.cc

// Copyright 2017 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_system_caret_win.h"

#include <windows.h>

#include "base/check.h"
#include "base/notreached.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"

namespace ui {

// static
void AXSystemCaretWin::AXPlatformNodeWinDeleter(AXPlatformNodeWin* ptr) {
  ptr->Destroy();
}

AXSystemCaretWin::AXSystemCaretWin(gfx::AcceleratedWidget event_target)
    : event_target_(event_target) {
  caret_.reset(
      static_cast<AXPlatformNodeWin*>(AXPlatformNodeWin::Create(this)));
  // The caret object is not part of the accessibility tree and so doesn't need
  // a node ID. A globally unique ID is used when firing Win events, retrieved
  // via |unique_id|.
  data_.id = -1;
  data_.role = ax::mojom::Role::kCaret;
  // |get_accState| should return 0 which means that the caret is visible.
  data_.state = 0;
  data_.AddState(ax::mojom::State::kInvisible);
  // According to MSDN, "Edit" should be the name of the caret object.
  data_.SetName(u"Edit");
  data_.relative_bounds.offset_container_id = -1;

  if (event_target_) {
    ::NotifyWinEvent(EVENT_OBJECT_CREATE, event_target_, OBJID_CARET,
                     -caret_->GetUniqueId());
  }
}

AXSystemCaretWin::~AXSystemCaretWin() {
  if (event_target_) {
    ::NotifyWinEvent(EVENT_OBJECT_DESTROY, event_target_, OBJID_CARET,
                     -caret_->GetUniqueId());
  }
}

Microsoft::WRL::ComPtr<IAccessible> AXSystemCaretWin::GetCaret() const {
  Microsoft::WRL::ComPtr<IAccessible> caret_accessible;
  HRESULT hr = caret_->QueryInterface(IID_PPV_ARGS(&caret_accessible));
  DCHECK(SUCCEEDED(hr));
  return caret_accessible;
}

void AXSystemCaretWin::MoveCaretTo(const gfx::Rect& bounds_physical_pixels) {
  if (bounds_physical_pixels.IsEmpty())
    return;

  // If the caret has non-empty bounds, assume it has been made visible.
  bool newly_visible = false;
  if (data_.HasState(ax::mojom::State::kInvisible)) {
    newly_visible = true;
    data_.RemoveState(ax::mojom::State::kInvisible);
  }

  if (!event_target_)
    return;

  if (newly_visible) {
    ::NotifyWinEvent(EVENT_OBJECT_SHOW, event_target_, OBJID_CARET,
                     -caret_->GetUniqueId());
  }

  gfx::RectF new_location(bounds_physical_pixels);
  // Avoid redundant caret move events (if the location stays the same), but
  // always fire when it's made visible again.
  if (data_.relative_bounds.bounds != new_location || newly_visible) {
    data_.relative_bounds.bounds = new_location;
    ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, event_target_, OBJID_CARET,
                     -caret_->GetUniqueId());
  }
}

void AXSystemCaretWin::Hide() {
  if (!data_.HasState(ax::mojom::State::kInvisible)) {
    data_.AddState(ax::mojom::State::kInvisible);
    data_.relative_bounds.bounds.set_width(0);
    if (event_target_) {
      ::NotifyWinEvent(EVENT_OBJECT_HIDE, event_target_, OBJID_CARET,
                       -caret_->GetUniqueId());
    }
  }
}

const AXNodeData& AXSystemCaretWin::GetData() const {
  return data_;
}

gfx::NativeViewAccessible AXSystemCaretWin::GetParent() const {
  if (!event_target_)
    return nullptr;

  gfx::NativeViewAccessible parent;
  HRESULT hr =
      ::AccessibleObjectFromWindow(event_target_, OBJID_WINDOW, IID_IAccessible,
                                   reinterpret_cast<void**>(&parent));
  if (SUCCEEDED(hr))
    return parent;
  return nullptr;
}

gfx::Rect AXSystemCaretWin::GetBoundsRect(
    const AXCoordinateSystem coordinate_system,
    const AXClippingBehavior clipping_behavior,
    AXOffscreenResult* offscreen_result) const {
  switch (coordinate_system) {
    case AXCoordinateSystem::kScreenPhysicalPixels:
      // We could optionally add clipping here if ever needed.
      return ToEnclosingRect(data_.relative_bounds.bounds);
    case AXCoordinateSystem::kScreenDIPs:
      return display::win::ScreenWin::ScreenToDIPRect(
          event_target_, ToEnclosingRect(data_.relative_bounds.bounds));
    case AXCoordinateSystem::kRootFrame:
    case AXCoordinateSystem::kFrame:
      NOTIMPLEMENTED();
      return gfx::Rect();
  }
}

gfx::AcceleratedWidget
AXSystemCaretWin::GetTargetForNativeAccessibilityEvent() {
  return event_target_;
}

bool AXSystemCaretWin::ShouldIgnoreHoveredStateForTesting() {
  return false;
}

AXPlatformNodeId AXSystemCaretWin::GetUniqueId() const {
  return unique_id_;
}

}  // namespace ui