diff --git a/src/Terminal.wprp b/src/Terminal.wprp index d37c749c9bd..80a6e730b7a 100644 --- a/src/Terminal.wprp +++ b/src/Terminal.wprp @@ -12,6 +12,7 @@ + @@ -23,6 +24,7 @@ + diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 3ee65dd307d..835108392d6 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -197,6 +197,7 @@ class Microsoft::Terminal::Core::Terminal final : const COORD GetSelectionEnd() const noexcept override; const std::wstring_view GetConsoleTitle() const noexcept override; void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override; + const bool IsUiaDataInitialized() const noexcept override; #pragma endregion void SetWriteInputCallback(std::function pfn) noexcept; diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 99c6c428509..d5e830d7c3b 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -249,3 +249,12 @@ bool Terminal::IsScreenReversed() const noexcept { return _screenReversed; } + +const bool Terminal::IsUiaDataInitialized() const noexcept +{ + // GH#11135: Windows Terminal needs to create and return an automation peer + // when a screen reader requests it. However, the terminal might not be fully + // initialized yet. So we use this to check if any crucial components of + // UiaData are not yet initialized. + return !!_buffer; +} diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 8b858c541e5..b1699531dc4 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -69,5 +69,6 @@ class RenderData final : const COORD GetSelectionAnchor() const noexcept; const COORD GetSelectionEnd() const noexcept; void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr); + const bool IsUiaDataInitialized() const noexcept override { return true; } #pragma endregion }; diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index f180d03e0f3..39f89e2078f 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -392,6 +392,11 @@ class MockRenderData : public IRenderData, IUiaData { } + const bool IsUiaDataInitialized() const noexcept + { + return true; + } + const std::wstring GetHyperlinkUri(uint16_t /*id*/) const noexcept { return {}; diff --git a/src/types/IUiaData.h b/src/types/IUiaData.h index 9cc6b8d9ca6..a1ea9990b7c 100644 --- a/src/types/IUiaData.h +++ b/src/types/IUiaData.h @@ -40,6 +40,7 @@ namespace Microsoft::Console::Types virtual const COORD GetSelectionAnchor() const noexcept = 0; virtual const COORD GetSelectionEnd() const noexcept = 0; virtual void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr) = 0; + virtual const bool IsUiaDataInitialized() const noexcept = 0; }; // See docs/virtual-dtors.md for an explanation of why this is weird. diff --git a/src/types/ScreenInfoUiaProviderBase.cpp b/src/types/ScreenInfoUiaProviderBase.cpp index b5cb4d44263..1d15a837e41 100644 --- a/src/types/ScreenInfoUiaProviderBase.cpp +++ b/src/types/ScreenInfoUiaProviderBase.cpp @@ -220,21 +220,19 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::SetFocus() IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) { + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + _LockConsole(); auto Unlock = wil::scope_exit([&]() noexcept { _UnlockConsole(); }); - - RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); - *ppRetVal = nullptr; - HRESULT hr = S_OK; + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); // make a safe array + HRESULT hr = S_OK; *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1); - if (*ppRetVal == nullptr) - { - return E_OUTOFMEMORY; - } + RETURN_HR_IF_NULL(E_OUTOFMEMORY, *ppRetVal); WRL::ComPtr range; if (!_pData->IsSelectionActive()) @@ -272,19 +270,18 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) { + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + _LockConsole(); auto Unlock = wil::scope_exit([&]() noexcept { _UnlockConsole(); }); - - RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); // make a safe array *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1); - if (*ppRetVal == nullptr) - { - return E_OUTOFMEMORY; - } + RETURN_HR_IF_NULL(E_OUTOFMEMORY, *ppRetVal); WRL::ComPtr range; const auto bufferSize = _pData->GetTextBuffer().GetSize(); diff --git a/src/types/TermControlUiaProvider.cpp b/src/types/TermControlUiaProvider.cpp index c39f7133b12..3ced0756134 100644 --- a/src/types/TermControlUiaProvider.cpp +++ b/src/types/TermControlUiaProvider.cpp @@ -16,9 +16,6 @@ HRESULT TermControlUiaProvider::RuntimeClassInitialize(_In_ ::Microsoft::Console RETURN_IF_FAILED(ScreenInfoUiaProviderBase::RuntimeClassInitialize(uiaData)); _controlInfo = controlInfo; - - // TODO GitHub #1914: Re-attach Tracing to UIA Tree - //Tracing::s_TraceUia(nullptr, ApiCall::Constructor, nullptr); return S_OK; } @@ -26,11 +23,6 @@ IFACEMETHODIMP TermControlUiaProvider::Navigate(_In_ NavigateDirection direction _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) noexcept { RETURN_HR_IF_NULL(E_INVALIDARG, ppProvider); - - // TODO GitHub #1914: Re-attach Tracing to UIA Tree - /*ApiMsgNavigate apiMsg; - apiMsg.Direction = direction; - Tracing::s_TraceUia(this, ApiCall::Navigate, &apiMsg);*/ *ppProvider = nullptr; if (direction == NavigateDirection_Parent) @@ -122,6 +114,12 @@ HRESULT TermControlUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr); *ppUtr = nullptr; + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized() || !_pData->IsSelectionActive()); + const auto start = _pData->GetSelectionAnchor(); // we need to make end exclusive diff --git a/src/types/TermControlUiaTextRange.cpp b/src/types/TermControlUiaTextRange.cpp index ba512888552..5d95dbd88d0 100644 --- a/src/types/TermControlUiaTextRange.cpp +++ b/src/types/TermControlUiaTextRange.cpp @@ -63,20 +63,6 @@ IFACEMETHODIMP TermControlUiaTextRange::Clone(_Outptr_result_maybenull_ ITextRan return hr; } -#if defined(_DEBUG) && defined(UiaTextRangeBase_DEBUG_MSGS) - OutputDebugString(L"Clone\n"); - std::wstringstream ss; - ss << _id << L" cloned to " << (static_cast(*ppRetVal))->_id; - std::wstring str = ss.str(); - OutputDebugString(str.c_str()); - OutputDebugString(L"\n"); -#endif - // TODO GitHub #1914: Re-attach Tracing to UIA Tree - // tracing - /*ApiMsgClone apiMsg; - apiMsg.CloneId = static_cast(*ppRetVal)->GetId(); - Tracing::s_TraceUia(this, ApiCall::Clone, &apiMsg);*/ - return S_OK; } diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 5c73d06e36f..6e1da79db5d 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -220,15 +220,18 @@ IFACEMETHODIMP UiaTextRangeBase::CompareEndpoints(_In_ TextPatternRangeEndpoint _Out_ int* pRetVal) noexcept try { - RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); + RETURN_HR_IF_NULL(E_INVALIDARG, pRetVal); *pRetVal = 0; + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); + // get the text range that we're comparing to const UiaTextRangeBase* range = static_cast(pTargetRange); - if (range == nullptr) - { - return E_INVALIDARG; - } + RETURN_HR_IF_NULL(E_INVALIDARG, range); // get endpoint value that we're comparing to const auto other = range->GetEndpoint(targetEndpoint); @@ -240,10 +243,7 @@ try // This is a temporary solution to comparing two UTRs from different TextBuffers // Ensure both endpoints fit in the current buffer. const auto bufferSize = _pData->GetTextBuffer().GetSize(); - if (!bufferSize.IsInBounds(mine, true) || !bufferSize.IsInBounds(other, true)) - { - return E_FAIL; - } + RETURN_HR_IF(E_FAIL, !bufferSize.IsInBounds(mine, true) || !bufferSize.IsInBounds(other, true)); // compare them *pRetVal = bufferSize.CompareInBounds(mine, other, true); @@ -259,6 +259,7 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) noexc auto Unlock = wil::scope_exit([&]() noexcept { _pData->UnlockConsole(); }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); try { @@ -446,6 +447,12 @@ try RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); *ppRetVal = nullptr; + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); + // AttributeIDs that require special handling switch (attributeId) { @@ -607,6 +614,12 @@ try RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); *ppRetVal = nullptr; + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); + const std::wstring queryText{ text, SysStringLen(text) }; const auto bufferSize = _getBufferSize(); const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; @@ -732,6 +745,12 @@ try RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); VariantInit(pRetVal); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); + // AttributeIDs that require special handling switch (attributeId) { @@ -819,13 +838,14 @@ CATCH_RETURN(); IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) noexcept { + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + _pData->LockConsole(); auto Unlock = wil::scope_exit([&]() noexcept { _pData->UnlockConsole(); }); - - RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); - *ppRetVal = nullptr; + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); try { @@ -927,21 +947,19 @@ CATCH_RETURN(); IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) noexcept try { - RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); + RETURN_HR_IF_NULL(E_INVALIDARG, pRetVal); + RETURN_HR_IF(E_INVALIDARG, maxLength < -1); *pRetVal = nullptr; - if (maxLength < -1) - { - return E_INVALIDARG; - } + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); const auto maxLengthOpt = (maxLength == -1) ? std::nullopt : std::optional{ maxLength }; - _pData->LockConsole(); - auto Unlock = wil::scope_exit([this]() noexcept { - _pData->UnlockConsole(); - }); const auto text = _getTextValue(maxLengthOpt); Unlock.reset(); @@ -1015,6 +1033,7 @@ try auto Unlock = wil::scope_exit([&]() noexcept { _pData->UnlockConsole(); }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); const auto wasDegenerate = IsDegenerate(); if (count != 0) @@ -1070,15 +1089,13 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin { RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = 0; - if (count == 0) - { - return S_OK; - } _pData->LockConsole(); auto Unlock = wil::scope_exit([&]() noexcept { _pData->UnlockConsole(); }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); + RETURN_HR_IF(S_OK, count == 0); try { @@ -1116,10 +1133,8 @@ try }); const UiaTextRangeBase* range = static_cast(pTargetRange); - if (range == nullptr) - { - return E_INVALIDARG; - } + RETURN_HR_IF_NULL(E_INVALIDARG, range); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); // TODO GH#5406: create a different UIA parent object for each TextBuffer // This is a temporary solution to comparing two UTRs from different TextBuffers @@ -1127,10 +1142,7 @@ try const auto bufferSize = _pData->GetTextBuffer().GetSize(); const auto mine = GetEndpoint(endpoint); const auto other = range->GetEndpoint(targetEndpoint); - if (!bufferSize.IsInBounds(mine, true) || !bufferSize.IsInBounds(other, true)) - { - return E_FAIL; - } + RETURN_HR_IF(E_FAIL, !bufferSize.IsInBounds(mine, true) || !bufferSize.IsInBounds(other, true)); SetEndpoint(endpoint, range->GetEndpoint(targetEndpoint)); @@ -1146,6 +1158,7 @@ try auto Unlock = wil::scope_exit([&]() noexcept { _pData->UnlockConsole(); }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); if (IsDegenerate()) { @@ -1190,6 +1203,7 @@ try auto Unlock = wil::scope_exit([&]() noexcept { _pData->UnlockConsole(); }); + RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); const auto oldViewport = _pData->GetViewport().ToInclusive(); const auto viewportHeight = _getViewportHeight(oldViewport);