02ac246807
## Summary of the Pull Request As a part of the Interactivity split, `TermControlAutomationPeer` had to be split into `TermControlAutomationPeer` (TCAP) and `InteractivityAutomationPeer` (IAP). Just about all of the functions in `InterativityAutomationPeer` operate by calling the non-XAML UIA Provider then wrapping the resulting `UIATextRange` into a XAML format (a `XamlUiaTextRange` [XUTR]). As a part of that XUTR constructor, we need a reference to the parent provider. We generally get that via `ProviderFromPeer()`, but IAP's `ProviderFromPeer()` returned null (presumably because IAP isn't in the UI tree, whereas TCAP is directly registered as the automation peer for the `TermControl`). It looks like some screen readers didn't care (like NVDA, though there may be a chance we just didn't encounter an issue just yet), but Narrator definitely did. The fix was to provide XUTR constructors the `ProviderFromPeer` from TCAP, _not_ IAP. To accomplish this, IAP now holds a weak reference to TCAP, and provides the `ProviderFromPeer` when needed. We can't cache this result because there is no guarantee that it won't change. Some miscellaneous changes include: - `TermControl::OnCreateAutomationPeer` now returns the existing auto peer instead of always creating a new one - `TCAP::WrapArrayOfTextRangeProviders` was removed as it was unused (normally, this would be directly affected by the main `ProviderFromPeer` change here) - `XUTR::GetEnclosingElement` is now hooked up to trace logging for debugging purposes ## References Introduced in #10051 Closes #11488 ## Validation Steps Performed ✅ Narrator scan mode now works (verified with character, word, and line navigation) ✅ NVDA movement still works (verified with word and line navigation)
257 lines
10 KiB
C++
257 lines
10 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "XamlUiaTextRange.h"
|
|
#include "../types/TermControlUiaTextRange.hpp"
|
|
#include <UIAutomationClient.h>
|
|
#include <UIAutomationCoreApi.h>
|
|
#include "../types/UiaTracing.h"
|
|
|
|
// the same as COR_E_NOTSUPPORTED
|
|
// we don't want to import the CLR headers to get it
|
|
#define XAML_E_NOT_SUPPORTED 0x80131515L
|
|
|
|
namespace UIA
|
|
{
|
|
using ::ITextRangeProvider;
|
|
using ::SupportedTextSelection;
|
|
using ::TextPatternRangeEndpoint;
|
|
using ::TextUnit;
|
|
}
|
|
|
|
namespace XamlAutomation
|
|
{
|
|
using winrt::Windows::UI::Xaml::Automation::SupportedTextSelection;
|
|
using winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple;
|
|
using winrt::Windows::UI::Xaml::Automation::Provider::ITextRangeProvider;
|
|
using winrt::Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint;
|
|
using winrt::Windows::UI::Xaml::Automation::Text::TextUnit;
|
|
}
|
|
|
|
namespace winrt::Microsoft::Terminal::Control::implementation
|
|
{
|
|
XamlAutomation::ITextRangeProvider XamlUiaTextRange::Clone() const
|
|
{
|
|
UIA::ITextRangeProvider* pReturn;
|
|
THROW_IF_FAILED(_uiaProvider->Clone(&pReturn));
|
|
auto xutr = winrt::make_self<XamlUiaTextRange>(pReturn, _parentProvider);
|
|
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
|
}
|
|
|
|
bool XamlUiaTextRange::Compare(XamlAutomation::ITextRangeProvider pRange) const
|
|
{
|
|
auto self = winrt::get_self<XamlUiaTextRange>(pRange);
|
|
|
|
BOOL returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->Compare(self->_uiaProvider.get(), &returnVal));
|
|
return returnVal;
|
|
}
|
|
|
|
int32_t XamlUiaTextRange::CompareEndpoints(XamlAutomation::TextPatternRangeEndpoint endpoint,
|
|
XamlAutomation::ITextRangeProvider pTargetRange,
|
|
XamlAutomation::TextPatternRangeEndpoint targetEndpoint)
|
|
{
|
|
auto self = winrt::get_self<XamlUiaTextRange>(pTargetRange);
|
|
|
|
int32_t returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->CompareEndpoints(static_cast<UIA::TextPatternRangeEndpoint>(endpoint),
|
|
self->_uiaProvider.get(),
|
|
static_cast<UIA::TextPatternRangeEndpoint>(targetEndpoint),
|
|
&returnVal));
|
|
return returnVal;
|
|
}
|
|
|
|
void XamlUiaTextRange::ExpandToEnclosingUnit(XamlAutomation::TextUnit unit) const
|
|
{
|
|
THROW_IF_FAILED(_uiaProvider->ExpandToEnclosingUnit(static_cast<UIA::TextUnit>(unit)));
|
|
}
|
|
|
|
XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindAttribute(int32_t /*textAttributeId*/,
|
|
winrt::Windows::Foundation::IInspectable /*val*/,
|
|
bool /*searchBackward*/)
|
|
{
|
|
// TODO GitHub #2161: potential accessibility improvement
|
|
// we don't support this currently
|
|
throw winrt::hresult_not_implemented();
|
|
}
|
|
|
|
XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindText(winrt::hstring text,
|
|
bool searchBackward,
|
|
bool ignoreCase)
|
|
{
|
|
UIA::ITextRangeProvider* pReturn;
|
|
const auto queryText = wil::make_bstr(text.c_str());
|
|
|
|
THROW_IF_FAILED(_uiaProvider->FindText(queryText.get(), searchBackward, ignoreCase, &pReturn));
|
|
|
|
auto xutr = winrt::make_self<XamlUiaTextRange>(pReturn, _parentProvider);
|
|
return *xutr;
|
|
}
|
|
|
|
winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const
|
|
{
|
|
// Call the function off of the underlying UiaTextRange.
|
|
VARIANT result;
|
|
THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, &result));
|
|
|
|
// Convert the resulting VARIANT into a format that is consumable by XAML.
|
|
switch (result.vt)
|
|
{
|
|
case VT_BSTR:
|
|
{
|
|
return box_value(result.bstrVal);
|
|
}
|
|
case VT_I4:
|
|
{
|
|
// Surprisingly, `long` is _not_ a WinRT type.
|
|
// So we have to use `int32_t` to make sure this is output properly.
|
|
// Otherwise, you'll get "Attribute does not exist" out the other end.
|
|
return box_value<int32_t>(result.lVal);
|
|
}
|
|
case VT_R8:
|
|
{
|
|
return box_value(result.dblVal);
|
|
}
|
|
case VT_BOOL:
|
|
{
|
|
return box_value<bool>(result.boolVal);
|
|
}
|
|
case VT_UNKNOWN:
|
|
{
|
|
// This one is particularly special.
|
|
// We might return a special value like UiaGetReservedMixedAttributeValue
|
|
// or UiaGetReservedNotSupportedValue.
|
|
// Some text attributes may return a real value, however, none of those
|
|
// are supported at this time.
|
|
// So we need to figure out what was actually intended to be returned.
|
|
|
|
com_ptr<IUnknown> mixedAttributeVal;
|
|
UiaGetReservedMixedAttributeValue(mixedAttributeVal.put());
|
|
|
|
if (result.punkVal == mixedAttributeVal.get())
|
|
{
|
|
return Windows::UI::Xaml::DependencyProperty::UnsetValue();
|
|
}
|
|
|
|
[[fallthrough]];
|
|
}
|
|
default:
|
|
{
|
|
// We _need_ to return XAML_E_NOT_SUPPORTED here.
|
|
// Returning nullptr is an improper implementation of it being unsupported.
|
|
// UIA Clients rely on this HRESULT to signify that the requested attribute is undefined.
|
|
// Anything else will result in the UIA Client refusing to read when navigating by word
|
|
// Magically, this doesn't affect other forms of navigation...
|
|
winrt::throw_hresult(XAML_E_NOT_SUPPORTED);
|
|
}
|
|
}
|
|
}
|
|
|
|
void XamlUiaTextRange::GetBoundingRectangles(com_array<double>& returnValue) const
|
|
{
|
|
returnValue = {};
|
|
try
|
|
{
|
|
SAFEARRAY* pReturnVal;
|
|
THROW_IF_FAILED(_uiaProvider->GetBoundingRectangles(&pReturnVal));
|
|
|
|
double* pVals;
|
|
THROW_IF_FAILED(SafeArrayAccessData(pReturnVal, (void**)&pVals));
|
|
|
|
long lBound, uBound;
|
|
THROW_IF_FAILED(SafeArrayGetLBound(pReturnVal, 1, &lBound));
|
|
THROW_IF_FAILED(SafeArrayGetUBound(pReturnVal, 1, &uBound));
|
|
|
|
long count = uBound - lBound + 1;
|
|
|
|
std::vector<double> vec;
|
|
vec.reserve(count);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
double element = pVals[i];
|
|
vec.push_back(element);
|
|
}
|
|
|
|
winrt::com_array<double> result{ vec };
|
|
returnValue = std::move(result);
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
}
|
|
|
|
XamlAutomation::IRawElementProviderSimple XamlUiaTextRange::GetEnclosingElement()
|
|
{
|
|
::Microsoft::Console::Types::UiaTracing::TextRange::GetEnclosingElement(*static_cast<::Microsoft::Console::Types::UiaTextRangeBase*>(_uiaProvider.get()));
|
|
return _parentProvider;
|
|
}
|
|
|
|
winrt::hstring XamlUiaTextRange::GetText(int32_t maxLength) const
|
|
{
|
|
BSTR returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->GetText(maxLength, &returnVal));
|
|
return winrt::to_hstring(returnVal);
|
|
}
|
|
|
|
int32_t XamlUiaTextRange::Move(XamlAutomation::TextUnit unit,
|
|
int32_t count)
|
|
{
|
|
int returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->Move(static_cast<UIA::TextUnit>(unit),
|
|
count,
|
|
&returnVal));
|
|
return returnVal;
|
|
}
|
|
|
|
int32_t XamlUiaTextRange::MoveEndpointByUnit(XamlAutomation::TextPatternRangeEndpoint endpoint,
|
|
XamlAutomation::TextUnit unit,
|
|
int32_t count) const
|
|
{
|
|
int returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->MoveEndpointByUnit(static_cast<UIA::TextPatternRangeEndpoint>(endpoint),
|
|
static_cast<UIA::TextUnit>(unit),
|
|
count,
|
|
&returnVal));
|
|
return returnVal;
|
|
}
|
|
|
|
void XamlUiaTextRange::MoveEndpointByRange(XamlAutomation::TextPatternRangeEndpoint endpoint,
|
|
XamlAutomation::ITextRangeProvider pTargetRange,
|
|
XamlAutomation::TextPatternRangeEndpoint targetEndpoint) const
|
|
{
|
|
auto self = winrt::get_self<XamlUiaTextRange>(pTargetRange);
|
|
THROW_IF_FAILED(_uiaProvider->MoveEndpointByRange(static_cast<UIA::TextPatternRangeEndpoint>(endpoint),
|
|
/*pTargetRange*/ self->_uiaProvider.get(),
|
|
static_cast<UIA::TextPatternRangeEndpoint>(targetEndpoint)));
|
|
}
|
|
|
|
void XamlUiaTextRange::Select() const
|
|
{
|
|
THROW_IF_FAILED(_uiaProvider->Select());
|
|
}
|
|
|
|
void XamlUiaTextRange::AddToSelection() const
|
|
{
|
|
// we don't support this
|
|
throw winrt::hresult_not_implemented();
|
|
}
|
|
|
|
void XamlUiaTextRange::RemoveFromSelection() const
|
|
{
|
|
// we don't support this
|
|
throw winrt::hresult_not_implemented();
|
|
}
|
|
|
|
void XamlUiaTextRange::ScrollIntoView(bool alignToTop) const
|
|
{
|
|
THROW_IF_FAILED(_uiaProvider->ScrollIntoView(alignToTop));
|
|
}
|
|
|
|
winrt::com_array<XamlAutomation::IRawElementProviderSimple> XamlUiaTextRange::GetChildren() const
|
|
{
|
|
// we don't have any children
|
|
return {};
|
|
}
|
|
}
|