Don't close infobar on first chrome.debugger.detach

Steps to reproduce: Call chrome.debugger.attach for two different
targets (different tabs), then call chrome.debugger.detach for
only one of the two targets, test that only the one target specified
is detached.

Fixed: 1489587
Change-Id: I3a77fbb79a8275feba7896440d348283907cda4b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4910999
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1207292}
diff --git a/chrome/browser/extensions/api/debugger/debugger_api.cc b/chrome/browser/extensions/api/debugger/debugger_api.cc
index 09694b7..b5a5b6d3 100644
--- a/chrome/browser/extensions/api/debugger/debugger_api.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_api.cc
@@ -396,7 +396,6 @@
 }
 
 ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() {
-  ExtensionDevToolsInfoBarDelegate::NotifyExtensionDetached(extension_id());
   g_attached_client_hosts.Get().erase(this);
 
   // Decrement the associated worker keepalive, if any.
diff --git a/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
index 92b635b..9f769f86 100644
--- a/chrome/browser/extensions/api/debugger/debugger_apitest.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
@@ -464,6 +464,92 @@
   EXPECT_EQ(0u, manager->infobar_count());
 }
 
+IN_PROC_BROWSER_TEST_F(DebuggerApiTest,
+                       InfoBarIsNotRemovedWhenAnotherDebuggerAttached) {
+  const int tab_id1 = sessions::SessionTabHelper::IdForTab(
+                          browser()->tab_strip_model()->GetActiveWebContents())
+                          .id();
+  infobars::ContentInfoBarManager* manager =
+      infobars::ContentInfoBarManager::FromWebContents(
+          browser()->tab_strip_model()->GetActiveWebContents());
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
+      browser(), embedded_test_server()->GetURL("/simple.html"),
+      WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
+  const int tab_id2 = sessions::SessionTabHelper::IdForTab(
+                          browser()->tab_strip_model()->GetActiveWebContents())
+                          .id();
+
+  // Attaching to a tab should create an infobar.
+  {
+    auto attach_function = base::MakeRefCounted<DebuggerAttachFunction>();
+    attach_function->set_extension(extension());
+    ASSERT_TRUE(api_test_utils::RunFunction(
+        attach_function.get(),
+        base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id1), profile()));
+  }
+
+  EXPECT_EQ(1u, manager->infobar_count());
+
+  // Attaching to a 2nd tab, to have another attached debugger.
+  {
+    auto attach_function = base::MakeRefCounted<DebuggerAttachFunction>();
+    attach_function->set_extension(extension());
+    ASSERT_TRUE(api_test_utils::RunFunction(
+        attach_function.get(),
+        base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id2), profile()));
+  }
+
+  EXPECT_EQ(1u, manager->infobar_count());
+
+  // Detaching from the tab should not remove the infobar after 5 seconds, as
+  // another debugger is still attached.
+  {
+    auto detach_function = base::MakeRefCounted<DebuggerDetachFunction>();
+    detach_function->set_extension(extension());
+    ASSERT_TRUE(api_test_utils::RunFunction(
+        detach_function.get(), base::StringPrintf("[{\"tabId\": %d}]", tab_id1),
+        profile()));
+  }
+
+  // Advance the clock by 5 seconds.
+  {
+    base::RunLoop run_loop;
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(),
+        ExtensionDevToolsInfoBarDelegate::kAutoCloseDelay);
+    AdvanceClock(ExtensionDevToolsInfoBarDelegate::kAutoCloseDelay);
+    run_loop.Run();
+  }
+
+  // Verify inforbar not removed.
+  EXPECT_EQ(1u, manager->infobar_count());
+
+  // Now detach the last debugger.
+  {
+    auto detach_function = base::MakeRefCounted<DebuggerDetachFunction>();
+    detach_function->set_extension(extension());
+    ASSERT_TRUE(api_test_utils::RunFunction(
+        detach_function.get(), base::StringPrintf("[{\"tabId\": %d}]", tab_id2),
+        profile()));
+  }
+
+  // Advance the clock by 5 seconds, once again.
+  {
+    base::RunLoop run_loop;
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(),
+        ExtensionDevToolsInfoBarDelegate::kAutoCloseDelay);
+    AdvanceClock(ExtensionDevToolsInfoBarDelegate::kAutoCloseDelay);
+    run_loop.Run();
+  }
+
+  // Verify inforbar removed.
+  EXPECT_EQ(0u, manager->infobar_count());
+}
+
 class CrossProfileDebuggerApiTest : public DebuggerApiTest {
  protected:
   Profile* other_profile() { return other_profile_; }
diff --git a/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.cc b/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.cc
index 88dcd56..da679fc 100644
--- a/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.cc
+++ b/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.cc
@@ -61,18 +61,6 @@
   DCHECK(erased);
 }
 
-void ExtensionDevToolsInfoBarDelegate::NotifyExtensionDetached(
-    const std::string& extension_id) {
-  const Delegates& delegates = g_delegates.Get();
-  const auto iter = delegates.find(extension_id);
-  if (iter != delegates.cend()) {
-    // Infobar_ was set in Create() which makes the following access safe.
-    iter->second->timer_.Start(FROM_HERE, kAutoCloseDelay,
-                               iter->second->infobar_.get(),
-                               &GlobalConfirmInfoBar::Close);
-  }
-}
-
 infobars::InfoBarDelegate::InfoBarIdentifier
 ExtensionDevToolsInfoBarDelegate::GetIdentifier() const {
   return EXTENSION_DEV_TOOLS_INFOBAR_DELEGATE;
@@ -114,7 +102,11 @@
     std::string extension_id,
     const std::string& extension_name)
     : extension_id_(std::move(extension_id)),
-      extension_name_(base::UTF8ToUTF16(extension_name)) {}
+      extension_name_(base::UTF8ToUTF16(extension_name)) {
+  callback_list_.set_removal_callback(base::BindRepeating(
+      &ExtensionDevToolsInfoBarDelegate::MaybeStartAutocloseTimer,
+      base::Unretained(this)));
+}
 
 base::CallbackListSubscription
 ExtensionDevToolsInfoBarDelegate::RegisterDestroyedCallback(
@@ -122,4 +114,12 @@
   return callback_list_.Add(std::move(destroyed_callback));
 }
 
+void ExtensionDevToolsInfoBarDelegate::MaybeStartAutocloseTimer() {
+  if (callback_list_.empty()) {
+    // infobar_ was set in Create() which makes the following access safe.
+    timer_.Start(FROM_HERE, kAutoCloseDelay, infobar_.get(),
+                 &GlobalConfirmInfoBar::Close);
+  }
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.h b/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.h
index 48fb3aa..9363aac 100644
--- a/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.h
+++ b/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar_delegate.h
@@ -47,9 +47,6 @@
   gfx::ElideBehavior GetMessageElideBehavior() const override;
   int GetButtons() const override;
 
-  // Autocloses the infobar_ after 5 seconds.
-  static void NotifyExtensionDetached(const std::string& extension_id);
-
  private:
   ExtensionDevToolsInfoBarDelegate(std::string extension_id,
                                    const std::string& extension_name);
@@ -58,6 +55,9 @@
   base::CallbackListSubscription RegisterDestroyedCallback(
       base::OnceClosure destroyed_callback);
 
+  // Autocloses the infobar_ after 5 seconds, only when no callbacks remain.
+  void MaybeStartAutocloseTimer();
+
   const ExtensionId extension_id_;
   const std::u16string extension_name_;
   // infobar_ is set after attaching an extension and is deleted 5 seconds after