Fix UIA ScrollIntoView at EndExclusive (#7868)

`ScrollIntoView` is responsible for scrolling the viewport to include
the UTR's start endpoint. The crash was caused by `start` being at the
exclusive end, and attempting to scroll to it. This is now fixed by
clamping the result to the bottom of the buffer.

Most of the work here is to allow a test for this. `ScrollIntoView`
relied on a virtual `ChangeViewport` function. By making that
non-virtual, the `DummyElementProvider` in the tests can now be a
`ScreenInfoUiaProviderBase`. This opens up the possibility of more
UiaTextRange tests in the future too.

Closes #7839
This commit is contained in:
Carlos Zamora 2020-10-09 13:27:13 -07:00 committed by GitHub
parent d33ca7e8eb
commit 7a1932c556
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 97 additions and 45 deletions

View file

@ -77,12 +77,6 @@ IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider*
return S_OK;
}
void UiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow)
{
auto provider = static_cast<ScreenInfoUiaProvider*>(_pProvider);
provider->ChangeViewport(NewWindow);
}
void UiaTextRange::_TranslatePointToScreen(LPPOINT clientPoint) const
{
ClientToScreen(_getWindowHandle(), clientPoint);

View file

@ -56,7 +56,6 @@ namespace Microsoft::Console::Interactivity::Win32
IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override;
protected:
void _ChangeViewport(const SMALL_RECT NewWindow) override;
void _TranslatePointToScreen(LPPOINT clientPoint) const override;
void _TranslatePointFromScreen(LPPOINT screenPoint) const override;

View file

@ -7,6 +7,7 @@
#include "CommonState.hpp"
#include "uiaTextRange.hpp"
#include "../types/ScreenInfoUiaProviderBase.h"
#include "../../../buffer/out/textBuffer.hpp"
using namespace WEX::Common;
@ -24,46 +25,65 @@ using namespace Microsoft::Console::Interactivity::Win32;
// it not doing anything for its implementation because it is not used
// during the unit tests below.
class DummyElementProvider final : public IRawElementProviderSimple
class DummyElementProvider final : public ScreenInfoUiaProviderBase
{
public:
// IUnknown methods
IFACEMETHODIMP_(ULONG)
AddRef()
{
return 1;
}
IFACEMETHODIMP_(ULONG)
Release()
{
return 1;
}
IFACEMETHODIMP QueryInterface(_In_ REFIID /*riid*/,
_COM_Outptr_result_maybenull_ void** /*ppInterface*/)
{
return E_NOTIMPL;
};
// IRawElementProviderSimple methods
IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* /*pOptions*/)
IFACEMETHODIMP Navigate(_In_ NavigateDirection /*direction*/,
_COM_Outptr_result_maybenull_ IRawElementProviderFragment** /*ppProvider*/) override
{
return E_NOTIMPL;
}
IFACEMETHODIMP GetPatternProvider(_In_ PATTERNID /*iid*/,
_COM_Outptr_result_maybenull_ IUnknown** /*ppInterface*/)
IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* /*pRect*/) override
{
return E_NOTIMPL;
}
IFACEMETHODIMP GetPropertyValue(_In_ PROPERTYID /*idProp*/,
_Out_ VARIANT* /*pVariant*/)
IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** /*ppProvider*/) override
{
return E_NOTIMPL;
}
IFACEMETHODIMP get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** /*ppProvider*/)
void ChangeViewport(const SMALL_RECT /*NewWindow*/)
{
return;
}
HRESULT GetSelectionRange(_In_ IRawElementProviderSimple* /*pProvider*/, const std::wstring_view /*wordDelimiters*/, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
{
return E_NOTIMPL;
}
// degenerate range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/, const std::wstring_view /*wordDelimiters*/, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
{
return E_NOTIMPL;
}
// degenerate range at cursor position
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/,
const Cursor& /*cursor*/,
const std::wstring_view /*wordDelimiters*/,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
{
return E_NOTIMPL;
}
// specific endpoint range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/,
const COORD /*start*/,
const COORD /*end*/,
const std::wstring_view /*wordDelimiters*/,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
{
return E_NOTIMPL;
}
// range from a UiaPoint
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/,
const UiaPoint /*point*/,
const std::wstring_view /*wordDelimiters*/,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
{
return E_NOTIMPL;
}
@ -106,6 +126,12 @@ class UiaTextRangeTests
ExpectedResult expected;
};
struct ScrollTest
{
std::wstring comment;
short yPos;
};
static constexpr wchar_t* toString(TextUnit unit) noexcept
{
// if a format is not supported, it goes to the next largest text unit
@ -1284,4 +1310,40 @@ class UiaTextRangeTests
THROW_IF_FAILED(utr->GetText(-1, &text));
VERIFY_ARE_EQUAL(L"M", std::wstring_view{ text });
}
TEST_METHOD(ScrollIntoView)
{
const auto bufferSize{ _pTextBuffer->GetSize() };
const auto viewportSize{ _pUiaData->GetViewport() };
const std::vector<ScrollTest> testData{
{ L"Origin", bufferSize.Top() },
{ L"ViewportHeight From Top - 1", bufferSize.Top() + viewportSize.Height() - 1 },
{ L"ViewportHeight From Top", bufferSize.Top() + viewportSize.Height() },
{ L"ViewportHeight From Top + 1", bufferSize.Top() + viewportSize.Height() + 1 },
{ L"ViewportHeight From Bottom - 1", bufferSize.BottomInclusive() - viewportSize.Height() - 1 },
{ L"ViewportHeight From Bottom", bufferSize.BottomInclusive() - viewportSize.Height() },
{ L"ViewportHeight From Bottom + 1", bufferSize.BottomInclusive() - viewportSize.Height() + 1 },
// GH#7839: ExclusiveEnd is a non-existent space,
// so scrolling to it when !alignToTop used to crash
{ L"Exclusive End", bufferSize.BottomExclusive() }
};
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:alignToTop", L"{false, true}")
END_TEST_METHOD_PROPERTIES();
bool alignToTop;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"alignToTop", alignToTop), L"Get alignToTop variant");
Microsoft::WRL::ComPtr<UiaTextRange> utr;
for (const auto test : testData)
{
Log::Comment(test.comment.c_str());
const til::point pos{ bufferSize.Left(), test.yPos };
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, pos, pos));
VERIFY_SUCCEEDED(utr->ScrollIntoView(alignToTop));
}
}
};

