mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 18:43:54 -06:00
Fix a crash when closing panes (#17333)
Calling Close() from within WalkPanes is not safe. Simply using _FindPane is enough to fix this. This PR also fixes another potential source of infinite recursion, and fixes panes being passed by-value into the callbacks. Closes #17305 ## Validation Steps Performed * Split panes vertically 3 times * `exit` the middle, the bottom and final one, in that order * Doesn't crash ✅
This commit is contained in:
parent
bdc7c4fdbc
commit
baba406a07
@ -467,7 +467,7 @@ std::shared_ptr<Pane> Pane::NextPane(const std::shared_ptr<Pane> targetPane)
|
|||||||
std::shared_ptr<Pane> nextPane = nullptr;
|
std::shared_ptr<Pane> nextPane = nullptr;
|
||||||
auto foundTarget = false;
|
auto foundTarget = false;
|
||||||
|
|
||||||
auto foundNext = WalkTree([&](auto pane) {
|
auto foundNext = WalkTree([&](const auto& pane) {
|
||||||
// If we are a parent pane we don't want to move to one of our children
|
// If we are a parent pane we don't want to move to one of our children
|
||||||
if (foundTarget && targetPane->_HasChild(pane))
|
if (foundTarget && targetPane->_HasChild(pane))
|
||||||
{
|
{
|
||||||
@ -985,6 +985,17 @@ void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectab
|
|||||||
// - <none>
|
// - <none>
|
||||||
void Pane::Close()
|
void Pane::Close()
|
||||||
{
|
{
|
||||||
|
// Pane has two events, CloseRequested and Closed. CloseRequested is raised by the content asking to be closed,
|
||||||
|
// but also by the window who owns the tab when it's closing. The event is then caught by the TerminalTab which
|
||||||
|
// calls Close() which then raises the Closed event. Now, if this is the last pane in the window, this will result
|
||||||
|
// in the window raising CloseRequested again which leads to infinite recursion, so we need to guard against that.
|
||||||
|
// Ideally we would have just a single event in the future.
|
||||||
|
if (_closed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_closed = true;
|
||||||
// Fire our Closed event to tell our parent that we should be removed.
|
// Fire our Closed event to tell our parent that we should be removed.
|
||||||
Closed.raise(nullptr, nullptr);
|
Closed.raise(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
@ -1341,7 +1352,7 @@ std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
|
|||||||
detached->_ApplySplitDefinitions();
|
detached->_ApplySplitDefinitions();
|
||||||
|
|
||||||
// Trigger the detached event on each child
|
// Trigger the detached event on each child
|
||||||
detached->WalkTree([](auto pane) {
|
detached->WalkTree([](const auto& pane) {
|
||||||
pane->Detached.raise(pane);
|
pane->Detached.raise(pane);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1542,7 +1553,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
|||||||
{
|
{
|
||||||
// update our path to our first remaining leaf
|
// update our path to our first remaining leaf
|
||||||
_parentChildPath = _firstChild;
|
_parentChildPath = _firstChild;
|
||||||
_firstChild->WalkTree([](auto p) {
|
_firstChild->WalkTree([](const auto& p) {
|
||||||
if (p->_IsLeaf())
|
if (p->_IsLeaf())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@ -2398,7 +2409,7 @@ void Pane::Id(uint32_t id) noexcept
|
|||||||
bool Pane::FocusPane(const uint32_t id)
|
bool Pane::FocusPane(const uint32_t id)
|
||||||
{
|
{
|
||||||
// Always clear the parent child path if we are focusing a leaf
|
// Always clear the parent child path if we are focusing a leaf
|
||||||
return WalkTree([=](auto p) {
|
return WalkTree([=](const auto& p) {
|
||||||
p->_parentChildPath.reset();
|
p->_parentChildPath.reset();
|
||||||
if (p->_id == id)
|
if (p->_id == id)
|
||||||
{
|
{
|
||||||
@ -2421,7 +2432,7 @@ bool Pane::FocusPane(const uint32_t id)
|
|||||||
// - true if focus was set
|
// - true if focus was set
|
||||||
bool Pane::FocusPane(const std::shared_ptr<Pane> pane)
|
bool Pane::FocusPane(const std::shared_ptr<Pane> pane)
|
||||||
{
|
{
|
||||||
return WalkTree([&](auto p) {
|
return WalkTree([&](const auto& p) {
|
||||||
if (p == pane)
|
if (p == pane)
|
||||||
{
|
{
|
||||||
p->_Focus();
|
p->_Focus();
|
||||||
|
|||||||
@ -248,7 +248,7 @@ private:
|
|||||||
|
|
||||||
std::optional<uint32_t> _id;
|
std::optional<uint32_t> _id;
|
||||||
std::weak_ptr<Pane> _parentChildPath{};
|
std::weak_ptr<Pane> _parentChildPath{};
|
||||||
|
bool _closed{ false };
|
||||||
bool _lastActive{ false };
|
bool _lastActive{ false };
|
||||||
winrt::event_token _firstClosedToken{ 0 };
|
winrt::event_token _firstClosedToken{ 0 };
|
||||||
winrt::event_token _secondClosedToken{ 0 };
|
winrt::event_token _secondClosedToken{ 0 };
|
||||||
|
|||||||
@ -739,7 +739,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
|
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
|
||||||
pane->WalkTree([](auto p) {
|
pane->WalkTree([](const auto& p) {
|
||||||
if (const auto control{ p->GetTerminalControl() })
|
if (const auto control{ p->GetTerminalControl() })
|
||||||
{
|
{
|
||||||
if (control.ReadOnly())
|
if (control.ReadOnly())
|
||||||
|
|||||||
@ -3812,7 +3812,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
// recipe for disaster. We won't ever open up a tab in this window.
|
// recipe for disaster. We won't ever open up a tab in this window.
|
||||||
newTerminalArgs.Elevate(false);
|
newTerminalArgs.Elevate(false);
|
||||||
const auto newPane = _MakePane(newTerminalArgs, nullptr, connection);
|
const auto newPane = _MakePane(newTerminalArgs, nullptr, connection);
|
||||||
newPane->WalkTree([](auto pane) {
|
newPane->WalkTree([](const auto& pane) {
|
||||||
pane->FinalizeConfigurationGivenDefault();
|
pane->FinalizeConfigurationGivenDefault();
|
||||||
});
|
});
|
||||||
_CreateNewTabFromPane(newPane);
|
_CreateNewTabFromPane(newPane);
|
||||||
|
|||||||
@ -41,7 +41,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
|
|
||||||
auto firstId = _nextPaneId;
|
auto firstId = _nextPaneId;
|
||||||
|
|
||||||
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
|
_rootPane->WalkTree([&](const auto& pane) {
|
||||||
// update the IDs on each pane
|
// update the IDs on each pane
|
||||||
if (pane->_IsLeaf())
|
if (pane->_IsLeaf())
|
||||||
{
|
{
|
||||||
@ -203,7 +203,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
{
|
{
|
||||||
ASSERT_UI_THREAD();
|
ASSERT_UI_THREAD();
|
||||||
|
|
||||||
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
|
_rootPane->WalkTree([&](const auto& pane) {
|
||||||
// Attach event handlers to each new pane
|
// Attach event handlers to each new pane
|
||||||
_AttachEventHandlersToPane(pane);
|
_AttachEventHandlersToPane(pane);
|
||||||
if (auto content = pane->GetContent())
|
if (auto content = pane->GetContent())
|
||||||
@ -275,7 +275,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
_UpdateHeaderControlMaxWidth();
|
_UpdateHeaderControlMaxWidth();
|
||||||
|
|
||||||
// Update the settings on all our panes.
|
// Update the settings on all our panes.
|
||||||
_rootPane->WalkTree([&](auto pane) {
|
_rootPane->WalkTree([&](const auto& pane) {
|
||||||
pane->UpdateSettings(settings);
|
pane->UpdateSettings(settings);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@ -534,7 +534,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
|
|
||||||
// Add the new event handlers to the new pane(s)
|
// Add the new event handlers to the new pane(s)
|
||||||
// and update their ids.
|
// and update their ids.
|
||||||
pane->WalkTree([&](auto p) {
|
pane->WalkTree([&](const auto& p) {
|
||||||
_AttachEventHandlersToPane(p);
|
_AttachEventHandlersToPane(p);
|
||||||
if (p->_IsLeaf())
|
if (p->_IsLeaf())
|
||||||
{
|
{
|
||||||
@ -624,7 +624,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
// manually.
|
// manually.
|
||||||
_rootPane->Closed(_rootClosedToken);
|
_rootPane->Closed(_rootClosedToken);
|
||||||
auto p = _rootPane;
|
auto p = _rootPane;
|
||||||
p->WalkTree([](auto pane) {
|
p->WalkTree([](const auto& pane) {
|
||||||
pane->Detached.raise(pane);
|
pane->Detached.raise(pane);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -650,7 +650,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
|
|
||||||
// Add the new event handlers to the new pane(s)
|
// Add the new event handlers to the new pane(s)
|
||||||
// and update their ids.
|
// and update their ids.
|
||||||
pane->WalkTree([&](auto p) {
|
pane->WalkTree([&](const auto& p) {
|
||||||
_AttachEventHandlersToPane(p);
|
_AttachEventHandlersToPane(p);
|
||||||
if (p->_IsLeaf())
|
if (p->_IsLeaf())
|
||||||
{
|
{
|
||||||
@ -949,26 +949,20 @@ namespace winrt::TerminalApp::implementation
|
|||||||
|
|
||||||
events.CloseRequested = content.CloseRequested(
|
events.CloseRequested = content.CloseRequested(
|
||||||
winrt::auto_revoke,
|
winrt::auto_revoke,
|
||||||
[dispatcher, weakThis](auto sender, auto&&) -> winrt::fire_and_forget {
|
[this](auto&& sender, auto&&) {
|
||||||
// Don't forget! this ^^^^^^^^ sender can't be a reference, this is a async callback.
|
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
|
||||||
|
|
||||||
// The lambda lives in the `std::function`-style container owned by `control`. That is, when the
|
|
||||||
// `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to
|
|
||||||
// copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines.
|
|
||||||
// See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870
|
|
||||||
const auto weakThisCopy = weakThis;
|
|
||||||
co_await wil::resume_foreground(dispatcher);
|
|
||||||
// Check if Tab's lifetime has expired
|
|
||||||
if (auto tab{ weakThisCopy.get() })
|
|
||||||
{
|
{
|
||||||
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
|
// Calling Close() while walking the tree is not safe, because Close() mutates the tree.
|
||||||
|
const auto pane = _rootPane->_FindPane([&](const auto& p) -> std::shared_ptr<Pane> {
|
||||||
|
if (p->GetContent() == content)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
if (pane)
|
||||||
{
|
{
|
||||||
tab->_rootPane->WalkTree([content](std::shared_ptr<Pane> pane) {
|
pane->Close();
|
||||||
if (pane->GetContent() == content)
|
|
||||||
{
|
|
||||||
pane->Close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user