views: Preserve overlay contents during Chrome window teardown

ChromeBrowserWidget closes owned widgets before the parent WindowClosing
callback can detach overlay contents. Closing an overlay widget with
CloseNow therefore deletes its contents View and invalidates the CefView
retained by the client.

Extract CefWindowView::CloseOverlayViews and call it from both
WindowClosing and ChromeBrowserWidget::CloseOwnedWidgets. This detaches
overlay contents and restores CEF ownership before owned widgets are
destroyed. Direct overlay widget closure retains its destruction behavior,
which Alloy overlay browsers need to complete browser shutdown.

Add an explicit IsValid assertion to the overlay destruction tests.

To test:
ceftests --gtest_filter=ChromeChildWindowTest.*:OwnedWidgetShutdownTest.*:ViewsBrowserViewTest.*
This commit is contained in:
Marshall Greenblatt committed 2026-09-06 13:10:23 -04:00
1 parent 33624e6bc2
commit 2d3ce7f943
5 files changed
+25 -12

No files matched your search

@@ -219,6 +219,12 @@ void ChromeBrowserWidget::CloseOwnedWidgets() {
return;
}
// Preserve overlay contents before CloseNow bypasses the normal overlay
// cleanup in CefWindowView::WindowClosing.
if (window_view_) {
window_view_->CloseOverlayViews();
}
views::Widget::ForEachOwnedWidget(GetNativeView(),
[this](views::Widget* widget) {
if (widget != this) {
+4 -6
View File
@@ -350,12 +350,10 @@ gfx::Rect CefOverlayViewHost::ComputeBounds() const {
void CefOverlayViewHost::Cleanup() {
// This method may be called multiple times. For example, explicitly after the
// client calls CefOverlayController::Destroy or implicitly when the host
// Widget is being closed or destroyed. In most implicit cases
// CefWindowView::WindowClosing will call this before the host Widget is
// destroyed, allowing the client to optionally reuse the child View. However,
// if CefWindowView::WindowClosing is not called, DeleteDelegate will call
// this after the host Widget and all associated Widgets/Views have been
// destroyed. In the DeleteDelegate case |widget_| will return nullptr.
// Widget is being closed or destroyed. CefWindowView::CloseOverlayViews calls
// this before the host Widget is destroyed, allowing the client to reuse the
// child View. If the overlay Widget is closed directly instead, its contents
// are destroyed and WidgetIsZombie calls this after resetting |widget_|.
if (view_ && widget_) {
// Remove the child View immediately. It may be reused by the client.
auto view = view_util::GetFor(view_, /*find_known_parent=*/false);
+10 -6
View File
@@ -763,12 +763,7 @@ bool CefWindowView::CanMaximize() const {
void CefWindowView::WindowClosing() {
// Close any overlays now, before the Widget is destroyed.
// Use a copy of the array because the original may be modified while
// iterating.
std::vector<raw_ptr<CefOverlayViewHost>> overlay_hosts = overlay_hosts_;
for (auto& overlay_host : overlay_hosts) {
overlay_host->Close();
}
CloseOverlayViews();
#if BUILDFLAG(IS_LINUX)
#if BUILDFLAG(SUPPORTS_OZONE_X11)
@@ -961,6 +956,15 @@ void CefWindowView::RemoveOverlayView(CefOverlayViewHost* host,
overlay_hosts_.erase(it);
}
void CefWindowView::CloseOverlayViews() {
// Use a copy of the array because the original may be modified while
// iterating.
std::vector<raw_ptr<CefOverlayViewHost>> overlay_hosts = overlay_hosts_;
for (auto& overlay_host : overlay_hosts) {
overlay_host->Close();
}
}
void CefWindowView::MoveOverlaysIfNecessary() {
if (overlay_hosts_.empty()) {
return;
+4
View File
@@ -108,6 +108,10 @@ class CefWindowView : public CefPanelView<views::View, CefWindowDelegate>,
// Called from CefOverlayViewHost::Cleanup().
void RemoveOverlayView(CefOverlayViewHost* host, views::View* host_view);
// Close overlays before owned Widgets are destroyed, preserving their
// contents Views for reuse by the client.
void CloseOverlayViews();
// Set/get the draggable regions.
void SetDraggableRegions(const std::vector<CefDraggableRegion>& regions);
SkRegion* draggable_region() const { return draggable_region_.get(); }
+1
View File
@@ -739,6 +739,7 @@ class OverlayTestWindowDelegate : public TestWindowDelegate {
// View is still visible but no longer drawn (because it no longer belongs
// to the controller).
EXPECT_TRUE(view_->IsValid());
EXPECT_EQ(nullptr, view_->GetWindow());
EXPECT_TRUE(view_->IsVisible());
EXPECT_FALSE(view_->IsDrawn());