// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/features.h"
#include "base/files/file.h"
#include "base/one_shot_event.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "chrome/browser/extensions/browser_window_util.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/scoped_test_mv2_enabler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window_deleter.h"
#include "chrome/browser/ui/extensions/extensions_container.h"
#include "chrome/browser/ui/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_key.h"
#include "chrome/browser/ui/side_panel/side_panel_ui.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "extensions/test/extension_background_page_waiter.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
namespace {

class DevToolsExtensionsProtocolTest : public DevToolsProtocolTestBase {
 public:
  void SetUpOnMainThread() override {
    DevToolsProtocolTestBase::SetUpOnMainThread();
    AttachToBrowserTarget();
  }

  const base::DictValue* SendLoadUnpackedCommand(
      const std::string& path,
      bool enable_in_incognito = false) {
    base::FilePath extension_path =
        base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
            .AppendASCII("devtools")
            .AppendASCII("extensions")
            .AppendASCII(path);

    base::DictValue params;
    params.Set("path", extension_path.AsUTF8Unsafe());
    params.Set("enableInIncognito", enable_in_incognito);

    return SendCommandSync("Extensions.loadUnpacked", std::move(params));
  }

  scoped_refptr<const extensions::Extension> InstallExtensionFromPath(
      const std::string& path) {
    extensions::ChromeTestExtensionLoader loader(browser()->GetProfile());

    base::FilePath extension_path =
        base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
            .AppendASCII("devtools")
            .AppendASCII("extensions")
            .AppendASCII(path);

    return loader.LoadExtension(extension_path);
  }

  const base::DictValue* SendStorageCommand(
      const std::string& command,
      const extensions::Extension* extension,
      base::DictValue extra_params) {
    base::DictValue storage_params;
    storage_params.Set("id", extension->id());
    storage_params.Set("storageArea", "local");
    storage_params.Merge(std::move(extra_params));

    const base::DictValue* get_result =
        SendCommandSync(command, std::move(storage_params));
    return get_result;
  }

