blob: f5ecd9759189b307964c1d2e861ebe1fdf865ac2 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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_platform_node_textprovider_win.h"
#include <utility>
#include "base/win/scoped_safearray.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h"
#define UIA_VALIDATE_TEXTPROVIDER_CALL() \
if (!owner()->GetDelegate()) \
return UIA_E_ELEMENTNOTAVAILABLE;
#define UIA_VALIDATE_TEXTPROVIDER_CALL_1_ARG(arg) \
if (!owner()->GetDelegate()) \
return UIA_E_ELEMENTNOTAVAILABLE; \
if (!arg) \
return E_INVALIDARG;
namespace ui {
AXPlatformNodeTextProviderWin::AXPlatformNodeTextProviderWin() {
DVLOG(1) << __func__;
}
AXPlatformNodeTextProviderWin::~AXPlatformNodeTextProviderWin() {}
// static
AXPlatformNodeTextProviderWin* AXPlatformNodeTextProviderWin::Create(
AXPlatformNodeWin* owner) {
CComObject<AXPlatformNodeTextProviderWin>* text_provider = nullptr;
if (SUCCEEDED(CComObject<AXPlatformNodeTextProviderWin>::CreateInstance(
&text_provider))) {
DCHECK(text_provider);
text_provider->owner_ = owner;
text_provider->AddRef();
return text_provider;
}
return nullptr;
}
// static
void AXPlatformNodeTextProviderWin::CreateIUnknown(AXPlatformNodeWin* owner,
IUnknown** unknown) {
Microsoft::WRL::ComPtr<AXPlatformNodeTextProviderWin> text_provider(
Create(owner));
if (text_provider)
*unknown = text_provider.Detach();
}
//
// ITextProvider methods.
//
HRESULT AXPlatformNodeTextProviderWin::GetSelection(SAFEARRAY** selection) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_GETSELECTION);
UIA_VALIDATE_TEXTPROVIDER_CALL();
*selection = nullptr;
AXPlatformNodeDelegate* delegate = owner()->GetDelegate();
ui::AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
AXPlatformNode* anchor_object =
delegate->GetFromNodeID(unignored_selection.anchor_object_id);
AXPlatformNode* focus_object =
delegate->GetFromNodeID(unignored_selection.focus_object_id);
// anchor_offset corresponds to the selection start index
// and focus_offset is where the selection ends.
auto start_offset = unignored_selection.anchor_offset;
auto end_offset = unignored_selection.focus_offset;
// If there's no selected object, return success and don't fill the SAFEARRAY.
if (!anchor_object || !focus_object)
return S_OK;
AXNodePosition::AXPositionInstance start =
anchor_object->GetDelegate()->CreateTextPositionAt(start_offset);
AXNodePosition::AXPositionInstance end =
focus_object->GetDelegate()->CreateTextPositionAt(end_offset);
DCHECK(!start->IsNullPosition());
DCHECK(!end->IsNullPosition());
// Reverse start and end if the selection goes backwards
if (*start > *end)
std::swap(start, end);
Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider =
AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
owner_.Get(), std::move(start), std::move(end));
if (&text_range_provider == nullptr)
return E_OUTOFMEMORY;
// Since we don't support disjoint text ranges, the SAFEARRAY returned
// will always have one element
base::win::ScopedSafearray selections_to_return(
SafeArrayCreateVector(VT_UNKNOWN /* element type */, 0 /* lower bound */,
1 /* number of elements */));
if (!selections_to_return.Get())
return E_OUTOFMEMORY;
LONG index = 0;
HRESULT hr = SafeArrayPutElement(selections_to_return.Get(), &index,
text_range_provider.Get());
DCHECK(SUCCEEDED(hr));
// Since DCHECK only happens in debug builds, return immediately to ensure
// that we're not leaking the SAFEARRAY on release builds
if (FAILED(hr))
return E_FAIL;
*selection = selections_to_return.Release();
return S_OK;
}
HRESULT AXPlatformNodeTextProviderWin::GetVisibleRanges(
SAFEARRAY** visible_ranges) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_GETVISIBLERANGES);
UIA_VALIDATE_TEXTPROVIDER_CALL();
const AXPlatformNodeDelegate* delegate = owner()->GetDelegate();
// Get the Clipped Frame Bounds of the current node, not from the root,
// so if this node is wrapped with overflow styles it will have the
// correct bounds
const gfx::Rect frame_rect = delegate->GetBoundsRect(
AXCoordinateSystem::kFrame, AXClippingBehavior::kClipped);
const auto start = delegate->CreateTextPositionAt(0);
const auto end = start->CreatePositionAtEndOfAnchor();
DCHECK(start->GetAnchor() == end->GetAnchor());
// SAFEARRAYs are not dynamic, so fill the visible ranges in a vector
// and then transfer to an appropriately-sized SAFEARRAY
std::vector<Microsoft::WRL::ComPtr<ITextRangeProvider>> ranges;
auto current_line_start = start->Clone();
while (!current_line_start->IsNullPosition() && *current_line_start < *end) {
auto current_line_end = current_line_start->CreateNextLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
if (current_line_end->IsNullPosition() || *current_line_end > *end)
current_line_end = end->Clone();
gfx::Rect current_rect = delegate->GetInnerTextRangeBoundsRect(
current_line_start->text_offset(), current_line_end->text_offset(),
AXCoordinateSystem::kFrame, AXClippingBehavior::kUnclipped);
if (frame_rect.Contains(current_rect)) {
Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider =
AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
owner_.Get(), current_line_start->Clone(),
current_line_end->Clone());
ranges.emplace_back(text_range_provider);
}
current_line_start = current_line_start->CreateNextLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}
base::win::ScopedSafearray scoped_visible_ranges(
SafeArrayCreateVector(VT_UNKNOWN /* element type */, 0 /* lower bound */,
ranges.size() /* number of elements */));
if (!scoped_visible_ranges.Get())
return E_OUTOFMEMORY;
LONG index = 0;
for (Microsoft::WRL::ComPtr<ITextRangeProvider>& current_provider : ranges) {
HRESULT hr = SafeArrayPutElement(scoped_visible_ranges.Get(), &index,
current_provider.Get());
DCHECK(SUCCEEDED(hr));
// Since DCHECK only happens in debug builds, return immediately to ensure
// that we're not leaking the SAFEARRAY on release builds
if (FAILED(hr))
return E_FAIL;
++index;
}
*visible_ranges = scoped_visible_ranges.Release();
return S_OK;
}
HRESULT AXPlatformNodeTextProviderWin::RangeFromChild(
IRawElementProviderSimple* child,
ITextRangeProvider** range) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_RANGEFROMCHILD);
UIA_VALIDATE_TEXTPROVIDER_CALL_1_ARG(child);
*range = nullptr;
Microsoft::WRL::ComPtr<ui::AXPlatformNodeWin> child_platform_node;
if (!SUCCEEDED(child->QueryInterface(IID_PPV_ARGS(&child_platform_node))))
return UIA_E_INVALIDOPERATION;
if (!owner()->IsDescendant(child_platform_node.Get()))
return E_INVALIDARG;
*range = GetRangeFromChild(owner(), child_platform_node.Get());
return S_OK;
}
HRESULT AXPlatformNodeTextProviderWin::RangeFromPoint(
UiaPoint uia_point,
ITextRangeProvider** range) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_RANGEFROMPOINT);
WIN_ACCESSIBILITY_API_PERF_HISTOGRAM(UMA_API_TEXT_RANGEFROMPOINT);
UIA_VALIDATE_TEXTPROVIDER_CALL();
*range = nullptr;
gfx::Point point(uia_point.x, uia_point.y);
// Retrieve the closest accessibility node. No coordinate unit conversion is
// needed, hit testing input is also in screen coordinates.
AXPlatformNodeWin* nearest_node =
static_cast<AXPlatformNodeWin*>(owner()->NearestLeafToPoint(point));
DCHECK(nearest_node);
DCHECK(nearest_node->IsLeaf());
AXNodePosition::AXPositionInstance start, end;
start = nearest_node->GetDelegate()->CreateTextPositionAt(
nearest_node->NearestTextIndexToPoint(point));
DCHECK(!start->IsNullPosition());
end = start->Clone();
*range = AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
nearest_node, std::move(start), std::move(end));
return S_OK;
}
HRESULT AXPlatformNodeTextProviderWin::get_DocumentRange(
ITextRangeProvider** range) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_GET_DOCUMENTRANGE);
UIA_VALIDATE_TEXTPROVIDER_CALL();
// Get range from child, where child is the current node. In other words,
// getting the text range of the current owner AxPlatformNodeWin node.
*range = GetRangeFromChild(owner(), owner());
return S_OK;
}
HRESULT AXPlatformNodeTextProviderWin::get_SupportedTextSelection(
enum SupportedTextSelection* text_selection) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_GET_SUPPORTEDTEXTSELECTION);
UIA_VALIDATE_TEXTPROVIDER_CALL();
*text_selection = SupportedTextSelection_Single;
return S_OK;
}
//
// ITextEditProvider methods.
//
HRESULT AXPlatformNodeTextProviderWin::GetActiveComposition(
ITextRangeProvider** range) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTEDIT_GETACTIVECOMPOSITION);
UIA_VALIDATE_TEXTPROVIDER_CALL();
*range = nullptr;
return GetTextRangeProviderFromActiveComposition(range);
}
HRESULT AXPlatformNodeTextProviderWin::GetConversionTarget(
ITextRangeProvider** range) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTEDIT_GETCONVERSIONTARGET);
UIA_VALIDATE_TEXTPROVIDER_CALL();
*range = nullptr;
return GetTextRangeProviderFromActiveComposition(range);
}
ITextRangeProvider* AXPlatformNodeTextProviderWin::GetRangeFromChild(
ui::AXPlatformNodeWin* ancestor,
ui::AXPlatformNodeWin* descendant) {
DCHECK(ancestor);
DCHECK(descendant);
DCHECK(descendant->GetDelegate());
DCHECK(ancestor->IsDescendant(descendant));
// Start and end should be leaf text positions that span the beginning and end
// of text content within a node. The start position should be the directly
// first child and the end position should be the deepest last child node.
AXNodePosition::AXPositionInstance start =
descendant->GetDelegate()->CreateTextPositionAt(0)->AsLeafTextPosition();
AXNodePosition::AXPositionInstance end;
if (descendant->IsDocument()) {
// Fast path for getting the range of the web root.
end = start->CreatePositionAtEndOfDocument();
} else if (descendant->GetChildCount() == 0) {
end = descendant->GetDelegate()
->CreateTextPositionAt(0)
->CreatePositionAtEndOfAnchor()
->AsLeafTextPosition();
} else {
AXPlatformNodeBase* deepest_last_child = descendant->GetLastChild();
while (deepest_last_child && deepest_last_child->GetChildCount() > 0)
deepest_last_child = deepest_last_child->GetLastChild();
end = deepest_last_child->GetDelegate()
->CreateTextPositionAt(0)
->CreatePositionAtEndOfAnchor()
->AsLeafTextPosition();
}
return AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
ancestor, std::move(start), std::move(end));
}
ui::AXPlatformNodeWin* AXPlatformNodeTextProviderWin::owner() const {
return owner_.Get();
}
HRESULT
AXPlatformNodeTextProviderWin::GetTextRangeProviderFromActiveComposition(
ITextRangeProvider** range) {
*range = nullptr;
// We fetch the start and end offset of an active composition only if
// this object has focus and TSF is in composition mode.
// The offsets here refer to the character positions in a plain text
// view of the DOM tree. Ex: if the active composition in an element
// has "abc" then the range will be (0,3) in both TSF and accessibility
if ((AXPlatformNode::FromNativeViewAccessible(
owner()->GetDelegate()->GetFocus()) ==
static_cast<AXPlatformNode*>(owner())) &&
owner()->HasActiveComposition()) {
gfx::Range active_composition_offset =
owner()->GetActiveCompositionOffsets();
AXNodePosition::AXPositionInstance start =
owner()->GetDelegate()->CreateTextPositionAt(
/*offset*/ active_composition_offset.start());
AXNodePosition::AXPositionInstance end =
owner()->GetDelegate()->CreateTextPositionAt(
/*offset*/ active_composition_offset.end());
*range = AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
owner_.Get(), std::move(start), std::move(end));
}
return S_OK;
}
} // namespace ui