For now, the core SELECTABLE_TEXT state is only exposed on text containers, not on text leaves. We may want to reconsider this at some point. For now, just handle this case in the UIA code, since that's the only place it matters. Differential Revision: https://phabricator.services.mozilla.com/D239855
182 lines
5.5 KiB
C++
182 lines
5.5 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "UiaText.h"
|
|
|
|
#include "MsaaAccessible.h"
|
|
#include "mozilla/a11y/States.h"
|
|
#include "TextLeafRange.h"
|
|
#include "UiaTextRange.h"
|
|
|
|
namespace mozilla::a11y {
|
|
|
|
// Helpers
|
|
|
|
static SAFEARRAY* TextLeafRangesToUiaRanges(
|
|
const nsTArray<TextLeafRange>& aRanges) {
|
|
// The documentation for GetSelection doesn't specify whether we should return
|
|
// an empty array or null if there are no ranges to return. However,
|
|
// GetVisibleRanges says that we should return an empty array, never null, so
|
|
// that's what we do.
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-itextprovider-getvisibleranges
|
|
SAFEARRAY* uiaRanges = SafeArrayCreateVector(VT_UNKNOWN, 0, aRanges.Length());
|
|
LONG indices[1] = {0};
|
|
for (const TextLeafRange& range : aRanges) {
|
|
// SafeArrayPutElement calls AddRef on the element, so we use a raw
|
|
// pointer here.
|
|
UiaTextRange* uiaRange = new UiaTextRange(range);
|
|
SafeArrayPutElement(uiaRanges, indices, uiaRange);
|
|
++indices[0];
|
|
}
|
|
return uiaRanges;
|
|
}
|
|
|
|
// IUnknown
|
|
IMPL_IUNKNOWN1(UiaText, ITextProvider)
|
|
|
|
// UiaText
|
|
|
|
UiaText::UiaText(MsaaAccessible* aMsaa) : mMsaa(aMsaa) {}
|
|
|
|
Accessible* UiaText::Acc() const { return mMsaa->Acc(); }
|
|
|
|
// ITextProvider methods
|
|
|
|
STDMETHODIMP
|
|
UiaText::GetSelection(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
|
|
if (!aRetVal) {
|
|
return E_INVALIDARG;
|
|
}
|
|
Accessible* acc = Acc();
|
|
if (!acc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
AutoTArray<TextLeafRange, 1> ranges;
|
|
TextLeafRange::GetSelection(acc, ranges);
|
|
if (ranges.IsEmpty()) {
|
|
// There is no selection. Check if there is a caret.
|
|
if (TextLeafPoint caret = TextLeafPoint::GetCaret(acc)) {
|
|
ranges.EmplaceBack(caret, caret);
|
|
}
|
|
}
|
|
*aRetVal = TextLeafRangesToUiaRanges(ranges);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
UiaText::GetVisibleRanges(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
|
|
if (!aRetVal) {
|
|
return E_INVALIDARG;
|
|
}
|
|
Accessible* acc = Acc();
|
|
if (!acc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
TextLeafRange fullRange = TextLeafRange::FromAccessible(acc);
|
|
// The most pragmatic way to determine visible text is to walk by line.
|
|
// XXX TextLeafRange::VisibleLines doesn't correctly handle lines that are
|
|
// scrolled out where the scroll container is a descendant of acc. See bug
|
|
// 1945010.
|
|
nsTArray<TextLeafRange> ranges = fullRange.VisibleLines(acc);
|
|
*aRetVal = TextLeafRangesToUiaRanges(ranges);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
UiaText::RangeFromChild(__RPC__in_opt IRawElementProviderSimple* aChildElement,
|
|
__RPC__deref_out_opt ITextRangeProvider** aRetVal) {
|
|
if (!aChildElement || !aRetVal) {
|
|
return E_INVALIDARG;
|
|
}
|
|
*aRetVal = nullptr;
|
|
Accessible* acc = Acc();
|
|
if (!acc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
Accessible* child = MsaaAccessible::GetAccessibleFrom(aChildElement);
|
|
if (!child || !acc->IsAncestorOf(child)) {
|
|
return E_INVALIDARG;
|
|
}
|
|
TextLeafRange range = TextLeafRange::FromAccessible(child);
|
|
RefPtr uiaRange = new UiaTextRange(range);
|
|
uiaRange.forget(aRetVal);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
UiaText::RangeFromPoint(struct UiaPoint aPoint,
|
|
__RPC__deref_out_opt ITextRangeProvider** aRetVal) {
|
|
if (!aRetVal) {
|
|
return E_INVALIDARG;
|
|
}
|
|
*aRetVal = nullptr;
|
|
Accessible* acc = Acc();
|
|
if (!acc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
|
|
// Find the deepest accessible node at the given screen coordinates.
|
|
Accessible* child = acc->ChildAtPoint(
|
|
aPoint.x, aPoint.y, Accessible::EWhichChildAtPoint::DeepestChild);
|
|
if (!child) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Find the closest point within the entirety of the leaf where the screen
|
|
// coordinates lie.
|
|
TextLeafRange leafRange = TextLeafRange::FromAccessible(child);
|
|
TextLeafPoint closestPoint =
|
|
leafRange.TextLeafPointAtScreenPoint(aPoint.x, aPoint.y);
|
|
TextLeafRange range{closestPoint, closestPoint};
|
|
RefPtr uiaRange = new UiaTextRange(range);
|
|
uiaRange.forget(aRetVal);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
UiaText::get_DocumentRange(__RPC__deref_out_opt ITextRangeProvider** aRetVal) {
|
|
if (!aRetVal) {
|
|
return E_INVALIDARG;
|
|
}
|
|
Accessible* acc = Acc();
|
|
if (!acc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
// On the web, the "document range" could either span the entire document or
|
|
// just a text input control, depending on the element on which the Text
|
|
// pattern was queried. See:
|
|
// https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-textpattern-and-embedded-objects-overview#webpage-and-text-input-controls-in-edge
|
|
TextLeafRange range = TextLeafRange::FromAccessible(acc);
|
|
RefPtr uiaRange = new UiaTextRange(range);
|
|
uiaRange.forget(aRetVal);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
UiaText::get_SupportedTextSelection(
|
|
__RPC__out enum SupportedTextSelection* aRetVal) {
|
|
if (!aRetVal) {
|
|
return E_INVALIDARG;
|
|
}
|
|
Accessible* acc = Acc();
|
|
if (!acc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
if (!acc->IsHyperText()) {
|
|
// Currently, the SELECTABLE_TEXT state is only exposed on HyperText
|
|
// Accessibles.
|
|
acc = acc->Parent();
|
|
}
|
|
if (acc && acc->State() & states::SELECTABLE_TEXT) {
|
|
*aRetVal = SupportedTextSelection_Multiple;
|
|
} else {
|
|
*aRetVal = SupportedTextSelection_None;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
} // namespace mozilla::a11y
|