// 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 <stddef.h>

#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_browsertest.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/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/crx_installer.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using base::test::TestFuture;

namespace extensions {

class ExtensionFunctionalTest : public ExtensionBrowserTest {
 public:
  void InstallExtensionSilently(const char* filename) {
    ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
    size_t num_before = registry->enabled_extensions().size();

    base::FilePath path = test_data_dir_.AppendASCII(filename);

    TestExtensionRegistryObserver extension_observer(registry);

    scoped_refptr<CrxInstaller> installer(
        CrxInstaller::CreateSilent(profile()));
    installer->set_is_gallery_install(false);
    installer->set_allow_silent_install(true);
    installer->set_install_source(mojom::ManifestLocation::kInternal);
    installer->set_off_store_install_allow_reason(
        CrxInstaller::OffStoreInstallAllowedInTest);

    TestFuture<std::optional<CrxInstallError>> installer_done_future;
    installer->AddInstallerCallback(
        installer_done_future
            .GetCallback<const std::optional<CrxInstallError>&>());
    installer->InstallCrx(path);

    const std::optional<CrxInstallError>& error = installer_done_future.Get();
    EXPECT_FALSE(error);

    size_t num_after = registry->enabled_extensions().size();
    EXPECT_EQ(num_before + 1, num_after);

    extension_observer.WaitForExtensionLoaded();
    const Extension* extension =
        registry->enabled_extensions().GetByID(last_loaded_extension_id());
    EXPECT_TRUE(extension);
  }
};

IN_PROC_BROWSER_TEST_F(ExtensionFunctionalTest, TestSetExtensionsState) {
  InstallExtensionSilently("google_talk.crx");

  // Disable the extension and verify.
  util::SetIsIncognitoEnabled(last_loaded_extension_id(), profile(), false);
  auto* registrar = ExtensionRegistrar::Get(profile());
  registrar->DisableExtension(last_loaded_extension_id(),
                              {disable_reason::DISABLE_USER_ACTION});
  EXPECT_FALSE(registrar->IsExtensionEnabled(last_loaded_extension_id()));

  // Enable the extension and verify.
  util::SetIsIncognitoEnabled(last_loaded_extension_id(), profile(), false);
  registrar->EnableExtension(last_loaded_extension_id());
  EXPECT_TRUE(registrar->IsExtensionEnabled(last_loaded_extension_id()));

  // Allow extension in incognito mode and verify.
  registrar->EnableExtension(last_loaded_extension_id());
  util::SetIsIncognitoEnabled(last_loaded_extension_id(), profile(), true);
  EXPECT_TRUE(util::IsIncognitoEnabled(last_loaded_extension_id(), profile()));

  // Disallow extension in incognito mode and verify.
  registrar->EnableExtension(last_loaded_extension_id());
  util::SetIsIncognitoEnabled(last_loaded_extension_id(), profile(), false);
  EXPECT_FALSE(util::IsIncognitoEnabled(last_loaded_extension_id(), profile()));
}

IN_PROC_BROWSER_TEST_F(ExtensionFunctionalTest,
                       FindingUnrelatedExtensionFramesFromAboutBlank) {
  // Load an extension before adding tabs.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("simple_with_file"));
  ASSERT_TRUE(extension);
  GURL extension_url = extension->GetResourceURL("file.html");

  // Load the extension in two unrelated tabs.
  ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), extension_url));
  NavigateToURLInNewTab(extension_url);

  // Sanity-check test setup: 2 frames share a renderer process, but are not in
  // a related browsing instance.
  TabListInterface* tab_list = GetTabListInterface();
  content::RenderFrameHost* tab1 =
      tab_list->GetTab(0)->GetContents()->GetPrimaryMainFrame();
  content::RenderFrameHost* tab2 =
      tab_list->GetTab(1)->GetContents()->GetPrimaryMainFrame();
  EXPECT_EQ(tab1->GetProcess(), tab2->GetProcess());
  EXPECT_FALSE(
      tab1->GetSiteInstance()->IsRelatedSiteInstance(tab2->GetSiteInstance()));

  // Name the 2 frames.
  EXPECT_TRUE(content::ExecJs(tab1, "window.name = 'tab1';"));
  EXPECT_TRUE(content::ExecJs(tab2, "window.name = 'tab2';"));

  // Open a new window from tab1 and store it in tab1_popup.
  content::RenderFrameHost* tab1_popup = nullptr;
  {
    content::WebContentsAddedObserver new_window_observer;
    ASSERT_EQ(true, EvalJs(tab1, "!!window.open('about:blank', 'new_popup');"));
    content::WebContents* popup_window = new_window_observer.GetWebContents();
    EXPECT_TRUE(WaitForLoadStop(popup_window));
    tab1_popup = popup_window->GetPrimaryMainFrame();
  }
  EXPECT_EQ(GURL(url::kAboutBlankURL), tab1_popup->GetLastCommittedURL());

  // Verify that |tab1_popup| can find unrelated frames from the same extension
  // (i.e. that it can find |tab2|.
  std::string location_of_opened_window =
      EvalJs(tab1_popup,
             "var w = window.open('', 'tab2');\n"
             "w.location.href;")
          .ExtractString();
  EXPECT_EQ(tab2->GetLastCommittedURL(), location_of_opened_window);
}

IN_PROC_BROWSER_TEST_F(ExtensionFunctionalTest, DownloadExtensionResource) {
  auto* download_manager = profile()->GetDownloadManager();
  content::DownloadTestObserverTerminal download_observer(
      download_manager, 1,
      content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT);
  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("download")));
  download_observer.WaitForFinished();

  std::vector<raw_ptr<download::DownloadItem, VectorExperimental>>
      download_items;
  download_manager->GetAllDownloads(&download_items);

  base::ScopedAllowBlockingForTesting allow_blocking;
  auto file_path = download_items[0]->GetTargetFilePath();

  base::FilePath expected_path = chrome_test_utils::GetTestFilePath(
      base::FilePath(),
      base::FilePath().AppendASCII("extensions/download/download.dat"));

  std::string actual_contents, expected_contents;
  ASSERT_TRUE(base::ReadFileToString(file_path, &actual_contents));
  ASSERT_TRUE(base::ReadFileToString(expected_path, &expected_contents));
  ASSERT_EQ(expected_contents, actual_contents);
}

}  // namespace extensions
