mirror of
https://github.com/microsoft/terminal.git
synced 2026-10-03 12:00:42 +00:00
## Summary of the Pull Request This includes the memory leak fixes that @lhecker and I investigated as a part of #19710. The `ITextRangeProvider`s (namely `UiaTextRange`s) weren't being destroyed after they were done being used by the screen reader. ## Validation Steps Performed In my own testing, I set a breakpoint on the destructor for `UiaTextRangeBase`. Prior to this change, that destructor would mainly be called when the terminal control was closed, which would result in us leaking these objects. With this change, I've confirmed that these text ranges are being destroyed immediately after they are done being used (without needing to close the terminal control). ## PR Checklist Closes #19710
224 lines
8.5 KiB
C++
224 lines
8.5 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include <UIAutomationCore.h>
|
|
#include "InteractivityAutomationPeer.h"
|
|
#include "InteractivityAutomationPeer.g.cpp"
|
|
|
|
#include "XamlUiaTextRange.h"
|
|
#include "../types/UiaTracing.h"
|
|
|
|
using namespace Microsoft::Console::Types;
|
|
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
|
using namespace winrt::Windows::Graphics::Display;
|
|
|
|
namespace UIA
|
|
{
|
|
using ::ITextRangeProvider;
|
|
using ::SupportedTextSelection;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
namespace winrt::Microsoft::Terminal::Control::implementation
|
|
{
|
|
InteractivityAutomationPeer::InteractivityAutomationPeer(Control::implementation::ControlInteractivity* owner) :
|
|
_interactivity{ owner }
|
|
{
|
|
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, _interactivity->GetRenderData(), this));
|
|
};
|
|
|
|
// Bounds is expected to be in pixels.
|
|
void InteractivityAutomationPeer::SetControlBounds(const Windows::Foundation::Rect bounds)
|
|
{
|
|
_controlBounds = { til::math::rounding, bounds };
|
|
}
|
|
|
|
// Padding is expected to be in DIPs.
|
|
void InteractivityAutomationPeer::SetControlPadding(const Core::Padding padding)
|
|
{
|
|
const auto scale = static_cast<float>(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel());
|
|
_controlPadding = { til::math::rounding, padding.Left * scale, padding.Top * scale, padding.Right * scale, padding.Bottom * scale };
|
|
}
|
|
|
|
void InteractivityAutomationPeer::ParentProvider(AutomationPeer parentProvider)
|
|
{
|
|
// LOAD-BEARING: use _parentProvider->ProviderFromPeer(_parentProvider) instead of this->ProviderFromPeer(*this).
|
|
// Since we split the automation peer into TermControlAutomationPeer and InteractivityAutomationPeer,
|
|
// using "this" returns null. This can cause issues with some UIA Client scenarios like any navigation in Narrator.
|
|
_parentProvider = parentProvider ? parentProvider.as<IAutomationPeerProtected>().ProviderFromPeer(parentProvider) : nullptr;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Signals the ui automation client that the terminal's selection has
|
|
// changed and should be updated
|
|
// - We will raise a new event, for out embedding control to be able to
|
|
// raise the event. AutomationPeer by itself doesn't hook up to the
|
|
// eventing mechanism, we need the FrameworkAutomationPeer to do that.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void InteractivityAutomationPeer::SignalSelectionChanged()
|
|
{
|
|
SelectionChanged.raise(*this, nullptr);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Signals the ui automation client that the terminal's output has changed
|
|
// and should be updated
|
|
// - We will raise a new event, for out embedding control to be able to
|
|
// raise the event. AutomationPeer by itself doesn't hook up to the
|
|
// eventing mechanism, we need the FrameworkAutomationPeer to do that.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void InteractivityAutomationPeer::SignalTextChanged()
|
|
{
|
|
TextChanged.raise(*this, nullptr);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Signals the ui automation client that the cursor's state has changed
|
|
// and should be updated
|
|
// - We will raise a new event, for out embedding control to be able to
|
|
// raise the event. AutomationPeer by itself doesn't hook up to the
|
|
// eventing mechanism, we need the FrameworkAutomationPeer to do that.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void InteractivityAutomationPeer::SignalCursorChanged()
|
|
{
|
|
CursorChanged.raise(*this, nullptr);
|
|
}
|
|
|
|
void InteractivityAutomationPeer::NotifyNewOutput(std::wstring_view newOutput)
|
|
{
|
|
NewOutput.raise(*this, hstring{ newOutput });
|
|
}
|
|
|
|
#pragma region ITextProvider
|
|
com_array<XamlAutomation::ITextRangeProvider> InteractivityAutomationPeer::GetSelection()
|
|
{
|
|
SAFEARRAY* pReturnVal;
|
|
THROW_IF_FAILED(_uiaProvider->GetSelection(&pReturnVal));
|
|
return WrapArrayOfTextRangeProviders(pReturnVal);
|
|
}
|
|
|
|
com_array<XamlAutomation::ITextRangeProvider> InteractivityAutomationPeer::GetVisibleRanges()
|
|
{
|
|
SAFEARRAY* pReturnVal;
|
|
THROW_IF_FAILED(_uiaProvider->GetVisibleRanges(&pReturnVal));
|
|
return WrapArrayOfTextRangeProviders(pReturnVal);
|
|
}
|
|
|
|
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::RangeFromChild(XamlAutomation::IRawElementProviderSimple /*childElement*/)
|
|
{
|
|
UIA::ITextRangeProvider* returnVal;
|
|
// ScreenInfoUiaProvider doesn't actually use parameter, so just pass in nullptr
|
|
THROW_IF_FAILED(_uiaProvider->RangeFromChild(/* IRawElementProviderSimple */ nullptr,
|
|
&returnVal));
|
|
return _CreateXamlUiaTextRange(returnVal);
|
|
}
|
|
|
|
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::RangeFromPoint(Windows::Foundation::Point screenLocation)
|
|
{
|
|
UIA::ITextRangeProvider* returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->RangeFromPoint({ screenLocation.X, screenLocation.Y }, &returnVal));
|
|
return _CreateXamlUiaTextRange(returnVal);
|
|
}
|
|
|
|
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::DocumentRange()
|
|
{
|
|
UIA::ITextRangeProvider* returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->get_DocumentRange(&returnVal));
|
|
return _CreateXamlUiaTextRange(returnVal);
|
|
}
|
|
|
|
XamlAutomation::SupportedTextSelection InteractivityAutomationPeer::SupportedTextSelection()
|
|
{
|
|
UIA::SupportedTextSelection returnVal;
|
|
THROW_IF_FAILED(_uiaProvider->get_SupportedTextSelection(&returnVal));
|
|
return static_cast<XamlAutomation::SupportedTextSelection>(returnVal);
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region IControlAccessibilityInfo
|
|
til::size InteractivityAutomationPeer::GetFontSize() const noexcept
|
|
{
|
|
return { til::math::rounding, _interactivity->Core().FontSize() };
|
|
}
|
|
|
|
til::rect InteractivityAutomationPeer::GetBounds() const noexcept
|
|
{
|
|
return _controlBounds;
|
|
}
|
|
|
|
HRESULT InteractivityAutomationPeer::GetHostUiaProvider(IRawElementProviderSimple** provider)
|
|
{
|
|
RETURN_HR_IF(E_INVALIDARG, provider == nullptr);
|
|
*provider = nullptr;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
til::rect InteractivityAutomationPeer::GetPadding() const noexcept
|
|
{
|
|
return _controlPadding;
|
|
}
|
|
|
|
void InteractivityAutomationPeer::ChangeViewport(const til::inclusive_rect& NewWindow)
|
|
{
|
|
_interactivity->UpdateScrollbar(static_cast<float>(NewWindow.top));
|
|
}
|
|
#pragma endregion
|
|
|
|
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::_CreateXamlUiaTextRange(UIA::ITextRangeProvider* returnVal) const
|
|
{
|
|
if (!_parentProvider)
|
|
{
|
|
return nullptr;
|
|
}
|
|
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, _parentProvider);
|
|
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
|
};
|
|
|
|
// Method Description:
|
|
// - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders
|
|
// Arguments:
|
|
// - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders)
|
|
// Return Value:
|
|
// - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders)
|
|
com_array<XamlAutomation::ITextRangeProvider> InteractivityAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
|
|
{
|
|
if (!_parentProvider)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// transfer ownership of UiaTextRanges to this new vector
|
|
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::TermControlUiaTextRange>(textRanges);
|
|
const auto len = gsl::narrow<uint32_t>(providers.size());
|
|
com_array<XamlAutomation::ITextRangeProvider> result{ len };
|
|
|
|
for (uint32_t i = 0; i < len; ++i)
|
|
{
|
|
if (auto xutr = _CreateXamlUiaTextRange(providers[i].detach()))
|
|
{
|
|
result[i] = std::move(xutr);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|