// Copyright 2012 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/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/values_test_util.h"
#include "build/build_config.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/extensions/api/developer_private/developer_private_functions.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_list/tab_list_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/service_worker_test_helpers.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/offscreen_document_host.h"
#include "extensions/browser/permissions/permissions_updater.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

#if !BUILDFLAG(IS_ANDROID)
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/dialog_delegate.h"
#endif  // !BUILDFLAG(IS_ANDROID)

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

class DeveloperPrivateApiTest : public ExtensionApiTest {
 protected:
  std::optional<api::developer_private::ExtensionInfo> GetExtensionInfo(
      const Extension& extension) {
    auto get_info_function =
        base::MakeRefCounted<api::DeveloperPrivateGetExtensionInfoFunction>();
    std::optional<base::Value> result =
        api_test_utils::RunFunctionAndReturnSingleResult(
            get_info_function.get(),
            content::JsReplace(R"([$1])", extension.id()), profile());
    if (!result) {
      ADD_FAILURE() << "No result back when getting extension info";
      return std::nullopt;
    }
    std::optional<api::developer_private::ExtensionInfo> info =
        api::developer_private::ExtensionInfo::FromValue(*result);
    if (!info) {
      ADD_FAILURE() << "Problem creating ExtensionInfo from result data";
    }
    return info;
  }
};

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
// Tests opening the developer tools for an app window.
IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectAppWindowView) {
  base::FilePath dir;
  base::PathService::Get(chrome::DIR_TEST_DATA, &dir);
  dir = dir.AppendASCII("extensions")
            .AppendASCII("platform_apps")
            .AppendASCII("minimal");

  // Load and launch a platform app.
  const Extension* app = LoadAndLaunchApp(dir);

  // Get the info about the app, including the inspectable views.
  auto info = GetExtensionInfo(*app);

  // There should be two inspectable views - the background page and the app
  // window.  Find the app window.
  ASSERT_EQ(2u, info->views.size());
  const api::developer_private::ExtensionView* window_view = nullptr;
  for (const auto& view : info->views) {
    if (view.type == api::developer_private::ViewType::kAppWindow) {
      window_view = &view;
      break;
    }
  }
  ASSERT_TRUE(window_view);

  // Inspect the app window.
  auto function =
      base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
  api_test_utils::RunFunction(
      function.get(),
      base::StringPrintf("[{\"renderViewId\": %d, \"renderProcessId\": %d}]",
                         window_view->render_view_id,
                         window_view->render_process_id),
      profile());

  // Verify that dev tools opened.
  std::list<raw_ptr<AppWindow, CtnExperimental>> app_windows =
      AppWindowRegistry::Get(profile())->GetAppWindowsForApp(app->id());
  ASSERT_EQ(1u, app_windows.size());
  EXPECT_TRUE(DevToolsWindow::GetInstanceForInspectedWebContents(
      (*app_windows.begin())->web_contents()));
}
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectEmbeddedOptionsPage) {
  base::FilePath dir;
  base::PathService::Get(chrome::DIR_TEST_DATA, &dir);
  // Load an extension that only has an embedded options_ui page.
  const Extension* extension = LoadExtension(dir.AppendASCII("extensions")
                                                 .AppendASCII("delayed_install")
                                                 .AppendASCII("v1"));
  ASSERT_TRUE(extension);

  // Open the embedded options page.
  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  ASSERT_TRUE(ExtensionTabUtil::OpenOptionsPageFromWebContents(extension,
                                                               web_contents));
  WaitForExtensionNotIdle(extension->id());

  // On Android, the option page will be opened in a new tab as the guest view
  // is not enabled on Android yet.
#if BUILDFLAG(IS_ANDROID)
  web_contents = chrome_test_utils::GetActiveWebContents(this);
  ASSERT_EQ("chrome-extension://" + extension->id() + "/popup.html",
            web_contents->GetURL());
#else
  // Get the info about the extension, including the inspectable views.
  auto info = GetExtensionInfo(*extension);

  // The embedded options page should show up.
  ASSERT_EQ(1u, info->views.size());
  const api::developer_private::ExtensionView& view = info->views[0];
  ASSERT_EQ(api::developer_private::ViewType::kExtensionGuest, view.type);

  // Inspect the embedded options page.
  auto function =
      base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
  api_test_utils::RunFunction(
      function.get(),
      base::StringPrintf("[{\"renderViewId\": %d, \"renderProcessId\": %d}]",
                         view.render_view_id, view.render_process_id),
      profile());

  // Verify that dev tools opened.
  content::RenderFrameHost* render_frame_host =
      content::RenderFrameHost::FromID(view.render_process_id,
                                       view.render_view_id);
  ASSERT_TRUE(render_frame_host);
  content::WebContents* wc =
      content::WebContents::FromRenderFrameHost(render_frame_host);
  ASSERT_TRUE(wc);
  EXPECT_TRUE(DevToolsWindow::GetInstanceForInspectedWebContents(wc));
#endif
}