 private:
  // TODO(https://crbug.com/40804030): Remove this when updated to use MV3.
  extensions::ScopedTestMV2Enabler mv2_enabler_;
};


IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CanInstallExtension) {
  const base::DictValue* result =
      SendLoadUnpackedCommand("simple_background_page");
  ASSERT_TRUE(result);
  ASSERT_TRUE(result->FindString("id"));
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension = registry->GetExtensionById(
      *result->FindString("id"), extensions::ExtensionRegistry::ENABLED);
  ASSERT_TRUE(extension);
  ASSERT_EQ(extension->id(), *result->FindString("id"));
  ASSERT_EQ(extension->location(),
            extensions::mojom::ManifestLocation::kUnpacked);
  ASSERT_FALSE(extensions::util::IsIncognitoEnabled(*result->FindString("id"),
                                                    browser()->GetProfile()));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CanInstallExtensionAndEnableItForIncognito) {
  const base::DictValue* result =
      SendLoadUnpackedCommand("simple_background_page", true);
  ASSERT_TRUE(result);
  ASSERT_TRUE(result->FindString("id"));
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension =
      registry->enabled_extensions().GetByID(*result->FindString("id"));
  ASSERT_TRUE(extension);
  ASSERT_EQ(extension->location(),
            extensions::mojom::ManifestLocation::kUnpacked);

  ASSERT_TRUE(extensions::util::IsIncognitoEnabled(*result->FindString("id"),
                                                   browser()->GetProfile()));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest, ThrowsOnWrongPath) {
  const base::DictValue* result = SendLoadUnpackedCommand("non-existent");
  ASSERT_FALSE(result);
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       InstalledExtensionIsNotEnabledInIncognito) {
  const base::DictValue* result = SendLoadUnpackedCommand(
      "simple_background_page", /*enable_in_incognito=*/false);
  ASSERT_TRUE(result);
  ASSERT_TRUE(result->FindString("id"));
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension = registry->GetExtensionById(
      *result->FindString("id"), extensions::ExtensionRegistry::ENABLED);
  ASSERT_TRUE(extension);
  ASSERT_EQ(extension->id(), *result->FindString("id"));
  ASSERT_EQ(extension->location(),
            extensions::mojom::ManifestLocation::kUnpacked);
  // Verify that the extension is not enabled in incognito, as `false` was
  // passed for `enable_in_incognito`.
  ASSERT_FALSE(extensions::util::IsIncognitoEnabled(*result->FindString("id"),
                                                    browser()->GetProfile()));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest, CanUninstallExtension) {
  const base::DictValue* install_result =
      SendLoadUnpackedCommand("simple_background_page");

  std::string id = *install_result->FindString("id");
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());
  const extensions::Extension* extension_before =
      registry->GetInstalledExtension(id);
  ASSERT_TRUE(extension_before);

  base::DictValue params;
  params.Set("id", id);
  const base::DictValue* uninstall_result =
      SendCommandSync("Extensions.uninstall", std::move(params));
  ASSERT_TRUE(uninstall_result);

  const extensions::Extension* extension_after =
      registry->GetInstalledExtension(id);
  ASSERT_FALSE(extension_after);
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       PRE_ExtensionMarkedAsInstalledViaCdp) {
  const base::DictValue* install_result =
      SendLoadUnpackedCommand("simple_background_page");

  std::string id = *install_result->FindString("id");
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());
  const extensions::Extension* extension = registry->GetInstalledExtension(id);
  ASSERT_TRUE(extension);
  EXPECT_TRUE(extension->creation_flags() &
              extensions::Extension::INSTALLED_VIA_CDP);
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       ExtensionMarkedAsInstalledViaCdp) {
  base::RunLoop run_loop;
  extensions::ExtensionSystem::Get(browser()->GetProfile())
      ->ready()
      .Post(FROM_HERE, run_loop.QuitWhenIdleClosure());
  run_loop.Run();

  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());
  // The extension should not be loaded on a subsequent run.

  // Verify that the extension is not in the registry.
  // We identify the extension by checking its name.
  bool found = false;
  for (const auto& extension : registry->GenerateInstalledExtensionsSet()) {
    if (extension->name() == "Test Extension - Simple Background Page") {
      found = true;
      break;
    }
  }
  EXPECT_FALSE(found);
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CannotUninstallNonUnpackedExtension) {
  auto extension =
      extensions::ExtensionBuilder("unpacked")
          .SetLocation(extensions::mojom::ManifestLocation::kComponent)
          .Build();
  extensions::ExtensionRegistrar::Get(browser()->GetProfile())
      ->AddExtension(extension.get());

  std::string id = extension.get()->id();
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());
  const extensions::Extension* extension_before =
      registry->GetInstalledExtension(id);
  ASSERT_TRUE(extension_before);

  base::DictValue params;
  params.Set("id", id);
  const base::DictValue* uninstall_result =
      SendCommandSync("Extensions.uninstall", std::move(params));
  ASSERT_FALSE(uninstall_result);

  const extensions::Extension* extension_after =
      registry->GetInstalledExtension(id);
  ASSERT_TRUE(extension_after);
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       FailsToUninstallNonexistentExtension) {
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  std::string id = "non-existent-id";
  const extensions::Extension* extension = registry->GetInstalledExtension(id);
  ASSERT_FALSE(extension);

  base::DictValue params;
  params.Set("id", id);
  const base::DictValue* uninstallResult =
      SendCommandSync("Extensions.uninstall", std::move(params));
  ASSERT_FALSE(uninstallResult);

  const extensions::Extension* extensionAfter =
      registry->GetInstalledExtension(id);
  ASSERT_FALSE(extensionAfter);
}

// Returns the `DevToolsAgentHost` associated with an extension's service
// worker if available.
scoped_refptr<content::DevToolsAgentHost> FindExtensionHost(
    const std::string& id) {
  for (auto& host : content::DevToolsAgentHost::GetOrCreateAll()) {
    if (host->GetType() == content::DevToolsAgentHost::kTypeServiceWorker &&
        host->GetURL().GetHost() == id) {
      return host;
    }
  }
  return nullptr;
}

// Returns the `DevToolsAgentHost` associated with an extension page if
// available.
scoped_refptr<content::DevToolsAgentHost> FindBackgroundPageHost(
    const std::string& path) {
  for (auto& host : content::DevToolsAgentHost::GetOrCreateAll()) {
    if (host->GetType() == "background_page" &&
        host->GetURL().GetPath() == path) {
      return host;
    }
  }
  return nullptr;
}

