diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc index 3307ffabd3cf6..6e96b4a18be6e 100644 --- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc +++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc @@ -687,6 +687,104 @@ TEST_F(DesktopWindowTreeHostPlatformTest, OnPaintAsActiveChanged) { EXPECT_FALSE(widget->ShouldPaintAsActive()); } +// A StubWindow whose Maximize() synchronously dispatches OnBoundsChanged to +// the delegate, mirroring the behaviour of X11Window::Maximize() when the +// window is currently fullscreen (X11Window::Maximize() calls +// SetFullscreen(false) which ends with +// platform_window_delegate_->OnBoundsChanged()). +class MaximizeNotifyingStubWindow : public ui::StubWindow { + public: + explicit MaximizeNotifyingStubWindow(const gfx::Rect& bounds) + : ui::StubWindow(bounds) {} + ~MaximizeNotifyingStubWindow() override = default; + + // ui::PlatformWindow: + void Maximize() override { + // Mirror X11Window: Maximize on a fullscreen window first un-fullscreens, + // which dispatches a bounds change with origin_changed=true. This call may + // synchronously delete |this| and the host -- do nothing after it. + delegate()->OnBoundsChanged({true}); + } +}; + +class MaximizeStubWindowFactory + : public aura::WindowTreeHostPlatform:: + PlatformWindowFactoryDelegateForTesting { + public: + MaximizeStubWindowFactory() { + aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting( + this); + } + ~MaximizeStubWindowFactory() override { + aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting( + nullptr); + } + + std::unique_ptr Create( + aura::WindowTreeHostPlatform* host) override { + auto stub = std::make_unique(gfx::Rect()); + stub->InitDelegateWithWidget(/*delegate=*/host, + /*widget=*/++last_accelerated_widget_); + return std::move(stub); + } + + private: + gfx::AcceleratedWidget last_accelerated_widget_ = gfx::kNullAcceleratedWidget; +}; + +// Widget observer that synchronously closes the widget the first time the +// bounds change. This stands in for any observer chain that may close the +// window during OnHostMovedInPixels/OnHostResizedInPixels -- something the +// surrounding code already guards against (see the weak-ptr guards in +// WindowTreeHostPlatform::OnBoundsChanged and +// DesktopWindowTreeHostLinux::OnBoundsChanged, and the equivalent guard in +// DesktopWindowTreeHostPlatform::SetFullscreen()). +class CloseOnBoundsChangedWidgetObserver : public WidgetObserver { + public: + explicit CloseOnBoundsChangedWidgetObserver(Widget* widget) { + observation_.Observe(widget); + } + ~CloseOnBoundsChangedWidgetObserver() override = default; + + void OnWidgetBoundsChanged(Widget* widget, + const gfx::Rect& new_bounds) override { + if (fired_) { + return; + } + fired_ = true; + observation_.Reset(); + widget->CloseNow(); + } + + private: + bool fired_ = false; + base::ScopedObservation observation_{this}; +}; + +// DesktopWindowTreeHostPlatform::Maximize() calls platform_window()->Maximize() +// followed immediately by IsMinimized() (which dereferences platform_window_ +// and performs a virtual call). On X11, PlatformWindow::Maximize() may +// synchronously dispatch OnBoundsChanged, which may delete the host. The +// sibling SetFullscreen() guards against this with a WeakPtr; Maximize() does +// not. Under ASAN this test triggers a heap-use-after-free in IsMinimized(). +TEST_F(DesktopWindowTreeHostPlatformTest, + MaximizeSurvivesSynchronousCloseDuringBoundsChange) { + auto factory = std::make_unique(); + std::unique_ptr widget = CreateWidgetWithNativeWidget(); + factory.reset(); + + widget->Show(); + + auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget( + widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget()); + ASSERT_TRUE(host_platform); + + CloseOnBoundsChangedWidgetObserver observer(widget.get()); + + // This should not crash. + host_platform->Maximize(); +} + #endif // !BUILDFLAG(IS_FUCHSIA) class VisibilityObserver : public aura::WindowObserver {