[M103] Reland "Fix UaF in media router dialog"

This is a reland of commit 6bb25233f4fae8519b185d08e9ba4ad90c372e4f

Some tests in the previous attempt failed because they were not waiting
for the async dialog close event. Fix that.

Original change's description:
> Fix UaF in media router dialog
>
> Dialog cleanup was done in OnWidgetClosing(), which will not be called
> if the dialog is destroyed by the platform. Use OnWidgetDestroying()
> instead.
>
> Bug: 1308341, 1240365
> Change-Id: Ifca4c2fdebc0947e0efdfffbf4f53f97855ada51
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3614727
> Reviewed-by: Takumi Fujimoto <takumif@chromium.org>
> Commit-Queue: Keren Zhu <kerenzhu@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1001140}

(cherry picked from commit 0cbff252e961eadb7b273155859ef989ff60d629)

Bug: 1308341, 1240365
Change-Id: I2e914a599e2f985371f37defa4129852e86ad214
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3638233
Reviewed-by: Takumi Fujimoto <takumif@chromium.org>
Commit-Queue: Keren Zhu <kerenzhu@chromium.org>
Cr-Original-Commit-Position: refs/heads/main@{#1003141}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3763585
Reviewed-by: Mike Wasserman <msw@chromium.org>
Auto-Submit: Keren Zhu <kerenzhu@chromium.org>
Cr-Commit-Position: refs/branch-heads/5060@{#1226}
Cr-Branched-From: b83393d0f4038aeaf67f970a024d8101df7348d1-refs/heads/main@{#1002911}
diff --git a/chrome/browser/ui/toolbar/media_router_action_controller.cc b/chrome/browser/ui/toolbar/media_router_action_controller.cc
index 6e171095..8419b198 100644
--- a/chrome/browser/ui/toolbar/media_router_action_controller.cc
+++ b/chrome/browser/ui/toolbar/media_router_action_controller.cc
@@ -23,9 +23,7 @@
           profile,
           media_router::MediaRouterFactory::GetApiForBrowserContext(profile)) {}
 
-MediaRouterActionController::~MediaRouterActionController() {
-  DCHECK_EQ(dialog_count_, 0u);
-}
+MediaRouterActionController::~MediaRouterActionController() = default;
 
 // static
 bool MediaRouterActionController::IsActionShownByPolicy(Profile* profile) {
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
index 28b24f71..f549f88 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
@@ -104,14 +104,15 @@
 void MediaRouterDialogControllerViews::Reset() {
   // If |ui_| is null, Reset() has already been called.
   if (ui_) {
-    if (IsShowingMediaRouterDialog() && GetActionController())
+    if (GetActionController())
       GetActionController()->OnDialogHidden();
     ui_.reset();
     MediaRouterDialogController::Reset();
   }
 }
 
-void MediaRouterDialogControllerViews::OnWidgetClosing(views::Widget* widget) {
+void MediaRouterDialogControllerViews::OnWidgetDestroying(
+    views::Widget* widget) {
   DCHECK(scoped_widget_observations_.IsObservingSource(widget));
   if (ui_)
     ui_->LogMediaSinkStatus();
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h
index c28340cc..13dd210 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h
@@ -46,7 +46,7 @@
   void Reset() override;
 
   // views::WidgetObserver:
-  void OnWidgetClosing(views::Widget* widget) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
 
   // Sets a callback to be called whenever a dialog is created.
   void SetDialogCreationCallbackForTesting(base::RepeatingClosure callback);
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc
index 05b45e4..1b11f3bc 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/top_container_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h"
 #include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
 #include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -20,6 +21,8 @@
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/page_transition_types.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/widget/native_widget_private.h"
 #include "ui/views/widget/widget.h"
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
@@ -51,6 +54,7 @@
 
   void OpenMediaRouterDialog();
   void CreateDialogController();
+  void CloseWebContents();
 
  protected:
   raw_ptr<WebContents> initiator_;
@@ -76,6 +80,10 @@
   ASSERT_TRUE(dialog_controller_->IsShowingMediaRouterDialog());
 }
 
+void MediaRouterDialogControllerViewsTest::CloseWebContents() {
+  initiator_->Close();
+}
+
 // Create/Get a media router dialog for initiator.
 IN_PROC_BROWSER_TEST_F(MediaRouterDialogControllerViewsTest,
                        OpenCloseMediaRouterDialog) {
@@ -88,6 +96,24 @@
   EXPECT_EQ(CastDialogView::GetCurrentDialogWidget(), nullptr);
 }
 
+// Regression test for crbug.com/1308341.
+IN_PROC_BROWSER_TEST_F(MediaRouterDialogControllerViewsTest,
+                       MediaBubbleClosedByPlatform) {
+  OpenMediaRouterDialog();
+  base::RunLoop().RunUntilIdle();
+  views::Widget* widget = CastDialogView::GetCurrentDialogWidget();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->HasObserver(dialog_controller_));
+  // The media bubble usually will close itself on deactivation, but
+  // crbug.com/1308341 shows a state where the browser is not responsive
+  // to activation change. Simulate that.
+  CastDialogView::GetInstance()->set_close_on_deactivate(false);
+  views::test::WidgetDestroyedWaiter waiter(widget);
+  widget->native_widget_private()->Close();
+  waiter.Wait();
+  CloseWebContents();
+}
+
 // The feature |media_router::kGlobalMediaControlsCastStartStop| is supported
 // on MAC, Linux and Windows only.
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
index f699e51..fe0b3a1 100644
--- a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ui/toolbar/media_router_action_controller.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/media_router/app_menu_test_api.h"
+#include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
 #include "chrome/browser/ui/views/media_router/cast_toolbar_button.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/url_constants.h"
@@ -34,6 +35,7 @@
 #include "content/public/test/test_utils.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/events/base_event_utils.h"
+#include "ui/views/test/widget_test.h"
 #include "ui/views/widget/widget.h"
 
 namespace media_router {
@@ -117,7 +119,10 @@
   menu.ExecuteCommand(IDC_ROUTE_MEDIA, 0);
   EXPECT_TRUE(dialog_controller->IsShowingMediaRouterDialog());
 
+  views::test::WidgetDestroyedWaiter waiter(
+      CastDialogView::GetCurrentDialogWidget());
   dialog_controller->HideMediaRouterDialog();
+  waiter.Wait();
   EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
 }
 
@@ -134,8 +139,11 @@
   app_menu_test_api->ExecuteCommand(IDC_ROUTE_MEDIA);
   EXPECT_TRUE(dialog_controller->IsShowingMediaRouterDialog());
 
+  views::test::WidgetDestroyedWaiter waiter(
+      CastDialogView::GetCurrentDialogWidget());
   dialog_controller->HideMediaRouterDialog();
   EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
+  waiter.Wait();
 }
 
 // TODO(crbug.com/1004635) Disabled on Linux due to flakiness.
@@ -157,8 +165,12 @@
 
   dialog_controller->ShowMediaRouterDialog(MediaRouterDialogOpenOrigin::PAGE);
   EXPECT_TRUE(ToolbarIconExists());
+
+  views::test::WidgetDestroyedWaiter waiter(
+      CastDialogView::GetCurrentDialogWidget());
   // Clicking on the toolbar icon should hide both the dialog and the icon.
   PressToolbarIcon();
+  waiter.Wait();
   EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
   EXPECT_FALSE(ToolbarIconExists());
 
@@ -252,21 +264,32 @@
   EXPECT_TRUE(dialog_controller->IsShowingMediaRouterDialog());
 
   // Reload the browser and wait.
-  content::TestNavigationObserver reload_observer(
-      browser()->tab_strip_model()->GetActiveWebContents());
-  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
-  reload_observer.Wait();
+  {
+    views::test::WidgetDestroyedWaiter waiter(
+        CastDialogView::GetCurrentDialogWidget());
+    content::TestNavigationObserver reload_observer(
+        browser()->tab_strip_model()->GetActiveWebContents());
+    chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
+    reload_observer.Wait();
 
-  // The reload should have closed the dialog.
-  EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
+    waiter.Wait();
+    // The reload should have closed the dialog.
+    EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
+  }
+
   PressToolbarIcon();
   EXPECT_TRUE(dialog_controller->IsShowingMediaRouterDialog());
 
-  // Navigate away.
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+  {
+    views::test::WidgetDestroyedWaiter waiter(
+        CastDialogView::GetCurrentDialogWidget());
+    // Navigate away.
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
 
-  // The navigation should have closed the dialog.
-  EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
+    // The navigation should have closed the dialog.
+    waiter.Wait();
+    EXPECT_FALSE(dialog_controller->IsShowingMediaRouterDialog());
+  }
   SetAlwaysShowActionPref(false);
 }