Delete the main frame RenderWidget during detach if it's not undead

When RenderViewImpl::Destroy() happens, if there is no local main frame
it should Close the WebView and destroy the undead RenderWidget.

But if there is a local main frame, then Closing the WebView will
detach that main frame, and in the process we should destroy the
RenderWidget for the main frame in the same way that we would for a
child frame.

This makes RenderViewImpl::DetachWebFrameWidget destroy the
RenderWidget if the call happens as part of WebView closing, from
RenderViewImpl::Destroy().

Otherwise, it continues to only close the WebFrameWidget and remove it
from the undead RenderWidget.

[email protected]

Bug: 419087
Change-Id: I642ccf0fe95547d5614af6b29c80a6176adaa231
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1825733
Reviewed-by: Daniel Cheng <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: danakj <[email protected]>
Cr-Commit-Position: refs/heads/master@{#701727}
diff --git a/content/public/renderer/render_frame_observer.h b/content/public/renderer/render_frame_observer.h
index 81b5f690..dada7d6 100644
--- a/content/public/renderer/render_frame_observer.h
+++ b/content/public/renderer/render_frame_observer.h
@@ -71,9 +71,6 @@
   virtual void WasHidden() {}
   virtual void WasShown() {}
 
-  // Called when associated widget is about to close.
-  virtual void WidgetWillClose() {}
-
   // Navigation callbacks.
   //
   // Each navigation starts with a DidStartNavigation call. Then it may be
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 876936a7..51f0f8c 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -5793,11 +5793,6 @@
 #endif  // ENABLE_PLUGINS
 }
 
-void RenderFrameImpl::WidgetWillClose() {
-  for (auto& observer : observers_)
-    observer.WidgetWillClose();
-}
-
 bool RenderFrameImpl::IsMainFrame() {
   return is_main_frame_;
 }
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 74e8541..e34937e 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -323,7 +323,6 @@
   // Notifications from RenderWidget.
   void WasHidden();
   void WasShown();
-  void WidgetWillClose();
 
   // Start/Stop loading notifications.
   // TODO(nasko): Those are page-level methods at this time and come from
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index bb019d4..4a3c760 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -79,6 +79,7 @@
 #include "content/renderer/render_process.h"
 #include "content/renderer/render_thread_impl.h"
 #include "content/renderer/render_widget_fullscreen_pepper.h"
+#include "content/renderer/render_widget_screen_metrics_emulator.h"
 #include "content/renderer/renderer_blink_platform_impl.h"
 #include "content/renderer/savable_resources.h"
 #include "content/renderer/v8_value_converter_impl.h"
@@ -553,7 +554,10 @@
 }
 
 RenderViewImpl::~RenderViewImpl() {
+  DCHECK(destroying_);  // Always deleted through Destroy().
   DCHECK(!frame_widget_);
+
+  g_routing_id_view_map.Get().erase(routing_id_);
   RenderThread::Get()->RemoveRoute(routing_id_);
 
 #ifndef NDEBUG
@@ -1042,7 +1046,21 @@
 }
 
 void RenderViewImpl::Destroy() {
-  render_widget_->PrepareForClose();
+  destroying_ = true;
+
+  // If there is no local main frame, then destroying the WebView will not
+  // detach anything, and the RenderWidget will not be destroyed. So we have
+  // to do it here.
+  bool close_render_widget_here = !main_render_frame_;
+
+  // Disable emulation before destroying everything. Turning off emulation
+  // accesses the WebViewImpl and the main frame (if it exists).
+  // TODO(danakj): Since we are being destroyed, is there even a reason to turn
+  // emulation off before closing?
+  if (page_properties()->ScreenMetricsEmulator()) {
+    page_properties()->ScreenMetricsEmulator()->DisableAndApply();
+    page_properties()->SetScreenMetricsEmulator(nullptr);
+  }
 
   webview_->Close();
   // The webview_ is already destroyed by the time we get here, remove any
@@ -1050,14 +1068,22 @@
   g_view_map.Get().erase(webview_);
   webview_ = nullptr;
 
-  // We pass ownership of |render_widget_| to itself. Grab a raw pointer to call
-  // the Close() method on so we don't have to be a C++ expert to know whether
-  // we will end up with a nullptr where we didn't intend due to order of
-  // execution.
-  RenderWidget* closing_widget = render_widget_.get();
-  closing_widget->Close(std::move(render_widget_));
-
-  g_routing_id_view_map.Get().erase(GetRoutingID());
+  // We do this after WebView has closed, though it should not matter. WebView
+  // only uses the RenderWidget through WebWidgetClient that it accesses through
+  // a main frame. So it should not be able to see this happening when there is
+  // no local main frame.
+  if (close_render_widget_here) {
+    // TODO(danakj): Go through CloseForFrame()? But we don't need/want to post-
+    // task the Close step here, do we? Since we're inside RenderViewImpl
+    // destruction?
+    render_widget_->PrepareForClose();
+    // We pass ownership of |render_widget_| to itself. Grab a raw pointer to
+    // call the Close() method on so we don't have to be a C++ expert to know
+    // whether we will end up with a nullptr where we didn't intend due to order
+    // of execution.
+    RenderWidget* closing_widget = render_widget_.get();
+    closing_widget->Close(std::move(render_widget_));
+  }
 
   delete this;
 }
