diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc index a4e9aa6ca8d05..9120edf4f8cd6 100644 --- a/ui/views/widget/widget.cc +++ b/ui/views/widget/widget.cc @@ -1616,10 +1616,15 @@ void Widget::SetCapture(View* view) { } if (!native_widget_->HasCapture()) { + // Transferring capture synchronously dispatches capture-change events to + // the previously capturing window, which may close this widget. + WidgetDeletionObserver widget_deletion_observer(this); native_widget_->SetCapture(); - // Early return if setting capture was unsuccessful. - if (!native_widget_->HasCapture()) { + // Early return if this widget was destroyed, the native widget was torn + // down, or setting capture was unsuccessful. + if (!widget_deletion_observer.IsWidgetAlive() || !native_widget_ || + !native_widget_->HasCapture()) { return; } } diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc index 7d5098e147384..24a258eb722a6 100644 --- a/ui/views/widget/widget_unittest.cc +++ b/ui/views/widget/widget_unittest.cc @@ -3570,6 +3570,68 @@ TEST_F(WidgetTest, DestroyedWithCaptureViaEventMonitor) { EXPECT_TRUE(observer.widget_closed()); } +#if defined(USE_AURA) + +namespace { + +// A View that synchronously closes a (different) Widget when capture is lost. +class CloseOnCaptureLostView : public View { + METADATA_HEADER(CloseOnCaptureLostView, View) + + public: + explicit CloseOnCaptureLostView(Widget* widget_to_close) + : widget_to_close_(widget_to_close) {} + + CloseOnCaptureLostView(const CloseOnCaptureLostView&) = delete; + CloseOnCaptureLostView& operator=(const CloseOnCaptureLostView&) = delete; + + // View: + void OnMouseCaptureLost() override { + if (widget_to_close_) { + widget_to_close_.ExtractAsDangling()->CloseNow(); + } + } + + private: + raw_ptr widget_to_close_; +}; + +BEGIN_METADATA(CloseOnCaptureLostView) +END_METADATA + +} // namespace + +// Verifies that Widget::SetCapture handles the case where dispatching the +// capture-change notification synchronously closes the widget that is +// acquiring capture. +TEST_F(WidgetTest, SetCaptureHandlesWidgetDestroyedOnCaptureChange) { + Widget* capture_holder = CreateTopLevelNativeWidget(); + capture_holder->Show(); + + Widget* target = CreateTopLevelNativeWidget(); + target->Show(); + + // Route the previous holder's capture-lost notification to a view that + // synchronously closes `target`. + capture_holder->SetCapture(capture_holder->GetContentsView()->AddChildView( + std::make_unique(target))); + ASSERT_TRUE(capture_holder->HasCapture()); + + TestWidgetObserver target_observer(target); + + // Transferring capture to `target` triggers OnMouseCaptureLost on + // `capture_holder`, which closes `target` while it is still inside + // SetCapture. This must not crash. + target->SetCapture(nullptr); + + if (!target_observer.widget_closed()) { + target->CloseNow(); + } + capture_holder->CloseNow(); +} + +#endif // defined(USE_AURA) + TEST_F(WidgetTest, LockPaintAsActive) { WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget()); widget->ShowInactive();