Skip to content

Commit 638c6d0

Browse files
Ensure automation peer is created regardless of terminal initialization (#10971)
## Summary of the Pull Request The bug was that Narrator would still read the content of the old tab/pane although a new tab/pane was introduced. This is caused by the automation peer not being created when XAML requests it. Normally, we would prevent the automation peer from being created if the terminal was not fully initialized. This change allows the automation peer to be created regardless of the terminal being fully initialized by... - `TermControl`: `_InitializeTerminal` updates the padding (dependent on the `SwapChainPanel`) upon full initialization - `ControlCore`: initialize the `_renderer` in the ctor so that we can attach the UIA Engine before `ControlCore::Initialize()` is called (dependent on `SwapChainPanel` loading) As a bonus, this also fixes a locking issue where logging would attempt to get the text range's text and lock twice. The locking fix is very similar to #10937. ## PR Checklist Closes [MSFT 33353327](https://microsoft.visualstudio.com/OS/_workitems/edit/33353327) ## Validation Steps Performed - New pane from key binding is announced by Narrator - New tab from key binding is announced by Narrator
1 parent 68294f8 commit 638c6d0

4 files changed

Lines changed: 57 additions & 40 deletions

File tree

src/cascadia/TerminalControl/ControlCore.cpp

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation
104104
auto pfnTerminalTaskbarProgressChanged = std::bind(&ControlCore::_terminalTaskbarProgressChanged, this);
105105
_terminal->TaskbarProgressChangedCallback(pfnTerminalTaskbarProgressChanged);
106106

107+
// MSFT 33353327: Initialize the renderer in the ctor instead of Initialize().
108+
// We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go.
109+
// If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach
110+
// the UIA Engine to the renderer. This prevents us from signaling changes to the cursor or buffer.
111+
{
112+
// First create the render thread.
113+
// Then stash a local pointer to the render thread so we can initialize it and enable it
114+
// to paint itself *after* we hand off its ownership to the renderer.
115+
// We split up construction and initialization of the render thread object this way
116+
// because the renderer and render thread have circular references to each other.
117+
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
118+
auto* const localPointerToThread = renderThread.get();
119+
120+
// Now create the renderer and initialize the render thread.
121+
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
122+
123+
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
124+
if (auto strongThis{ weakThis.get() })
125+
{
126+
strongThis->_RendererEnteredErrorStateHandlers(*strongThis, nullptr);
127+
}
128+
});
129+
130+
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
131+
}
132+
107133
// Get our dispatcher. If we're hosted in-proc with XAML, this will get
108134
// us the same dispatcher as TermControl::Dispatcher(). If we're out of
109135
// proc, this'll return null. We'll need to instead make a new
@@ -196,27 +222,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
196222
return false;
197223
}
198224

199-
// First create the render thread.
200-
// Then stash a local pointer to the render thread so we can initialize it and enable it
201-
// to paint itself *after* we hand off its ownership to the renderer.
202-
// We split up construction and initialization of the render thread object this way
203-
// because the renderer and render thread have circular references to each other.
204-
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
205-
auto* const localPointerToThread = renderThread.get();
206-
207-
// Now create the renderer and initialize the render thread.
208-
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
209-
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
210-
211-
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
212-
if (auto strongThis{ weakThis.get() })
213-
{
214-
strongThis->_RendererEnteredErrorStateHandlers(*strongThis, nullptr);
215-
}
216-
});
217-
218-
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
219-
220225
// Set up the DX Engine
221226
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
222227
_renderer->AddRenderEngine(dxEngine.get());
@@ -248,7 +253,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
248253
_settings.InitialCols(width);
249254
_settings.InitialRows(height);
250255

251-
_terminal->CreateFromSettings(_settings, renderTarget);
256+
_terminal->CreateFromSettings(_settings, *_renderer);
252257

253258
// IMPORTANT! Set this callback up sooner than later. If we do it
254259
// after Enable, then it'll be possible to paint the frame once
@@ -1465,10 +1470,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
14651470

14661471
void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
14671472
{
1468-
if (_renderer)
1469-
{
1470-
_renderer->AddRenderEngine(pEngine);
1471-
}
1473+
// _renderer will always exist since it's introduced in the ctor
1474+
_renderer->AddRenderEngine(pEngine);
14721475
}
14731476