View file

@ -80,12 +80,6 @@ IFACEMETHODIMP TermControlUiaTextRange::Clone(_Outptr_result_maybenull_ ITextRan
return S_OK;
}
void TermControlUiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow)
{
const gsl::not_null<TermControlUiaProvider*> provider = static_cast<TermControlUiaProvider*>(_pProvider);
provider->ChangeViewport(NewWindow);
}
// Method Description:
// - Transform coordinates relative to the client to relative to the screen
// Arguments:

View file

@ -55,7 +55,6 @@ namespace Microsoft::Terminal
IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override;
protected:
void _ChangeViewport(const SMALL_RECT NewWindow) override;
void _TranslatePointToScreen(LPPOINT clientPoint) const override;
void _TranslatePointFromScreen(LPPOINT screenPoint) const override;
const COORD _getScreenFontSize() const override;

View file

@ -784,7 +784,7 @@ try
}
else
{
// we can align to the top so we'll just move the viewport
// we can't align to the top so we'll just move the viewport
// to the bottom of the screen buffer
newViewport.Bottom = bottomRow;
newViewport.Top = bottomRow - viewportHeight + 1;
@ -796,9 +796,13 @@ try
// check if we can align to the bottom
if (static_cast<unsigned int>(endScreenInfoRow) >= viewportHeight)
{
// GH#7839: endScreenInfoRow may be ExclusiveEnd
// ExclusiveEnd is past the bottomRow
// so we need to clamp to the bottom row to stay in bounds
// we can align to bottom
newViewport.Bottom = endScreenInfoRow;
newViewport.Top = endScreenInfoRow - viewportHeight + 1;
newViewport.Bottom = std::min(endScreenInfoRow, bottomRow);
newViewport.Top = base::ClampedNumeric<short>(newViewport.Bottom) - viewportHeight + 1;
}
else
{
@ -815,7 +819,8 @@ try
Unlock.reset();
_ChangeViewport(newViewport);
const gsl::not_null<ScreenInfoUiaProviderBase*> provider = static_cast<ScreenInfoUiaProviderBase*>(_pProvider);
provider->ChangeViewport(newViewport);
UiaTracing::TextRange::ScrollIntoView(alignToTop, *this);
return S_OK;

View file

@ -127,7 +127,6 @@ namespace Microsoft::Console::Types
std::wstring _wordDelimiters{};
virtual void _ChangeViewport(const SMALL_RECT NewWindow) = 0;
virtual void _TranslatePointToScreen(LPPOINT clientPoint) const = 0;
virtual void _TranslatePointFromScreen(LPPOINT screenPoint) const = 0;