// TODO(crbug.com/40273479): Test is flaky on MSan builders.
#if defined(MEMORY_SANITIZER)
#define MAYBE_InspectInactiveServiceWorkerBackground \
  DISABLED_InspectInactiveServiceWorkerBackground
#else
#define MAYBE_InspectInactiveServiceWorkerBackground \
  InspectInactiveServiceWorkerBackground
#endif
IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest,
                       MAYBE_InspectInactiveServiceWorkerBackground) {
  ResultCatcher result_catcher;
  // Load an extension that is service worker-based.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("service_worker")
                        .AppendASCII("worker_based_background")
                        .AppendASCII("inspect"),
                    // Wait for the registration to be stored since we'll stop
                    // the worker.
                    {.wait_for_registration_stored = true});
  ASSERT_TRUE(extension);
  ASSERT_TRUE(result_catcher.GetNextResult());

  // Stop the service worker.
  browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(),
                                                             extension->id());

  // Get the info about the extension, including the inspectable views.
  auto info = GetExtensionInfo(*extension);

  // There should be a worker based background for the extension.
  ASSERT_EQ(1u, info->views.size());
  const api::developer_private::ExtensionView& view = info->views[0];
  EXPECT_EQ(api::developer_private::ViewType::kExtensionServiceWorkerBackground,
            view.type);
  // The service worker should be inactive (indicated by -1 for
  // the process id).
  EXPECT_EQ(-1, view.render_process_id);

  // Inspect the inactive service worker background.
  DevToolsWindowCreationObserver devtools_window_created_observer;
  auto dev_tools_function =
      base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
  api_test_utils::RunFunction(dev_tools_function.get(),
                              base::StringPrintf(
                                  R"([{"renderViewId": -1,
                                                      "renderProcessId": -1,
                                                      "isServiceWorker": true,
                                                      "extensionId": "%s"
                                                   }])",
                                  extension->id().c_str()),
                              profile());
  devtools_window_created_observer.WaitForLoad();

  // Verify that dev tool window opened.
  scoped_refptr<content::DevToolsAgentHost> service_worker_host;
  content::DevToolsAgentHost::List targets =
      content::DevToolsAgentHost::GetOrCreateAll();
  for (const scoped_refptr<content::DevToolsAgentHost>& host : targets) {
    if (host->GetType() == content::DevToolsAgentHost::kTypeServiceWorker &&
        host->GetURL() ==
            BackgroundInfo::GetBackgroundServiceWorkerScriptURL(extension)) {
      EXPECT_FALSE(service_worker_host);
      service_worker_host = host;
    }
  }

  ASSERT_TRUE(service_worker_host);
  EXPECT_TRUE(DevToolsWindow::FindDevToolsWindow(service_worker_host.get()));
}

IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest,
                       InspectActiveServiceWorkerBackground) {
  ResultCatcher result_catcher;
  // Load an extension that is service worker based.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("service_worker")
                        .AppendASCII("worker_based_background")
                        .AppendASCII("inspect"));
  ASSERT_TRUE(extension);
  ASSERT_TRUE(result_catcher.GetNextResult());

  // Get the info about the extension, including the inspectable views.
  auto info = GetExtensionInfo(*extension);

  // There should be a worker based background for the extension.
  ASSERT_EQ(1u, info->views.size());
  const api::developer_private::ExtensionView& view = info->views[0];
  EXPECT_EQ(api::developer_private::ViewType::kExtensionServiceWorkerBackground,
            view.type);
  EXPECT_NE(-1, view.render_process_id);

  // Inspect the service worker page.
  auto dev_tools_function =
      base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
  api_test_utils::RunFunction(
      dev_tools_function.get(),
      base::StringPrintf(
          R"([{"renderViewId": -1,
               "renderProcessId": %d,
               "isServiceWorker": true,
               "extensionId": "%s"
            }])",
          info->views[0].render_process_id, extension->id().c_str()),
      profile());

  // Find the service worker background host.
  content::DevToolsAgentHost::List targets =
      content::DevToolsAgentHost::GetOrCreateAll();
  scoped_refptr<content::DevToolsAgentHost> service_worker_host;
  for (const scoped_refptr<content::DevToolsAgentHost>& host : targets) {
    if (host->GetType() == content::DevToolsAgentHost::kTypeServiceWorker &&
        host->GetURL() ==
            BackgroundInfo::GetBackgroundServiceWorkerScriptURL(extension)) {
      EXPECT_FALSE(service_worker_host);
      service_worker_host = host;
    }
  }

  // Verify that dev tools opened.
  ASSERT_TRUE(service_worker_host);
  EXPECT_TRUE(DevToolsWindow::FindDevToolsWindow(service_worker_host.get()));
}

// TODO(crbug.com/40882269): The test is flaky on MSAN and Linux. Re-enable it.
#if defined(MEMORY_SANITIZER) || BUILDFLAG(IS_LINUX)
#define MAYBE_InspectSplitModeServiceWorkerBackgrounds \
  DISABLED_InspectSplitModeServiceWorkerBackgrounds
#else
#define MAYBE_InspectSplitModeServiceWorkerBackgrounds \
  InspectSplitModeServiceWorkerBackgrounds