@@ -1549,17 +1575,39 @@
 }
 
 void RenderViewImpl::DetachWebFrameWidget() {
-  // We should detach when making the RenderWidget undead so we don't expect it
-  // to be undead already. But when it is recycled for a provisional frame, then
-  // we can detach when closing the provisional frame.
-  DCHECK(render_widget_->IsUndeadOrProvisional() ||
-         render_widget_->is_closing());
   DCHECK(frame_widget_);
-  frame_widget_->Close();
-  frame_widget_ = nullptr;
 
-  // This just clears the webwidget_internal_ member from RenderWidget.
-  render_widget_->SetWebWidgetInternal(nullptr);
+  if (destroying_) {
+    // We are inside RenderViewImpl::Destroy() and the main frame is being
+    // detached as part of shutdown. So we can destroy the RenderWidget.
+
+    // The RenderWidget is closed and it will close the WebWidget stored in
+    // |frame_widget_|. We just want to drop raw pointer here.
+    frame_widget_ = nullptr;
+    // TODO(danakj): Go through CloseForFrame()? But we don't need/want to post-
+    // task the Close step here, do we? Since we're inside RenderViewImpl
+    // destruction?
+    render_widget_->PrepareForClose();
+    // We pass ownership of |render_widget_| to itself. Grab a raw pointer to
+    // call the Close() method on so we don't have to be a C++ expert to know
+    // whether we will end up with a nullptr where we didn't intend due to order
+    // of execution.
+    RenderWidget* closing_widget = render_widget_.get();
+    closing_widget->Close(std::move(render_widget_));
+  } else {
+    // We are not inside RenderViewImpl::Destroy(), the main frame is being
+    // detached and replaced with a remote frame proxy. We can't close the
+    // RenderWidget, and it is marked undead instead, but we do need to close
+    // the WebFrameWidget and remove it from the RenderWidget.
+
+    DCHECK(render_widget_->IsUndeadOrProvisional());
+    // The WebWidget needs to be closed even though the RenderWidget won't be
+    // here (since it is marked undead instead).
+    frame_widget_->Close();
+    frame_widget_ = nullptr;
+    // This just clears the webwidget_internal_ member from RenderWidget.
+    render_widget_->SetWebWidgetInternal(nullptr);
+  }
 }
 
 bool RenderViewImpl::SetZoomLevel(double zoom_level) {
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 30dbc35..6ce8dda6 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -197,6 +197,10 @@
   // frame is going away.
   void DetachWebFrameWidget();
 
+  // Called early before detaching begins for the main frame, and objects begin
+  // tearing down.
+  void PrepareForDetach();
+
   // Starts a timer to send an UpdateState message on behalf of |frame|, if the
   // timer isn't already running. This allows multiple state changing events to
   // be coalesced into one update.
@@ -552,6 +556,9 @@
   // it in the same order in the .cc file as it was in the header.
   // ---------------------------------------------------------------------------
 
+  // Becomes true when Destroy() is called.
+  bool destroying_ = false;
+
   // This is the |render_widget_| for the main frame.
   //
   // Instances of RenderWidget for child frame local roots, popups, and
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index be82a1b..9764b05 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -536,10 +536,12 @@
   // The RenderWidget may be deattached from JS, which in turn may be called
   // in a re-entrant context. We cannot synchronously destroy the object, so we
   // post a task to do so later.
+  //
+  // Unretained(this) stays valid because |this| is owned by the |widget| being
+  // passed to Close().
   GetCleanupTaskRunner()->PostNonNestableTask(
-      FROM_HERE,
-      base::BindOnce(&RenderWidget::Close, close_weak_ptr_factory_.GetWeakPtr(),
-                     std::move(widget)));
+      FROM_HERE, base::BindOnce(&RenderWidget::Close, base::Unretained(this),
+                                std::move(widget)));
 }
 
 void RenderWidget::Init(ShowCallback show_callback, WebWidget* web_widget) {
@@ -722,18 +724,17 @@
 
   // IPCs can be invoked from nested message loops. We must dispatch this
   // task non-nested to avoid re-entrancy issues.
+  //
+  // Unretained(this) stays valid because |this| is owned by the |widget| being
+  // passed to Close().
   GetCleanupTaskRunner()->PostNonNestableTask(
-      FROM_HERE,
-      base::BindOnce(&RenderWidget::Close, close_weak_ptr_factory_.GetWeakPtr(),
-                     base::WrapUnique(this)));
+      FROM_HERE, base::BindOnce(&RenderWidget::Close, base::Unretained(this),
+                                base::WrapUnique(this)));
 }
 
 void RenderWidget::PrepareForClose() {
   DCHECK(RenderThread::IsMainThread());
-  if (closing_)
-    return;
-  for (auto& observer : render_frames_)
-    observer.WidgetWillClose();
+  DCHECK(!closing_);
   closing_ = true;
 
   // Browser correspondence is no longer needed at this point.
@@ -747,27 +748,12 @@
   if (input_event_queue_)
     input_event_queue_->ClearClient();
 
-  // The emulator expects to use the WebWidget and layer_tree_host_, so disable
-  // it after closing the IPC route and before destroying those objects.
-  //
-  // Screen metrics emulation can only be set by the local main frame render
-  // widget.
-  if (delegate() && page_properties_->ScreenMetricsEmulator())
-    page_properties_->ScreenMetricsEmulator()->DisableAndApply();
+  close_weak_ptr_factory_.InvalidateWeakPtrs();
 
-  // TODO(https://crbug.com/995981): This logic is very confusing and should be
-  // fixed. When RenderWidget is owned by a RenderViewImpl, its lifetime is tied
-  // to the RenderViewImpl. In that case the RenderViewImpl takes responsibility
-  // for closing the WebWidget when the main frame is detached.
-  //
-  // For all other RenderWidgets, the RenderWidget is destroyed at the same
-  // time as the WebWidget, and the RenderWidget takes responsibility for doing
-  // that here.
-  if (!delegate())
+  // The |webwidget_| will be null when the main frame RenderWidget is undead.
+  if (webwidget_)
     webwidget_->Close();
   webwidget_ = nullptr;
-
-  close_weak_ptr_factory_.InvalidateWeakPtrs();
 }
 
 void RenderWidget::SynchronizeVisualPropertiesFromRenderView(
@@ -2085,13 +2071,15 @@
 }
 
 void RenderWidget::Close(std::unique_ptr<RenderWidget> widget) {
+  DCHECK(closing_);  // PrepareForClose() comes first.
+  // At the end of this method, |widget| which points to this is deleted.
+  DCHECK_EQ(widget.get(), this);
+
   layer_tree_view_.reset();
   // Note the ACK is a control message going to the RenderProcessHost.
   RenderThread::Get()->Send(new WidgetHostMsg_Close_ACK(routing_id()));
+  // For the destructor to verify |this| was deleted by Close().
   closed_ = true;
-
-  // At the end of this method, |widget| which points to this is deleted.
-  DCHECK_EQ(widget.get(), this);
 }
 
 blink::WebFrameWidget* RenderWidget::GetFrameWidget() const {