// Returns the `DevToolsAgentHost` associated with an extension page if
// available.
scoped_refptr<content::DevToolsAgentHost> FindPageHost(
    const std::string& path) {
  for (auto& host : content::DevToolsAgentHost::GetOrCreateAll()) {
    if (host->GetType() == content::DevToolsAgentHost::kTypePage &&
        host->GetURL().GetPath() == path) {
      return host;
    }
  }
  return nullptr;
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest, CanGetStorageValues) {
  ExtensionTestMessageListener activated_listener("WORKER_ACTIVATED");

  const base::DictValue* load_result =
      SendLoadUnpackedCommand("service_worker");
  ASSERT_TRUE(load_result);

  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension = registry->GetExtensionById(
      *load_result->FindString("id"), extensions::ExtensionRegistry::ENABLED);
  ASSERT_TRUE(extension);

  // Ensure service worker has had time to initialize.
  EXPECT_TRUE(activated_listener.WaitUntilSatisfied());

  // Access to storage commands is only allowed from a target associated with
  // the extension. Attach to the extension service worker to be able to test
  // the method.
  DetachProtocolClient();
  agent_host_ = FindExtensionHost(extension->id());
  agent_host_->AttachClient(this);

  //  Set some dummy values in storage.
  ASSERT_TRUE(SendStorageCommand(
      "Extensions.setStorageItems", extension,
      base::DictValue().Set("values", base::DictValue()
                                          .Set("foo", "bar")
                                          .Set("other", "value")
                                          .Set("remove-on-clear", "value"))));

  // Check only the requested keys are returned.
  const base::DictValue* get_result = SendStorageCommand(
      "Extensions.getStorageItems", extension,
      base::DictValue().Set("keys", base::ListValue().Append("foo")));
  ASSERT_TRUE(get_result);
  ASSERT_EQ(*get_result->FindDict("data")->FindString("foo"), "bar");
  ASSERT_FALSE(get_result->FindDict("data")->contains("other"));

  // Remove the `foo` key.
  ASSERT_TRUE(SendStorageCommand(
      "Extensions.removeStorageItems", extension,
      base::DictValue().Set("keys", base::ListValue().Append("foo"))));

  // Check the `foo` key no longer exists.
  const base::DictValue* get_result_2 = SendStorageCommand(
      "Extensions.getStorageItems", extension,
      base::DictValue().Set("keys", base::ListValue().Append("foo")));
  ASSERT_TRUE(get_result_2);
  ASSERT_FALSE(get_result_2->FindDict("data")->contains("foo"));

  // Clear the storage area.
  ASSERT_TRUE(SendStorageCommand("Extensions.clearStorageItems", extension,
                                 base::DictValue()));

  // Check the `remove-on-clear` key no longer exists.
  const base::DictValue* get_result_3 = SendStorageCommand(
      "Extensions.getStorageItems", extension,
      base::DictValue().Set("keys",
                            base::ListValue().Append("remove-on-clear")));
  ASSERT_TRUE(get_result_3);
  ASSERT_FALSE(get_result_3->FindDict("data")->contains("remove-on-clear"));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CanGetStorageValuesBackgroundPage) {
  const base::DictValue* load_result =
      SendLoadUnpackedCommand("background_page_storage_access");
  ASSERT_TRUE(load_result);

  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension = registry->GetExtensionById(
      *load_result->FindString("id"), extensions::ExtensionRegistry::ENABLED);
  ASSERT_TRUE(extension);

  DetachProtocolClient();

  extensions::ExtensionBackgroundPageWaiter(browser()->GetProfile(), *extension)
      .WaitForBackgroundOpen();
  agent_host_ = FindBackgroundPageHost("/_generated_background_page.html");
  agent_host_->AttachClient(this);

  ASSERT_TRUE(SendStorageCommand("Extensions.getStorageItems", extension,
                                 base::DictValue()));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CanGetStorageValuesContentScript) {
  const base::DictValue* load_result =
      SendLoadUnpackedCommand("simple_content_script");
  ASSERT_TRUE(load_result);

  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension = registry->GetExtensionById(
      *load_result->FindString("id"), extensions::ExtensionRegistry::ENABLED);
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url =
      embedded_test_server()->GetURL("/devtools/page_with_content_script.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  DetachProtocolClient();
  agent_host_ = FindPageHost("/devtools/page_with_content_script.html");
  agent_host_->AttachClient(this);

  ASSERT_TRUE(SendStorageCommand("Extensions.getStorageItems", extension,
                                 base::DictValue()));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CannotGetStorageValuesWithoutContentScript) {
  // Load an extension with no associated content scripts.
  const base::DictValue* load_result =
      SendLoadUnpackedCommand("service_worker");
  ASSERT_TRUE(load_result);

  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());

  const extensions::Extension* extension = registry->GetExtensionById(
      *load_result->FindString("id"), extensions::ExtensionRegistry::ENABLED);
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url =
      embedded_test_server()->GetURL("/devtools/page_with_content_script.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  DetachProtocolClient();
  agent_host_ = FindPageHost("/devtools/page_with_content_script.html");
  agent_host_->AttachClient(this);

  const base::DictValue* get_result = SendStorageCommand(
      "Extensions.getStorageItems", extension, base::DictValue());

  // Command should fail as extension has not injected content script.
  EXPECT_FALSE(get_result);
  ASSERT_EQ(*error()->FindString("message"), "Extension not found.");
}

// Test to ensure that the target associated with an extension service worker
// cannot access data from the storage associated with another extension.
IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       CannotGetStorageValuesUnrelatedTarget) {
  ExtensionTestMessageListener activated_listener("WORKER_ACTIVATED");

  const base::DictValue* load_result =
      SendLoadUnpackedCommand("service_worker");
  ASSERT_TRUE(load_result);

  const std::string first_extension_id = *load_result->FindString("id");

  // Ensure service worker has had time to initialize.
  EXPECT_TRUE(activated_listener.WaitUntilSatisfied());

  // Load a second extension.
  load_result = SendLoadUnpackedCommand("simple_background_page");
  ASSERT_TRUE(load_result);

  const std::string second_extension_id = *load_result->FindString("id");

  // Attach to first extension.
  DetachProtocolClient();
  agent_host_ = FindExtensionHost(first_extension_id);
  agent_host_->AttachClient(this);

  // Try to load data from the second extension from a context associated with
  // the first extension. This should be blocked.
  base::DictValue storage_params;
  storage_params.Set("id", second_extension_id);
  storage_params.Set("storageArea", "local");

  const base::DictValue* get_result =
      SendCommandSync("Extensions.getStorageItems", std::move(storage_params));

  // Command should fail as target does not have access.
  EXPECT_FALSE(get_result);
  ASSERT_EQ(*error()->FindString("message"), "Extension not found.");
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest, CanGetExtensions) {
  base::FilePath extensions_dir =
      base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
          .AppendASCII("devtools")
          .AppendASCII("extensions");

  base::FilePath unpacked_path =
      extensions_dir.AppendASCII("simple_background_page");
  base::FilePath packed_path = extensions_dir.AppendASCII("service_worker");

  // Load an unpacked extension.
  const base::DictValue* result =
      SendLoadUnpackedCommand("simple_background_page");
  std::string id = *result->FindString("id");
  ASSERT_FALSE(id.empty());

  // Load packed extension
  extensions::ChromeTestExtensionLoader loader(browser()->GetProfile());
  loader.set_location(extensions::mojom::ManifestLocation::kInternal);
  loader.set_pack_extension(true);
  auto packed_extension = loader.LoadExtension(packed_path);
  ASSERT_TRUE(packed_extension);
  std::string packed_id = packed_extension->id();
  ASSERT_FALSE(packed_id.empty());

  // Verify the internal extension is actually in the registry.
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(browser()->GetProfile());
  ASSERT_TRUE(registry->enabled_extensions().Contains(packed_id));

  content::RunAllTasksUntilIdle();
  const base::DictValue* list_result =
      SendCommandSync("Extensions.getExtensions", base::DictValue());
  ASSERT_TRUE(list_result);

  const base::ListValue* extensions = list_result->FindList("extensions");
  ASSERT_TRUE(extensions);
  EXPECT_EQ(extensions->size(), 1u);

  const base::DictValue& extension_info = (*extensions)[0].GetDict();
  EXPECT_EQ(*extension_info.FindString("id"), id);
  EXPECT_EQ(*extension_info.FindString("name"),
            "Test Extension - Simple Background Page");
  EXPECT_EQ(*extension_info.FindString("version"), "0.1");
  EXPECT_TRUE(*extension_info.FindBool("enabled"));
  EXPECT_EQ(*extension_info.FindStringByDottedPath("path"),
            unpacked_path.AsUTF8Unsafe());
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       TriggerActionShowsSidePanel) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
  ExtensionTestMessageListener activated_listener("running");
  scoped_refptr<const extensions::Extension> extension =
      InstallExtensionFromPath("side_panel_action");
  ASSERT_TRUE(activated_listener.WaitUntilSatisfied());

  extensions::ResultCatcher result_catcher;
  scoped_refptr<content::DevToolsAgentHost> page_host =
      content::DevToolsAgentHost::GetOrCreateForTab(
          browser()->tab_strip_model()->GetActiveWebContents());
  base::DictValue trigger_extension_params;
  trigger_extension_params.Set("id", extension->id());
  trigger_extension_params.Set("targetId", page_host->GetId());
  const base::DictValue* trigger_result = SendCommandSync(
      "Extensions.triggerAction", std::move(trigger_extension_params));
  ASSERT_TRUE(trigger_result);

  EXPECT_FALSE(trigger_result->FindDict("error"));
  EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();

  SidePanelUI* side_panel_ui = browser()->GetFeatures().side_panel_ui();
  ASSERT_TRUE(side_panel_ui);
  EXPECT_TRUE(side_panel_ui->IsSidePanelEntryShowing(
      SidePanelEntry::Key(SidePanelEntry::Id::kExtension, extension->id())));
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       TriggerActionShowsPopup) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));

  extensions::ResultCatcher result_catcher;
  scoped_refptr<const extensions::Extension> extension =
      InstallExtensionFromPath("popup_action");

  scoped_refptr<content::DevToolsAgentHost> page_host =
      content::DevToolsAgentHost::GetOrCreateForTab(
          browser()->tab_strip_model()->GetActiveWebContents());
  base::DictValue trigger_extension_params;
  trigger_extension_params.Set("id", extension->id());
  trigger_extension_params.Set("targetId", page_host->GetId());
  const base::DictValue* trigger_result = SendCommandSync(
      "Extensions.triggerAction", std::move(trigger_extension_params));
  ASSERT_TRUE(trigger_result);

  EXPECT_FALSE(trigger_result->FindDict("error"));
  EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();

  BrowserWindowInterface* bwi =
      extensions::browser_window_util::GetBrowserForTabContents(
          *browser()->tab_strip_model()->GetActiveWebContents());
  auto* extensions_container = ExtensionsContainer::From(*bwi);

  ASSERT_TRUE(extensions_container);
  auto* action_view = extensions_container->GetActionForId(extension->id());
  ASSERT_TRUE(action_view);
  EXPECT_TRUE(action_view->IsShowingPopup());
}

IN_PROC_BROWSER_TEST_F(DevToolsExtensionsProtocolTest,
                       TriggerActionDispatchesEvent) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));

  extensions::ResultCatcher result_catcher;

  scoped_refptr<const extensions::Extension> extension =
      InstallExtensionFromPath("on_clicked_action");

  scoped_refptr<content::DevToolsAgentHost> page_host =
      content::DevToolsAgentHost::GetOrCreateForTab(
          browser()->tab_strip_model()->GetActiveWebContents());

  base::DictValue trigger_extension_params;
  trigger_extension_params.Set("id", extension->id());
  trigger_extension_params.Set("targetId", page_host->GetId());
  const base::DictValue* trigger_result = SendCommandSync(
      "Extensions.triggerAction", std::move(trigger_extension_params));
  ASSERT_TRUE(trigger_result);

  EXPECT_FALSE(trigger_result->FindDict("error"));
  EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}  // namespace