14741477
bool ControlCore::IsInReadOnlyMode() const

src/cascadia/TerminalControl/ControlInteractivity.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
614614
Control::InteractivityAutomationPeer ControlInteractivity::OnCreateAutomationPeer()
615615
try
616616
{
617-
auto autoPeer = winrt::make_self<implementation::InteractivityAutomationPeer>(this);
617+
const auto autoPeer = winrt::make_self<implementation::InteractivityAutomationPeer>(this);
618618

619619
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get());
620620
_core->AttachUiaEngine(_uiaEngine.get());

src/cascadia/TerminalControl/TermControl.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
377377
_changeBackgroundColor(bg);
378378

379379
// Apply padding as swapChainPanel's margin
380-
auto newMargin = ParseThicknessFromPadding(newSettings.Padding());
380+
const auto newMargin = ParseThicknessFromPadding(newSettings.Padding());
381381
SwapChainPanel().Margin(newMargin);
382382

383383
TSFInputControl().Margin(newMargin);
@@ -511,18 +511,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
511511
// - The automation peer for our control
512512
Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer()
513513
{
514-
if (_initializedTerminal && !_IsClosing()) // only set up the automation peer if we're ready to go live
514+
// MSFT 33353327: We're purposefully not using _initializedTerminal to ensure we're fully initialized.
515+
// Doing so makes us return nullptr when XAML requests an automation peer.
516+
// Instead, we need to give XAML an automation peer, then fix it later.
517+
if (!_IsClosing())
515518
{
516519
// create a custom automation peer with this code pattern:
517520
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
518521
if (const auto& interactivityAutoPeer{ _interactivity.OnCreateAutomationPeer() })
519522
{
520-
auto margins{ SwapChainPanel().Margin() };
521-
522-
Core::Padding padding{ margins.Left,
523-
margins.Top,
524-
margins.Right,
525-
margins.Bottom };
523+
const auto margins{ SwapChainPanel().Margin() };
524+
const Core::Padding padding{ margins.Left,
525+
margins.Top,
526+
margins.Right,
527+
margins.Bottom };
526528
_automationPeer = winrt::make<implementation::TermControlAutomationPeer>(this, padding, interactivityAutoPeer);
527529
return _automationPeer;
528530
}
@@ -717,6 +719,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
717719

718720
_initializedTerminal = true;
719721

722+
// MSFT 33353327: If the AutomationPeer was created before we were done initializing,
723+
// make sure it's properly set up now.
724+
if (_automationPeer)
725+
{
726+
_automationPeer.UpdateControlBounds();
727+
const auto margins{ GetPadding() };
728+
_automationPeer.SetControlPadding(Core::Padding{ margins.Left,
729+
margins.Top,
730+
margins.Right,
731+
margins.Bottom });
732+
}
733+
720734
// Likewise, run the event handlers outside of lock (they could
721735
// be reentrant)
722736
_InitializedHandlers(*this, nullptr);

src/types/UiaTextRangeBase.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,12 @@ try
938938
const auto maxLengthOpt = (maxLength == -1) ?
939939
std::nullopt :
940940
std::optional<unsigned int>{ maxLength };
941+
_pData->LockConsole();
942+
auto Unlock = wil::scope_exit([this]() noexcept {
943+
_pData->UnlockConsole();
944+
});
941945
const auto text = _getTextValue(maxLengthOpt);
946+
Unlock.reset();
942947

943948
*pRetVal = SysAllocString(text.c_str());
944949
RETURN_HR_IF_NULL(E_OUTOFMEMORY, *pRetVal);
@@ -958,11 +963,6 @@ CATCH_RETURN();
958963
#pragma warning(disable : 26447) // compiler isn't filtering throws inside the try/catch
959964
std::wstring UiaTextRangeBase::_getTextValue(std::optional<unsigned int> maxLength) const
960965
{
961-
_pData->LockConsole();
962-
auto Unlock = wil::scope_exit([&]() noexcept {
963-
_pData->UnlockConsole();
964-
});
965-
966966
std::wstring textData{};
967967
if (!IsDegenerate())
968968
{

0 commit comments

Comments
 (0)