#endif
IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest,
                       MAYBE_InspectSplitModeServiceWorkerBackgrounds) {
  ResultCatcher result_catcher;
  // Load an extension that is service worker based, split mode and enabled in
  // incognito.
  static constexpr char kManifest[] =
      R"({
           "name": "Split mode worker test",
           "manifest_version": 3,
           "version": "0.1",
           "background": {"service_worker": "worker.js"},
           "incognito": "split"
         })";
  TestExtensionDir test_dir;
  test_dir.WriteManifest(kManifest);
  test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"),
                     "chrome.test.notifyPass();");
  const Extension* extension =
      LoadExtension(test_dir.UnpackedPath(), {.allow_in_incognito = true});
  ASSERT_TRUE(extension);
  ASSERT_TRUE(result_catcher.GetNextResult());

  // Get the info about the extension, including the inspectable views.
  auto info = GetExtensionInfo(*extension);

  // With no incognito window open, there should be a single worker based
  // background page for the extension.
  ASSERT_EQ(1u, info->views.size());
  int main_render_process_id = -1;
  {
    const api::developer_private::ExtensionView& view = info->views[0];
    EXPECT_EQ(
        api::developer_private::ViewType::kExtensionServiceWorkerBackground,
        view.type);
    EXPECT_NE(-1, view.render_process_id);
    main_render_process_id = view.render_process_id;
    EXPECT_FALSE(view.incognito);
  }

  // Now open up an incognito browser window page and check the inspectable
  // views again. Waiting for the result catcher will wait for the incognito
  // service worker to have become active.
  content::WebContents* const incognito_window =
      PlatformOpenURLOffTheRecord(profile(), GURL("about:blank"));
  ASSERT_TRUE(incognito_window);
  ASSERT_TRUE(result_catcher.GetNextResult());
  info = GetExtensionInfo(*extension);
  // The views should now have 2 entries, one for the main worker which will be
  // the same as before and a new one for the incognito worker.
  ASSERT_EQ(2u, info->views.size());
  EXPECT_NE(info->views[0].incognito, info->views[1].incognito);
  int incognito_render_process_id = -1;
  for (auto& view : info->views) {
    EXPECT_EQ(
        api::developer_private::ViewType::kExtensionServiceWorkerBackground,
        view.type);
    EXPECT_NE(-1, view.render_process_id);
    if (view.incognito) {
      EXPECT_NE(main_render_process_id, view.render_process_id);
      incognito_render_process_id = view.render_process_id;
    } else {
      EXPECT_EQ(main_render_process_id, view.render_process_id);
    }
  }

  // Open a devtools window for both the primary and incognito worker.
  std::string kOpenDevToolsParams =
      R"([{"renderViewId": -1,
           "renderProcessId": $1,
           "isServiceWorker": true,
           "extensionId": $2,
           "incognito": $3
         }])";
  DevToolsWindow* main_devtools_window = nullptr;
  {
    DevToolsWindowCreationObserver devtools_window_created_observer;
    auto dev_tools_function =
        base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
    api_test_utils::RunFunction(
        dev_tools_function.get(),
        content::JsReplace(kOpenDevToolsParams, main_render_process_id,
                           extension->id().c_str(), /*incognito:*/ false),
        profile());
    devtools_window_created_observer.WaitForLoad();
    main_devtools_window = devtools_window_created_observer.devtools_window();
  }
  DevToolsWindow* incognito_devtools_window = nullptr;
  {
    DevToolsWindowCreationObserver devtools_window_created_observer;
    auto dev_tools_function =
        base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
    api_test_utils::RunFunction(
        dev_tools_function.get(),
        content::JsReplace(kOpenDevToolsParams, incognito_render_process_id,
                           extension->id().c_str(), /*incognito:*/ true),
        profile());
    devtools_window_created_observer.WaitForLoad();
    incognito_devtools_window =
        devtools_window_created_observer.devtools_window();
  }

  // Both windows should have opened and should not point to the same window.
  ASSERT_TRUE(main_devtools_window);
  ASSERT_TRUE(incognito_devtools_window);
  ASSERT_NE(main_devtools_window, incognito_devtools_window);
}

// Test that offscreen documents show up in the list of inspectable views and
// can be inspected.
IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectOffscreenDocument) {
  static constexpr char kManifest[] =
      R"({
           "name": "Offscreen Document Test",
           "manifest_version": 3,
           "version": "0.1"
         })";
  TestExtensionDir test_dir;
  test_dir.WriteManifest(kManifest);
  test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
                     "<html>offscreen</html>");

  const Extension* extension = LoadExtension(test_dir.UnpackedPath());

  // Create an offscreen document and wait for it to load.
  std::unique_ptr<OffscreenDocumentHost> offscreen_document;
  GURL offscreen_url = extension->GetResourceURL("offscreen.html");
  {
    ExtensionHostTestHelper offscreen_waiter(profile(), extension->id());
    offscreen_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument);
    offscreen_document = std::make_unique<OffscreenDocumentHost>(
        *extension,
        profile(), offscreen_url);
    offscreen_document->CreateRendererSoon();
    offscreen_waiter.WaitForHostCompletedFirstLoad();
  }

  // Get the list of inspectable views for the extension.
  auto info = GetExtensionInfo(*extension);

  // The only inspectable view should be the offscreen document. Validate the
  // metadata.
  ASSERT_EQ(1u, info->views.size());
  const api::developer_private::ExtensionView& view = info->views[0];
  EXPECT_EQ(api::developer_private::ViewType::kOffscreenDocument, view.type);
  content::WebContents* offscreen_contents =
      offscreen_document->host_contents();
  EXPECT_EQ(offscreen_url.spec(), view.url);
  EXPECT_EQ(offscreen_document->render_process_host()->GetDeprecatedID(),
            view.render_process_id);
  EXPECT_EQ(offscreen_contents->GetPrimaryMainFrame()->GetRoutingID(),
            view.render_view_id);
  EXPECT_FALSE(view.incognito);
  EXPECT_FALSE(view.is_iframe);

  // The document shouldn't currently be under inspection.
  EXPECT_FALSE(
      DevToolsWindow::GetInstanceForInspectedWebContents(offscreen_contents));

  // Call the API function to inspect the offscreen document.
  auto dev_tools_function =
      base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
  api_test_utils::RunFunction(
      dev_tools_function.get(),
      content::JsReplace(
          R"([{"renderViewId": $1,
               "renderProcessId": $2,
               "extensionId": $3
            }])",
          view.render_view_id, view.render_process_id, extension->id()),
      profile());

  // Validate that the devtools window is now shown.
  DevToolsWindow* dev_tools_window =
      DevToolsWindow::GetInstanceForInspectedWebContents(offscreen_contents);
  ASSERT_TRUE(dev_tools_window);

  // Tidy up.
  DevToolsWindowTesting::CloseDevToolsWindowSync(dev_tools_window);
}

IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, UninstallMultipleExtensions) {
  // Load first extension.
  static constexpr char kManifest_0[] =
      R"({
           "name": "Multiple extensions uninstall test 0",
           "manifest_version": 3,
           "version": "0.1"
         })";
  TestExtensionDir test_dir_0;
  test_dir_0.WriteManifest(kManifest_0);
  const Extension* extension_0 = LoadExtension(test_dir_0.UnpackedPath());
  ASSERT_TRUE(extension_0);
  std::string extension_0_id = extension_0->id();

  // Load second extension.
  static constexpr char kManifest_1[] =
      R"({
           "name": "Multiple extensions uninstall test 1",
           "manifest_version": 3,
           "version": "0.1"
         })";
  TestExtensionDir test_dir_1;
  test_dir_1.WriteManifest(kManifest_1);
  const Extension* extension_1 = LoadExtension(test_dir_1.UnpackedPath());
  ASSERT_TRUE(extension_1);
  std::string extension_1_id = extension_1->id();

  auto function = base::MakeRefCounted<
      api::DeveloperPrivateRemoveMultipleExtensionsFunction>();
  std::unique_ptr<ExtensionFunctionDispatcher> dispatcher =
      std::make_unique<ExtensionFunctionDispatcher>(profile());
  function->SetDispatcher(dispatcher->AsWeakPtr());

  std::string args =
      base::StrCat({"[[\"", extension_0_id, "\", \"", extension_1_id, "\"]]"});
  api_test_utils::SendResponseHelper response_helper(function.get());
  function->SetArgs(base::test::ParseJsonList(args));
  function->accept_bubble_for_testing(true);
  function->RunWithValidation().Execute();
  response_helper.WaitForResponse();

  // Verify the extensions are uninstalled.
  EXPECT_FALSE(extension_registry()->GetExtensionById(
      extension_0_id, ExtensionRegistry::EVERYTHING));
  EXPECT_FALSE(extension_registry()->GetExtensionById(
      extension_1_id, ExtensionRegistry::EVERYTHING));
}

class DeveloperPrivateApiRateExtensionTest : public DeveloperPrivateApiTest {
 private:
  base::test::ScopedFeatureList feature_list_{
      extensions_features::kCWSReviewPromptingNativeUI};
};

IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiRateExtensionTest,
                       OpenReviewPage_NavigatesToCWS) {
  scoped_refptr<const Extension> cws_extension =
      ExtensionBuilder("CWS Extension")
          .SetLocation(mojom::ManifestLocation::kInternal)
          .AddFlags(Extension::FROM_WEBSTORE)
          .Build();
  PermissionsUpdater updater(profile());
  updater.InitializePermissions(cws_extension.get());
  updater.GrantActivePermissions(cws_extension.get());
  extension_registrar()->AddExtension(cws_extension.get());

  base::DictValue cws_info_dict;
  cws_info_dict.Set("is-present", true);
  cws_info_dict.Set("is-live", true);
  cws_info_dict.Set("violation-type", 0);
  ExtensionPrefs::Get(profile())->UpdateExtensionPref(
      cws_extension->id(), "cws-info", base::Value(std::move(cws_info_dict)));

  content::WebContents* web_contents = GetActiveWebContents();
  ASSERT_TRUE(web_contents);

  TabListInterface* tab_list =
      TabListInterface::From(browser_window_interface());
  ASSERT_TRUE(tab_list);
  int initial_tab_count = tab_list->GetTabCount();

  GURL expected_url = extensions::util::GetCWSWritingReviewUrl(
      cws_extension->id(), extensions::util::CWSReviewSource::kExtensionsPage);

  content::TestNavigationObserver observer(expected_url);
  observer.StartWatchingNewWebContents();

  auto function =
      base::MakeRefCounted<api::DeveloperPrivateOpenReviewPageFunction>();
  function->SetRenderFrameHost(web_contents->GetPrimaryMainFrame());

  std::string args =
      base::StringPrintf(R"(["%s"])", cws_extension->id().c_str());
  EXPECT_TRUE(api_test_utils::RunFunction(function.get(), args, profile()));

  observer.Wait();

  EXPECT_EQ(initial_tab_count + 1, tab_list->GetTabCount());
  content::WebContents* new_tab = tab_list->GetActiveTab()->GetContents();
  EXPECT_NE(web_contents, new_tab);

  EXPECT_EQ(expected_url, new_tab->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiRateExtensionTest,
                       OpenReviewPage_IncognitoIneligible) {
  content::WebContents* incognito_contents =
      PlatformOpenURLOffTheRecord(profile(), GURL("about:blank"));
  ASSERT_TRUE(incognito_contents);

  Profile* incognito_profile =
      Profile::FromBrowserContext(incognito_contents->GetBrowserContext());

  scoped_refptr<const Extension> cws_extension =
      ExtensionBuilder("CWS Extension")
          .SetLocation(mojom::ManifestLocation::kInternal)
          .AddFlags(Extension::FROM_WEBSTORE)
          .Build();
  PermissionsUpdater updater(profile());
  updater.InitializePermissions(cws_extension.get());
  updater.GrantActivePermissions(cws_extension.get());
  extension_registrar()->AddExtension(cws_extension.get());

  auto function =
      base::MakeRefCounted<api::DeveloperPrivateOpenReviewPageFunction>();
  function->SetRenderFrameHost(incognito_contents->GetPrimaryMainFrame());

  std::string args =
      base::StringPrintf(R"(["%s"])", cws_extension->id().c_str());
  std::string error = api_test_utils::RunFunctionAndReturnError(
      function.get(), args, incognito_profile);
  EXPECT_EQ("The extension is ineligible for review prompts.", error);
}

IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiRateExtensionTest,
                       ExtensionsUI_CwsReviewPromptingEnabled) {
  static constexpr char kScript[] =
      "import('chrome://resources/js/load_time_data.js').then(m => "
      "m.loadTimeData.getBoolean('cwsReviewPromptingEnabled'))";

  content::WebContents* web_contents = GetActiveWebContents();
  ASSERT_TRUE(web_contents);

  // 1. Regular profile with policy allowed: cwsReviewPromptingEnabled is true.
  ASSERT_TRUE(
      content::NavigateToURL(web_contents, GURL("chrome://extensions")));
  EXPECT_EQ(true, content::EvalJs(web_contents, kScript));

  // 2. Enterprise policy disabled: cwsReviewPromptingEnabled is false.
  profile()->GetPrefs()->SetBoolean(prefs::kExtensionReviewPromptsAllowed,
                                    false);
  ASSERT_TRUE(
      content::NavigateToURL(web_contents, GURL("chrome://extensions")));
  EXPECT_EQ(false, content::EvalJs(web_contents, kScript));
}

}  // namespace extensions
