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

#include <algorithm>
#include <string>
#include <string_view>
#include <vector>

#include "base/containers/to_vector.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/extensions/api/permissions/permissions_api.h"
#include "chrome/browser/extensions/api/tabs/tabs_api.h"
#include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_management_test_util.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/embedder_support/switches.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/console_message.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/service_worker_context_observer.h"
#include "content/public/browser/shared_cors_origin_access_list.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/resource_load_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/permissions/active_tab_permission_granter.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/browser/service_worker/service_worker_test_utils.h"
#include "extensions/browser/url_loader_factory_manager.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/permissions_manager_waiter.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "services/network/public/cpp/cors/origin_access_list.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/network_context.mojom-shared.h"
#include "services/network/public/mojom/trust_tokens.mojom-shared.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/apps/app_service/chrome_app_deprecation/chrome_app_deprecation.h"
#endif

namespace extensions {

namespace {

const char kCorsErrorWhenFetching[] = "error: TypeError: Failed to fetch";

// This is the public key of tools/origin_trials/eftest.key, used to validate
// origin trial tokens generated by tools/origin_trials/generate_token.py.
constexpr char kOriginTrialPublicKeyForTesting[] =
    "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";

std::string CreateFetchScript(
    const GURL& resource,
    std::optional<base::DictValue> request_init = std::nullopt) {
  const char kFetchScriptTemplate[] = R"(
    fetch($1, $2)
      .then(response => response.text())
      .then(text => domAutomationController.send(text))
      .catch(err => domAutomationController.send('error: ' + err));
  )";
  return content::JsReplace(
      kFetchScriptTemplate, resource,
      request_init ? std::move(*request_init) : base::DictValue());
}

std::string PopString(content::DOMMessageQueue* message_queue) {
  std::string json;
  EXPECT_TRUE(message_queue->WaitForMessage(&json));
  std::optional<base::Value> value =
      base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS);
  EXPECT_TRUE(value->is_string());
  return value->GetString();
}

}  // namespace

using ::testing::HasSubstr;

class OrbAndCorsExtensionTestBase : public ExtensionBrowserTest {
 public:
  OrbAndCorsExtensionTestBase() = default;

  void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
    InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
    command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
                                    kOriginTrialPublicKeyForTesting);
  }

  void SetUpOnMainThread() override {
    ExtensionBrowserTest::SetUpOnMainThread();

    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
  }

 protected:
  TestExtensionDir dir_;
};

class ServiceWorkerConsoleObserver
    : public content::ServiceWorkerContextObserver {
 public:
  explicit ServiceWorkerConsoleObserver(
      content::BrowserContext* browser_context) {
    scoped_observation_.Observe(
        service_worker_test_utils::GetServiceWorkerContext(browser_context));
  }
  ~ServiceWorkerConsoleObserver() override = default;

  ServiceWorkerConsoleObserver(const ServiceWorkerConsoleObserver&) = delete;
  ServiceWorkerConsoleObserver& operator=(const ServiceWorkerConsoleObserver&) =
      delete;

  using Message = content::ConsoleMessage;
  const std::vector<Message>& messages() const { return messages_; }

  void WaitForMessages() { run_loop_.Run(); }

 private:
  // ServiceWorkerContextObserver:
  void OnReportConsoleMessage(int64_t version_id,
                              const GURL& scope,
                              const Message& message) override {
    messages_.push_back(message);
    run_loop_.Quit();
  }

  base::RunLoop run_loop_;
  std::vector<Message> messages_;
  base::ScopedObservation<content::ServiceWorkerContext,
                          content::ServiceWorkerContextObserver>
      scoped_observation_{this};
};

class OrbAndCorsExtensionBrowserTest : public OrbAndCorsExtensionTestBase {
 public:
  OrbAndCorsExtensionBrowserTest() = default;

  OrbAndCorsExtensionBrowserTest(const OrbAndCorsExtensionBrowserTest&) =
      delete;
  OrbAndCorsExtensionBrowserTest& operator=(
      const OrbAndCorsExtensionBrowserTest&) = delete;

  void SetUpInProcessBrowserTestFixture() override {
    policy_provider_.SetDefaultReturns(
        /*is_initialization_complete_return=*/true,
        /*is_first_policy_load_complete_return=*/true);
    policy_provider_.SetAutoRefresh();
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
        &policy_provider_);
  }

  const Extension* InstallExtension(
      GURL resource_to_fetch_from_declarative_content_script = GURL()) {
    bool use_declarative_content_script =
        resource_to_fetch_from_declarative_content_script.is_valid();
    const char kContentScriptManifestEntry[] = R"(
          "content_scripts": [{
            "all_frames": true,
            "match_about_blank": true,
            "matches": ["*://fetch-initiator.com/*"],
            "js": ["content_script.js"]
          }],
    )";
    const char kManifestTemplate[] = R"(
        {
          "name": "CrossOriginReadBlockingTest - Extension",
          "version": "1.0",
          "manifest_version": 2,
          "permissions": [
              "activeTab",
              "tabs",
              "*://fetch-initiator.com/*",
              "*://127.0.0.1/*",
              "*://cross-site.com/*",
              "*://*.subdomain.com/*",
              "*://other-with-permission.com/*"
              // This list intentionally does NOT include
              // other-without-permission.com.
          ],
          %s
          "background": {"scripts": ["background_script.js"]},
          "web_accessible_resources": [ "page.html" ]
        } )";
    dir_.WriteManifest(base::StringPrintf(
        kManifestTemplate,
        use_declarative_content_script ? kContentScriptManifestEntry : ""));

    dir_.WriteFile(FILE_PATH_LITERAL("background_script.js"), "");
    dir_.WriteFile(FILE_PATH_LITERAL("page.html"), "<body>Hello World!</body>");

    if (use_declarative_content_script) {
      dir_.WriteFile(
          FILE_PATH_LITERAL("content_script.js"),
          CreateFetchScript(resource_to_fetch_from_declarative_content_script));
    }
    extension_ =
        LoadExtension(dir_.UnpackedPath(), {.allow_in_incognito = true});
    DCHECK(extension_);

    return extension_;
  }

  // Verifies that |console_observer| has captured a console message indicating
  // that CORS has blocked a response.
  //
  // |console_observer| can be either
  // - ServiceWorkerConsoleObserver (defined above in this file)
  // or
  // - content::WebContentsConsoleObserver
  template <typename TConsoleObserver>
  void VerifyFetchWasBlockedByCors(const TConsoleObserver& console_observer) {
    std::vector<std::string> messages = base::ToVector(
        console_observer.messages(), [](const auto& console_message) {
          return base::UTF16ToUTF8(console_message.message);
        });

    // We allow more than 1 console message, because the test might flakily see
    // extra console messages - see https://crbug.com/40693726.
    EXPECT_THAT(messages, testing::Contains(testing::HasSubstr(
                              "has been blocked by CORS policy")));
  }

  // Setup resource load observer. We will frequently be testing against
  // active_web_contents, so we use that as default when no web_contents
  // is passed in.
  void ObserveResourceLoads(content::WebContents* web_contents = nullptr) {
    if (!web_contents) {
      web_contents = active_web_contents();
    }
    resource_load_observer_ =
        std::make_unique<content::ResourceLoadObserver>(web_contents);
  }

  void VerifyFetchWasBlockedByOrb(const GURL& url) {
    // The test must setup resource_load_observer_ for the appropriate web
    // contents before calling this method.
    EXPECT_TRUE(resource_load_observer_);
    resource_load_observer_->WaitForResourceCompletion(url);
    EXPECT_TRUE(resource_load_observer_->GetResource(url));
    EXPECT_TRUE(
        (*resource_load_observer_->GetResource(url))->raw_body_bytes.is_zero());

    // For later versions of ORB the error code lets us determine precisely
    // whether a fetch was blocked by ORB. For "v0.1" and "v0.2" (for
    // JS-initiated fetches), we have to rely on circumstantial evidence (like
    // an empty body, which could have other reasons).
    if (base::FeatureList::IsEnabled(
            network::features::kOpaqueResponseBlockingErrorsForAllFetches)) {
      EXPECT_EQ(net::ERR_BLOCKED_BY_ORB,
                (*resource_load_observer_->GetResource(url))->net_error);
    }
  }

  void VerifyFetchWasAllowedByOrb(const GURL& url) {
    // The test must setup resource_load_observer_ for the appropriate web
    // contents before calling this method.
    EXPECT_TRUE(resource_load_observer_);
    resource_load_observer_->WaitForResourceCompletion(url);
    EXPECT_TRUE(resource_load_observer_->GetResource(url));

    // Non-cors requests may return an opaque response. The ResourceLoadObserver
    // is based on WebContentObserver, which models these as a generic network
    // error (rather than any of the specific network errors). So we accept a
    // resource has having been allowed if the error code is either OK, or
    // the generic FAILED (but no other error state).
    EXPECT_THAT((*resource_load_observer_->GetResource(url))->net_error,
                testing::AnyOf(
                    testing::Eq(net::OK),         // Request passes.
                    testing::Eq(net::ERR_FAILED)  // Non-cors opaque response.
                    ));
  }

  void VerifyOrbWasDisabled(const GURL& url) {
    // If ORB doesn't apply, it should not block any request.
    VerifyFetchWasAllowedByOrb(url);
  }

  // Verifies that fetching a ORB-eligible resource from a content script will
  // be blocked by CORS.
  //
  // This verification helper might not work for non-ORB-eligible resources
  // like MIME types not covered by ORB (e.g. application/octet-stream) or
  // same-origin responses.
  void VerifyOrbEligibleFetchFromContentScript(
      const content::WebContentsConsoleObserver& console_observer,
      const std::string& actual_fetch_result,
      const std::string& expected_fetch_result) {
    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();

    // Verify the fetch was blocked by CORS.
    EXPECT_EQ(kCorsErrorWhenFetching, actual_fetch_result);
    VerifyFetchWasBlockedByCors(console_observer);

    // No verification if the request was blocked by ORB, because
    // 1) once request_initiator is trustworthy, ORB should only
    //    apply to no-cors requests
    // 2) some CORS-blocked requests may not reach ORB/response-started
    //    stage at all (e.g. if CORS blocks a redirect).

    // TODO(lukasza): Verify that the request was made in CORS mode (e.g.
    // included an Origin header).
  }

  void VerifyNonOrbElligibleFetchFromContentScript(
      const GURL& url,
      const content::WebContentsConsoleObserver& console_observer,
      const std::string& actual_fetch_result,
      const std::string& expected_fetch_result_prefix) {
    // Verify that ORB allowed the response.
    VerifyFetchWasAllowedByOrb(url);

    // Verify that the response body was blocked by CORS.
    EXPECT_EQ(kCorsErrorWhenFetching, actual_fetch_result);
    VerifyFetchWasBlockedByCors(console_observer);
  }

  content::WebContents* active_web_contents() { return GetActiveWebContents(); }

  const Extension* InstallExtensionWithManifest(std::string_view manifest) {
    dir_.WriteManifest(manifest);
    dir_.WriteFile(FILE_PATH_LITERAL("background_script.js"), "");
    dir_.WriteFile(FILE_PATH_LITERAL("page.html"), "");

    extension_ = LoadExtension(dir_.UnpackedPath());
    return extension_;
  }

  const Extension* InstallExtensionWithPermissionToAllUrls(
      bool enable_file_access = false) {
    const char kManifestTemplate[] = R"(
        {
          "name": "CrossOriginReadBlockingTest - Extension/AllUrls",
          "version": "1.0",
          "manifest_version": 2,
          "permissions": [ "tabs", "<all_urls>" ],
          "background": {"scripts": ["background_script.js"]}
        } )";
    dir_.WriteManifest(kManifestTemplate);
    dir_.WriteFile(FILE_PATH_LITERAL("background_script.js"), "");
    extension_ = LoadExtension(dir_.UnpackedPath(),
                               {.allow_file_access = enable_file_access});
    DCHECK(extension_);

    return extension_;
  }

  bool RegisterServiceWorkerForExtension(
      const std::string& service_worker_script) {
    static constexpr char kServiceWorkerPath[] = "service_worker.js";
    dir_.WriteFile(base::FilePath::FromASCII(kServiceWorkerPath).value(),
                   service_worker_script);

    static constexpr char kRegistrationScript[] = R"(
        navigator.serviceWorker.register($1).then(function() {
          // Wait until the service worker is active.
          return navigator.serviceWorker.ready;
        }).then(function(r) {
          chrome.test.sendScriptResult('SUCCESS');
        }).catch(function(err) {
          chrome.test.sendScriptResult('ERROR: ' + err.message);
        }); )";
    std::string registration_script =
        content::JsReplace(kRegistrationScript, kServiceWorkerPath);

    base::Value result =
        ExecuteScriptInBackgroundPage(extension_->id(), registration_script);
    if (result != "SUCCESS") {
      ADD_FAILURE() << "Failed to register the service worker: " << result;
    }
    return !::testing::Test::HasFailure();
  }

  // Injects (into |web_contents|) a content_script that performs a fetch of
  // |url|. Returns the body of the response.
  //
  // The method below uses "programmatic" (rather than "declarative") way to
  // inject a content script, but the behavior and permissions of the content
  // script should be the same in both cases.  See also
  // https://developer.chrome.com/extensions/content_scripts#programmatic.
  std::string FetchViaContentScript(const GURL& url,
                                    content::WebContents* web_contents) {
    return FetchHelper(
        url, web_contents,
        base::BindOnce(&OrbAndCorsExtensionBrowserTest::ExecuteContentScript,
                       base::Unretained(this), base::Unretained(web_contents)));
  }

  // Performs a fetch of |url| from the background page of the test extension.
  // Returns the body of the response.
  std::string FetchViaBackgroundPage(const GURL& url,
                                     const Extension* extension,
                                     BrowserWindowInterface* browser) {
    content::WebContents* background_web_contents =
        ProcessManager::Get(browser->GetProfile())
            ->GetBackgroundHostForExtension(extension->id())
            ->host_contents();
    return FetchViaFrame(url, background_web_contents);
  }
  std::string FetchViaBackgroundPage(const GURL& url) {
    return FetchViaBackgroundPage(url, extension_, browser());
  }

  // Performs a fetch of `url` from `execution_target` (directly, without going
  // through content scripts).  Returns the body of the response.
  std::string FetchViaFrame(
      const GURL& url,
      const content::ToRenderFrameHost& execution_target) {
    return FetchHelper(
        url,
        content::WebContents::FromRenderFrameHost(
            execution_target.render_frame_host()),
        base::BindOnce(&OrbAndCorsExtensionBrowserTest::ExecuteRegularScript,
                       base::Unretained(this),
                       execution_target.render_frame_host()));
  }

  // Performs a fetch of |url| from a srcdoc subframe added to |parent_frame|
  // and executing a script via <script> tag.  Returns the body of the response.
  std::string FetchViaSrcDocFrame(GURL url,
                                  content::RenderFrameHost* parent_frame) {
    return FetchHelper(
        url, content::WebContents::FromRenderFrameHost(parent_frame),
        base::BindOnce(&OrbAndCorsExtensionBrowserTest::ExecuteInSrcDocFrame,
                       base::Unretained(this), base::Unretained(parent_frame)));
  }

  GURL GetExtensionResource(const std::string& relative_path) {
    return extension_->GetResourceURL(relative_path);
  }

  url::Origin GetExtensionOrigin() { return extension_->origin(); }

  GURL GetTestPageUrl(const std::string& hostname) {
    // Using the page below avoids a network fetch of /favicon.ico which helps
    // avoid extra synchronization hassles in the tests.
    return embedded_test_server()->GetURL(
        hostname, "/favicon/title1_with_data_uri_icon.html");
  }

  const Extension* extension() { return extension_; }

  // Asks the test |extension_| to inject |content_script| into |web_contents|.
  //
  // This is an implementation of FetchCallback.
  // Returns true if the content script execution started successfully.
  bool ExecuteContentScript(content::WebContents* web_contents,
                            const std::string& content_script) {
    int tab_id = ExtensionTabUtil::GetTabId(web_contents);
    std::string background_script = content::JsReplace(
        "chrome.tabs.executeScript($1, { code: $2 });", tab_id, content_script);
    return ExecuteScriptInBackgroundPageNoWait(extension_->id(),
                                               background_script);
  }

 protected:
  testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;

 private:
  // Executes |regular_script| in |web_contents|.
  //
  // This is an implementation of FetchCallback.
  // Returns true if the script execution started successfully.
  bool ExecuteRegularScript(content::RenderFrameHost* frame,
                            const std::string& regular_script) {
    content::ExecuteScriptAsync(frame, regular_script);

    // Report artificial success to meet FetchCallback's requirements.
    return true;
  }

  // Injects into |parent_frame| an "srcdoc" subframe that contains/executes
  // |script_to_run_in_subframe| via <script> tag.
  //
  // This function is useful to exercise a scenario when a <script> tag may
  // execute before the browser gets a chance to see the a frame/navigation
  // commit is happening.
  //
  // This is an implementation of FetchCallback.
  // Returns true if the script execution started successfully.
  bool ExecuteInSrcDocFrame(content::RenderFrameHost* parent_frame,
                            const std::string& script_to_run_in_subframe) {
    static int sequence_id = 0;
    sequence_id++;
    std::string filename =
        base::StringPrintf("srcdoc_script_%d.js", sequence_id);
    dir_.WriteFile(base::FilePath::FromUTF8Unsafe(filename).value(),
                   script_to_run_in_subframe);

    // Using <script src=...></script> instead of <script>...</script> to avoid
    // extensions CSP which forbids inline scripts.
    const char kScriptTemplate[] = R"(
        var subframe = document.createElement('iframe');
        subframe.srcdoc = '<script src=' + $1 + '></script>';
        document.body.appendChild(subframe); )";
    std::string subframe_injection_script =
        content::JsReplace(kScriptTemplate, filename);
    content::ExecuteScriptAsync(parent_frame, subframe_injection_script);

    // Report artificial success to meet FetchCallback's requirements.
    return true;
  }

  // FetchCallback represents a function that executes |fetch_script|.
  //
  // |fetch_script| will include calls to |domAutomationController.send| and
  // therefore instances of FetchCallback should not inject their own calls to
  // |domAutomationController.send| (e.g. this constraint rules out
  // ExecuteScriptInBackgroundPage and/or content::ExecJs).
  //
  // The function should return true if script execution started successfully.
  //
  // Currently used "implementations":
  // - OrbAndCorsExtensionBrowserTest::ExecuteContentScript(web_contents)
  // - OrbAndCorsExtensionBrowserTest::ExecuteRegularScript(web_contents)
  // - ExecuteScriptInBackgroundPageNoWait(profile, ext_id)
  using FetchCallback =
      base::OnceCallback<bool(const std::string& fetch_script)>;

  // Returns response body of a fetch of |url| initiated via |fetch_callback|.
  std::string FetchHelper(const GURL& url,
                          content::WebContents* web_contents,
                          FetchCallback fetch_callback) {
    content::DOMMessageQueue message_queue(web_contents);

    // Inject a content script that performs a cross-origin fetch to
    // cross-site.com.
    EXPECT_TRUE(std::move(fetch_callback).Run(CreateFetchScript(url)));

    // Wait until the message comes back and extract result from the message.
    return PopString(&message_queue);
  }

  raw_ptr<const Extension, DanglingUntriaged> extension_ = nullptr;
  std::unique_ptr<content::ResourceLoadObserver> resource_load_observer_;
};

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromDeclarativeContentScript_NoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));

  ASSERT_TRUE(InstallExtension(cross_site_resource));

  // Test case #1: Declarative script injected after a browser-initiated
  // navigation of the main frame.
  {
    // Monitor ORB behavior + result of the fetch.
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    content::DOMMessageQueue message_queue(active_web_contents());

    // Navigate to a fetch-initiator.com page - this should trigger execution of
    // the |content_script| declared in the extension manifest.
    GURL page_url = GetTestPageUrl("fetch-initiator.com");
    ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
    EXPECT_EQ(
        page_url,
        active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
    EXPECT_EQ(
        url::Origin::Create(page_url),
        active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

    // Extract results of the fetch done in the declarative content script.
    std::string fetch_result = PopString(&message_queue);

    // Verify whether the fetch worked or not (expectations differ depending on
    // various factors - see the body of
    // VerifyOrbEligibleFetchFromContentScript).
    VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                             "nosniff.xml - body\n");
  }

  // Test case #2: Declarative script injected after a renderer-initiated
  // creation of an about:blank frame.
  {
    // Monitor ORB behavior + result of the fetch.
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    content::DOMMessageQueue message_queue(active_web_contents());

    // Inject an about:blank subframe - this should trigger execution of the
    // |content_script| declared in the extension manifest.
    const char kBlankSubframeInjectionScript[] = R"(
        var subframe = document.createElement('iframe');
        document.body.appendChild(subframe); )";
    content::ExecuteScriptAsync(active_web_contents(),
                                kBlankSubframeInjectionScript);

    // Extract results of the fetch done in the declarative content script.
    std::string fetch_result = PopString(&message_queue);

    // Verify whether the fetch worked or not (expectations differ depending on
    // various factors - see the body of
    // VerifyOrbEligibleFetchFromContentScript).
    VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                             "nosniff.xml - body\n");
  }
}

// Test that verifies the current, baked-in (but not necessarily desirable
// behavior) where a content script injected by an extension can bypass
// CORS (and ORB) for any hosts the extension has access to.
// See also https://crbug.com/40577839.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_NoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // cross-site.com.
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify whether the fetch worked or not (expectations differ depending on
  // various factors - see the body of
  // VerifyOrbEligibleFetchFromContentScript).
  VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                           "nosniff.xml - body\n");
}

// Tests that extension permission to bypass CORS is revoked after the extension
// is unloaded.  See also https://crbug.com/41389197.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_UnloadedExtension) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const extensions::Extension* extension = InstallExtension();
  ASSERT_TRUE(extension);

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that adds a link that initiates a fetch from
  // cross-site.com.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  {
    const char kNewButtonScriptTemplate[] = R"(
        function startFetch() {
            %s
        }

        var link = document.createElement('a');
        link.href = '#foo';
        link.addEventListener('click', function() {
            startFetch();
        });
        link.id = 'fetch-button';
        link.innerText = 'start-fetch';

        document.body.appendChild(link);
        domAutomationController.send('READY');
    )";
    content::DOMMessageQueue queue(active_web_contents());
    ASSERT_TRUE(ExecuteContentScript(
        active_web_contents(),
        base::StringPrintf(kNewButtonScriptTemplate,
                           CreateFetchScript(cross_site_resource).c_str())));
    ASSERT_EQ("READY", PopString(&queue));
  }

  // Click the button - the fetch should be blocked.
  //
  // Clicking the button will execute the 'click' handler belonging to the
  // content script (i.e. the `startFetch` method defined in the
  // kNewButtonScriptTemplate above).  Directly executing the script via
  // content::ExecJs would have executed the script in the main world
  // (which is not what we want).
  const char kFetchInitiatingScript[] = R"(
      document.getElementById('fetch-button').click();
    )";
  {
    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
    content::WebContentsConsoleObserver console_observer(active_web_contents());

    content::DOMMessageQueue queue(active_web_contents());
    content::ExecuteScriptAsync(active_web_contents(), kFetchInitiatingScript);
    std::string fetch_result = PopString(&queue);

    VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                             "nosniff.xml - body\n");
  }

  // Unload the extension and try fetching again.  The content script should
  // still be present and work, but after the extension is unloaded, the fetch
  // should always fail.  See also https://crbug.com/41389197.
  extension_registrar()->DisableExtension(
      extension->id(), {disable_reason::DISABLE_USER_ACTION});
  EXPECT_FALSE(ExtensionRegistry::Get(profile())->enabled_extensions().GetByID(
      extension->id()));
  {
    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
    ObserveResourceLoads();
    content::WebContentsConsoleObserver console_observer(active_web_contents());

    content::DOMMessageQueue queue(active_web_contents());
    content::ExecuteScriptAsync(active_web_contents(), kFetchInitiatingScript);
    std::string fetch_result = PopString(&queue);

    // Verify that the fetch was blocked by CORS.  (ORB only applies to
    // `no-cors` requests.)
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    VerifyFetchWasBlockedByCors(console_observer);
    VerifyFetchWasAllowedByOrb(cross_site_resource);
  }
}

// Test that <all_urls> permission does not apply to hosts blocked by policy.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ContentScriptVsHostBlockedByPolicy_NoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtensionWithPermissionToAllUrls());
  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost("*", "*://*.example.com");
    pref.AddPolicyAllowedHost("*", "*://public.example.com");
  }

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Test fetch from a host allowed by the policy (and allowed by the extension
  // permissions).
  {
    SCOPED_TRACE(::testing::Message() << "Allowed by policy");
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    GURL cross_site_resource(
        embedded_test_server()->GetURL("public.example.com", "/nosniff.xml"));
    std::string fetch_result =
        FetchViaContentScript(cross_site_resource, active_web_contents());

    // Verify whether the fetch worked or not (expectations differ depending on
    // various factors - see the body of
    // VerifyOrbEligibleFetchFromContentScript).
    VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                             "nosniff.xml - body\n");
  }

  // Test fetch from a host blocked by the policy (and allowed by the extension
  // permissions).
  {
    SCOPED_TRACE(::testing::Message() << "Blocked by policy");
    ObserveResourceLoads();
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    GURL cross_site_resource(
        embedded_test_server()->GetURL("example.com", "/nosniff.xml"));
    std::string fetch_result =
        FetchViaContentScript(cross_site_resource, active_web_contents());

    // Verify that the fetch was blocked by CORS.  (ORB only applies to
    // `no-cors` requests.)
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    VerifyFetchWasBlockedByCors(console_observer);
    VerifyFetchWasAllowedByOrb(cross_site_resource);
  }
}

// Test that <all_urls> permission does not apply to hosts blocked by policy.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ContentScriptVsHostBlockedByPolicy_AllowedTextResource) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtensionWithPermissionToAllUrls());
  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost("*", "*://*.example.com");
    pref.AddPolicyAllowedHost("*", "*://public.example.com");
  }

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Test fetch from a host allowed by the policy (and allowed by the extension
  // permissions).
  {
    SCOPED_TRACE(::testing::Message() << "Allowed by policy");
    ObserveResourceLoads();
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    GURL cross_site_resource(embedded_test_server()->GetURL(
        "public.example.com", "/save_page/text.txt"));
    std::string fetch_result =
        FetchViaContentScript(cross_site_resource, active_web_contents());

    // Verify that the fetch was allowed by ORB.  CORS expectations differ
    // depending on exact scenario.
    VerifyNonOrbElligibleFetchFromContentScript(
        cross_site_resource, console_observer, fetch_result,
        "text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2");
  }

  // Test fetch from a host blocked by the policy (and allowed by the extension
  // permissions).
  {
    SCOPED_TRACE(::testing::Message() << "Blocked by policy");
    ObserveResourceLoads();
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    GURL cross_site_resource(
        embedded_test_server()->GetURL("example.com", "/save_page/text.txt"));
    std::string fetch_result =
        FetchViaContentScript(cross_site_resource, active_web_contents());

    // Verify that the fetch was blocked by CORS.
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    VerifyFetchWasAllowedByOrb(cross_site_resource);
    VerifyFetchWasBlockedByCors(console_observer);
  }
}

class OrbAndCorsUserHostRestrictionsBrowserTest
    : public OrbAndCorsExtensionBrowserTest {
 public:
  OrbAndCorsUserHostRestrictionsBrowserTest() {
    std::vector<base::test::FeatureRef> enabled_features = {
        extensions_features::kExtensionsMenuAccessControl,
        extensions_features::kExtensionsMenuAccessControlWithPermittedSites};
    scoped_feature_list_.InitWithFeatures(enabled_features, {});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(OrbAndCorsUserHostRestrictionsBrowserTest,
                       PolicyVsUserHostRestrictions) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtensionWithPermissionToAllUrls());
  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost("*", "*://*.example.com");
    pref.AddPolicyAllowedHost("*", "*://public.example.com");
  }

  GURL policy_allowed_resource =
      embedded_test_server()->GetURL("public.example.com", "/nosniff.xml");
  GURL policy_restricted_resource =
      embedded_test_server()->GetURL("restricted.example.com", "/nosniff.xml");

  // Now, add user settings that are in the inverse of the policy ones:
  // Blocked on public.example.com, but allowed on restricted.example.com.

  PermissionsManager* permissions_manager = PermissionsManager::Get(profile());
  {
    // Sites can be set as restricted iff the `host controls` flag is enabled.
    PermissionsManagerWaiter waiter(permissions_manager);
    permissions_manager->AddUserRestrictedSite(
        url::Origin::Create(policy_allowed_resource));
    waiter.WaitForUserPermissionsSettingsChange();
  }
  {
    // Sites can be set as permitted iff the `host controls` and `permitted
    // sites` flags are enabled.
    PermissionsManagerWaiter waiter(permissions_manager);
    permissions_manager->AddUserPermittedSite(
        url::Origin::Create(policy_restricted_resource));
    waiter.WaitForUserPermissionsSettingsChange();
  }

  {
    std::string fetch_result = FetchViaBackgroundPage(policy_allowed_resource);
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
  }
  {
    std::string fetch_result =
        FetchViaBackgroundPage(policy_restricted_resource);
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
  }
}

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_PermissionToAllUrls) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtensionWithPermissionToAllUrls());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // cross-site.com.
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify whether the fetch worked or not (expectations differ depending on
  // various factors - see the body of
  // VerifyOrbEligibleFetchFromContentScript).
  VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                           "nosniff.xml - body\n");
}
// Verification that granting file access to extensions doesn't relax CORS in
// case of requests to file: URLs (even from content scripts of extensions with
// <all_urls> permission).  See also https://crbug.com/40672635#comment15.
IN_PROC_BROWSER_TEST_F(
    OrbAndCorsExtensionBrowserTest,
    FromProgrammaticContentScript_PermissionToAllUrls_FileUrls) {
  // Install the extension and verify that the extension has access to file URLs
  // (<all_urls> permission is not sufficient - the extension has to be
  // additionally granted file access by setting LoadOptions.allow_file_access
  // to true in ExtensionBrowserTest::LoadExtension).
  const Extension* extension =
      InstallExtensionWithPermissionToAllUrls(/*enable_file_access=*/true);
  ASSERT_TRUE(extension);
  ASSERT_TRUE(util::AllowFileAccess(
      extension->id(), active_web_contents()->GetBrowserContext()));

  // Gather the test URLs.
  GURL page_url = chrome_test_utils::GetTestUrl(
      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title1.html")));
  GURL same_dir_resource = chrome_test_utils::GetTestUrl(
      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title2.html")));
  ASSERT_EQ(url::kFileScheme, page_url.GetScheme());
  ASSERT_EQ(url::kFileScheme, same_dir_resource.GetScheme());

  // Navigate to a file:// test page.
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  const char kScriptTemplate[] = R"(
      const url = $1;
      const xhr = new XMLHttpRequest();
      xhr.onload = () => domAutomationController.send(xhr.responseText);
      xhr.onerror = () => domAutomationController.send('XHR ERROR');
      xhr.open("GET", url);
      xhr.send()
  )";
  std::string script = content::JsReplace(kScriptTemplate, same_dir_resource);

  // Sanity check: file: -> file: XHR (no extensions involved) should be blocked
  // by CORS equivalent inside FileURLLoaderFactory.  (All file: URLs are
  // treated as an opaque origin, so all such XHRs would be considered
  // cross-origin.)
  {
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    ObserveResourceLoads();
    content::DOMMessageQueue queue(active_web_contents());
    ExecuteScriptAsync(active_web_contents(), script);
    std::string xhr_result = PopString(&queue);

    // Verify that the XHR was blocked by CORS-equivalent in
    // FileURLLoaderFactory.
    EXPECT_EQ("XHR ERROR", xhr_result);
    VerifyFetchWasBlockedByCors(console_observer);
    VerifyFetchWasAllowedByOrb(same_dir_resource);
  }

  // Inject a content script that performs a cross-origin XHR from another file:
  // URL (all file: URLs are treated as an opaque origin, so all such XHRs would
  // be considered cross-origin).
  //
  // The ability to inject content scripts into file: URLs comes from a
  // combination of <all_urls> and granting file access to the extension.
  //
  // The script below uses the XMLHttpRequest API, rather than fetch API,
  // because the fetch API doesn't support file: requests currently
  // (see https://crbug.com/40673850#comment10 and
  // https://crbug.com/40673851#comment20).
  {
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    ObserveResourceLoads();
    content::DOMMessageQueue queue(active_web_contents());
    ExecuteContentScript(active_web_contents(), script);
    std::string xhr_result = PopString(&queue);

    // Verify that the XHR was blocked by CORS-equivalent in
    // FileURLLoaderFactory (even though the extension has <all_urls> permission
    // and was granted file access).
    EXPECT_EQ("XHR ERROR", xhr_result);
    VerifyFetchWasBlockedByCors(console_observer);
    VerifyFetchWasAllowedByOrb(same_dir_resource);
  }
}

// Coverage of *.subdomain.com extension permissions for ORB-eligible fetches
// (via nosniff.xml).
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_SubdomainPermissions) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Verify behavior for fetching URLs covered by extension permissions.
  GURL kAllowedUrls[] = {
      embedded_test_server()->GetURL("subdomain.com", "/nosniff.xml"),
      embedded_test_server()->GetURL("foo.subdomain.com", "/nosniff.xml"),
  };
  for (const GURL& allowed_url : kAllowedUrls) {
    SCOPED_TRACE(::testing::Message() << "allowed_url = " << allowed_url);

    content::WebContentsConsoleObserver console_observer(active_web_contents());
    std::string fetch_result =
        FetchViaContentScript(allowed_url, active_web_contents());

    // Verify whether the fetch worked or not (expectations differ depending on
    // various factors - see the body of
    // VerifyOrbEligibleFetchFromContentScript).
    VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                             "nosniff.xml - body\n");
  }
}

// Test that verifies the current, baked-in (but not necessarily desirable
// behavior) where a content script injected by an extension can bypass
// CORS (and ORB) for any hosts the extension has access to.
// See also https://crbug.com/40111762 and https://crbug.com/40577839.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_RedirectToNoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // cross-site.com.
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  GURL redirecting_url(embedded_test_server()->GetURL(
      "other-with-permission.com",
      std::string("/server-redirect?") + cross_site_resource.spec()));
  std::string fetch_result =
      FetchViaContentScript(redirecting_url, active_web_contents());

  // Verify whether the fetch worked or not (expectations differ depending on
  // various factors - see the body of
  // VerifyOrbEligibleFetchFromContentScript).
  VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                           "nosniff.xml - body\n");
}

// Test that verifies CORS-allowed fetches work for targets that are not
// covered by the extension permissions.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ContentScript_CorsAllowedByServer_NoPermissionToTarget) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // other-without-permission.com.
  ObserveResourceLoads();
  GURL cross_site_resource(embedded_test_server()->GetURL(
      "other-without-permission.com", "/cors-ok.txt"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify that the fetch succeeded (because of the server's
  // Access-Control-Allow-Origin response header).
  EXPECT_EQ("cors-ok.txt - body\n", fetch_result);
  VerifyFetchWasAllowedByOrb(cross_site_resource);
}

// Test that verifies that CORS blocks non-ORB-eligible fetches for targets
// that are not covered by the extension permissions.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ContentScript_CorsIgnoredByServer_NoPermissionToTarget) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // other-without-permission.com.
  ObserveResourceLoads();
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(embedded_test_server()->GetURL(
      "other-without-permission.com", "/save_page/text.txt"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify that the fetch was blocked by CORS (because the extension has no
  // permission to the target + server didn't reply with
  // Access-Control-Allow-Origin response header).
  EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
  VerifyFetchWasBlockedByCors(console_observer);

  // Verify that the fetch was allowed by ORB (because ORB doesn't apply to
  // CORS requests).
  VerifyFetchWasAllowedByOrb(cross_site_resource);
}

// Tests that same-origin fetches (same-origin relative to the webpage the
// content script is injected into) are allowed.  See also
// https://crbug.com/40608046.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_SameOrigin) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a same-origin fetch to
  // fetch-initiator.com.
  ObserveResourceLoads();
  GURL same_origin_resource(
      embedded_test_server()->GetURL("fetch-initiator.com", "/nosniff.xml"));
  std::string fetch_result =
      FetchViaContentScript(same_origin_resource, active_web_contents());

  // Verify that no blocking occurred.
  EXPECT_THAT(fetch_result, ::testing::StartsWith("nosniff.xml - body"));
  VerifyFetchWasAllowedByOrb(same_origin_resource);
}

// Test that responses that would have been allowed by ORB anyway are not
// reported to LogInitiatorSchemeBypassingDocumentBlocking.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_AllowedTextResource) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // cross-site.com.
  ObserveResourceLoads();
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/save_page/text.txt"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify that the fetch was allowed by ORB.  CORS expectations differ
  // depending on exact scenario.
  VerifyNonOrbElligibleFetchFromContentScript(
      cross_site_resource, console_observer, fetch_result,
      "text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2");
}

// The private-state-token-redemption Permissions Policy feature, which is
// enabled by default, is required in order to execute a Trust Tokens
// (https://github.com/wicg/trust-token-api) redemption operation alongside a
// subresource request. To enforce this requirement, the browser binds the
// feature's value to a frame's subresource loader.
//
// Ensure that it is being propagated correctly for by verifying that a content
// script can execute a redemption operation.
//
// (Specifically, this makes sure RFHI is passing the correct factory
// parameter to URLLoaderFactoryParamsHelper::CreateForIsolatedWorld.)
// TODO(crbug.com/40221677): Have trust tokens handle the existence, or not, of
// PrivacySandboxSettings3.
IN_PROC_BROWSER_TEST_F(
    OrbAndCorsExtensionBrowserTest,
    DISABLED_FromProgrammaticContentScript_TrustTokenRedemptionAllowed) {
  // Trust Tokens operations only work on secure origins - set up a https test
  // server to help with this. One alternative would be using a localhost URL
  // from |embedded_test_server|, but this would require modifying the extension
  // manifest in InstallExtension.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
  ASSERT_TRUE(https_server.Start());

  // Load the test extension.
  ASSERT_TRUE(InstallExtension());

  GURL page_url = https_server.GetURL("/title1.html");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));

  // This doesn't need to exist; we expect the fetch to fail during precondition
  // checking.
  GURL resource("/fake-trust-token-page");

  {
    content::DOMMessageQueue message_queue(active_web_contents());

    base::DictValue request_init;
    request_init.SetByDottedPath("trustToken.type", "token-redemption");

    EXPECT_TRUE(ExecuteContentScript(
        active_web_contents(),
        CreateFetchScript(resource, std::move(request_init))));
    // The operation should fail because the Trust Tokens operation failed (we
    // didn't set up enough Trust Tokens state for it to execute), not because
    // the operation was forbidden (which would trigger a TypeError).
    EXPECT_THAT(PopString(&message_queue), HasSubstr("InvalidStateError"));
  }

  // Make sure the permission propagates correctly after a network service
  // crash.
  if (!content::IsOutOfProcessNetworkService()) {
    return;
  }
  SimulateNetworkServiceCrash();
  active_web_contents()
      ->GetPrimaryMainFrame()
      ->FlushNetworkAndNavigationInterfacesForTesting(
          /*do_nothing_if_no_network_service_connection=*/false);
  {
    content::DOMMessageQueue message_queue(active_web_contents());

    base::DictValue request_init;
    request_init.SetByDottedPath("trustToken.type", "token-redemption");

    EXPECT_TRUE(ExecuteContentScript(
        active_web_contents(),
        CreateFetchScript(resource, std::move(request_init))));
    // The operation should fail because the Trust Tokens operation failed (we
    // didn't set up enough Trust Tokens state for it to execute), not because
    // the operation was forbidden (which would trigger a TypeError).
    EXPECT_THAT(PopString(&message_queue), HasSubstr("InvalidStateError"));
  }
}

// Coverage of *.subdomain.com extension permissions for non-ORB eligible
// fetches (via save_page/text.txt).
IN_PROC_BROWSER_TEST_F(
    OrbAndCorsExtensionBrowserTest,
    FromProgrammaticContentScript_AllowedTextResource_SubdomainPermissions) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Verify behavior for fetching URLs covered by extension permissions.
  GURL kAllowedUrls[] = {
      embedded_test_server()->GetURL("subdomain.com", "/save_page/text.txt"),
      embedded_test_server()->GetURL("x.subdomain.com", "/save_page/text.txt"),
  };
  for (const GURL& allowed_url : kAllowedUrls) {
    SCOPED_TRACE(::testing::Message() << "allowed_url = " << allowed_url);

    // Inject a content script that performs a cross-origin fetch to
    // cross-site.com.
    ObserveResourceLoads();
    content::WebContentsConsoleObserver console_observer(active_web_contents());
    std::string fetch_result =
        FetchViaContentScript(allowed_url, active_web_contents());

    // Verify that ORB sniffing allowed the response.
    VerifyNonOrbElligibleFetchFromContentScript(
        allowed_url, console_observer, fetch_result,
        "text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2");
  }
}

// Test that responses that would have been allowed by ORB after sniffing are
// included in the AllowedByOrbButNotCors UMA.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_AllowedAfterSniffing) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // cross-site.com (to a PNG image that is incorrectly labelled as
  // `Content-Type: text/html`).
  ObserveResourceLoads();
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(embedded_test_server()->GetURL(
      "cross-site.com", "/downloads/image-labeled-as-html.png"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify that ORB sniffing allowed the response.
  VerifyNonOrbElligibleFetchFromContentScript(
      cross_site_resource, console_observer, fetch_result, "\xEF\xBF\xBDPNG");
}

// Test that responses are blocked by ORB, but have empty response body are not
// reported to LogInitiatorSchemeBypassingDocumentBlocking.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromProgrammaticContentScript_EmptyAndBlocked) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin fetch to
  // cross-site.com.
  content::WebContentsConsoleObserver console_observer(active_web_contents());
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.empty"));
  std::string fetch_result =
      FetchViaContentScript(cross_site_resource, active_web_contents());

  // Verify whether the fetch worked or not (expectations differ depending on
  // various factors - see the body of
  // VerifyOrbEligibleFetchFromContentScript).
  VerifyOrbEligibleFetchFromContentScript(console_observer, fetch_result,
                                           "" /* expected_response_body */);
}

// Test that requests from an extension background page use relaxed CORS
// processing.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_NoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // This test covers the default incognito mode (spanning mode) where there is
  // only a single background page (i.e. no separate incognito background page).
  EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension()));

  content::WebContents* background_web_contents =
      ProcessManager::Get(profile())
          ->GetBackgroundHostForExtension(extension()->id())
          ->host_contents();

  // Performs a cross-origin fetch from the background page.
  {
    ObserveResourceLoads(background_web_contents);
    GURL cross_site_resource(
        embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
    std::string fetch_result = FetchViaBackgroundPage(cross_site_resource);

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
    VerifyOrbWasDisabled(cross_site_resource);
  }
}

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_NoSniffXml_OriginAccessList) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());
  content::WebContents* background_web_contents =
      ProcessManager::Get(profile())
          ->GetBackgroundHostForExtension(extension()->id())
          ->host_contents();

  // The extension manifest has */cross-site.com in it's permissions array.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  ASSERT_EQ(background_web_contents->GetBrowserContext()
                ->GetSharedCorsOriginAccessList()
                ->GetOriginAccessList()
                .CheckAccessState(GetExtensionOrigin(), cross_site_resource),
            network::cors::OriginAccessList::AccessState::kAllowed);

  // This url is intentionally left out of the `permissions` array in the
  // extensions manifest.
  GURL cross_site_without_permission_resource("other-without-permission.com");
  ASSERT_NE(background_web_contents->GetBrowserContext()
                ->GetSharedCorsOriginAccessList()
                ->GetOriginAccessList()
                .CheckAccessState(GetExtensionOrigin(),
                                  cross_site_without_permission_resource),
            network::cors::OriginAccessList::AccessState::kAllowed);
}

// Test that requests from an extension background page use relaxed ORB
// processing in `no-cors` mode.  See also https://crbug.com/40793005.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_NoSniffXml_NoCors) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // This test covers the default incognito mode (spanning mode) where there is
  // only a single background page (i.e. no separate incognito background page).
  EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension()));

  // Performs a cross-origin fetch from the background page in "no-cors" mode.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  base::DictValue request_init;
  request_init.Set("mode", "no-cors");
  std::string script =
      CreateFetchScript(cross_site_resource, std::move(request_init));
  content::WebContents* background_web_contents =
      ProcessManager::Get(profile())
          ->GetBackgroundHostForExtension(extension()->id())
          ->host_contents();
  content::DOMMessageQueue message_queue(background_web_contents);
  content::ExecuteScriptAsync(background_web_contents, script);
  std::string fetch_result = PopString(&message_queue);

  // Verify that no blocking occurred (this is a bit unusual, as "no-cors"
  // responses are normally "opaque" - their body is normally not exposed to
  // Javascript).  See also https://crbug.com/40793005.
  EXPECT_EQ("nosniff.xml - body\n", fetch_result);
}

// Test that requests from an extension background page use relaxed ORB
// processing.  This test covers split-mode extensions - see:
// https://developer.chrome.com/docs/extensions/mv2/manifest/incognito/#split)
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_IncognitoSplitMode) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Install a split-mode extension with permission to cross-site.com.
  TestExtensionDir extension_dir;
  constexpr char kManifest[] = R"(
      {
        "name": "Split-mode CORS-testing extension",
        "version": "1.0",
        "manifest_version": 2,
        "incognito": "split",
        "permissions": [ "*://cross-site.com/*" ],
        "background": {
          "scripts": ["bg_script.js"]
        }
      } )";
  extension_dir.WriteManifest(kManifest);
  extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), R"(
      if (chrome.extension.inIncognitoContext) {
          chrome.test.sendMessage('Ready: incognito');
      } else {
          chrome.test.sendMessage('Ready: not incognito');
      } )");
  const Extension* extension = nullptr;
  {
    ExtensionTestMessageListener listener("Ready: not incognito");
    extension = LoadExtension(extension_dir.UnpackedPath(),
                              {.allow_in_incognito = true});
    ASSERT_TRUE(extension);
    ASSERT_TRUE(listener.WaitUntilSatisfied());
  }

  // This test covers the split-mode incognito mode where there is a separate
  // background page for the regular profile and a separate background page for
  // the incognito profile.
  EXPECT_TRUE(IncognitoInfo::IsSplitMode(extension));

  // Open an incognito window.  (The incognito-specific background host for the
  // extension will be created after creating a window.)
  BrowserWindowInterface* incognito_browser = nullptr;
  {
    ExtensionTestMessageListener listener("Ready: incognito");
    incognito_browser = CreateIncognitoBrowser();
    ASSERT_TRUE(listener.WaitUntilSatisfied());
  }

  // Both the regular and the incognito background pages should be able to
  // bypass CORS for accessing the `cross_site_resource`.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  {
    SCOPED_TRACE("Regular profile's background page");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
  {
    SCOPED_TRACE("Incognito profile's background page");
    std::string fetch_result = FetchViaBackgroundPage(
        cross_site_resource, extension, incognito_browser);
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
}

// Test that ORB+CORS are enforced for extensions with no permissions to
// http/https origins.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ExtensionWithNoHttpPermissions) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // "permission" entry in the manifest below mimics the PDF extension which
  // has no permissions to http/https origins (and therefore doesn't care
  // about relaxed ORB and/or CORS).
  const char kManifest[] = R"(
      {
        "name": "CrossOriginReadBlockingTest - Extension",
        "version": "1.0",
        "manifest_version": 2,
        "permissions": [ "contentSettings" ],
        "background": {"scripts": ["background_script.js"]}
      } )";
  ASSERT_TRUE(InstallExtensionWithManifest(kManifest));

  content::WebContents* background_web_contents =
      ProcessManager::Get(profile())
          ->GetBackgroundHostForExtension(extension()->id())
          ->host_contents();

  // Perform a cross-origin CORS fetch from the background page.
  {
    ObserveResourceLoads(background_web_contents);
    GURL cross_site_resource1(
        embedded_test_server()->GetURL("cross-site.com", "/nosniff.empty"));
    std::string fetch_result = FetchViaBackgroundPage(cross_site_resource1);

    // Verify that the fetch was blocked by CORS.
    //
    // This behavior verification is a bit important, but here it mostly tests
    // the test setup, rather than the behavior this test was created for.
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);

    // ORB only applies to `no-cors` requests.
    VerifyFetchWasAllowedByOrb(cross_site_resource1);
  }

  // Perform a cross-origin `no-cors` request from the background page.
  {
    // Use a slightly different URL to avoid having to think what effect the
    // network cache might have on test results.
    GURL cross_site_resource2(
        embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));

    base::DictValue request_init;
    request_init.Set("method", "GET");
    request_init.Set("mode", "no-cors");

    ObserveResourceLoads(background_web_contents);
    content::DOMMessageQueue queue(background_web_contents);
    content::ExecuteScriptAsync(
        background_web_contents,
        CreateFetchScript(cross_site_resource2, std::move(request_init)));
    std::string fetch_result = PopString(&queue);

    // Verify that the fetch was blocked by ORB.
    //
    // This is the main verification in the test.  This verifies that the
    // extension background page uses a URLLoaderFactory created with
    // URLLoaderFactoryParams::is_orb_enabled set to the default, secure value
    // of `true`.
    //
    // ORB ErrorsForAllFetches: Error message, because the fetch failed.
    // ORB v0.2: Empty result. ORB v0.2 can result in either network errors
    //   or empty results. But in this test all fetches are initiated by script,
    //   for which ORB v0.2 delivers empty responses, like earlier versions.
    // CORB & ORB v0.1: Empty result, because of no-cors.
    if (base::FeatureList::IsEnabled(
            network::features::kOpaqueResponseBlockingErrorsForAllFetches)) {
      EXPECT_THAT(fetch_result, HasSubstr("TypeError: Failed to fetch"));
    } else {
      EXPECT_EQ(fetch_result, "");
    }
    VerifyFetchWasBlockedByOrb(cross_site_resource2);
  }
}

// Test that ORB+CORS are enforced for extensions with optional permissions to
// http/https origins.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ExtensionWithOptionalHttpPermissions) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // There are only *optional* HTTP permissions declared in the manifest below -
  // they will be granted later, at runtime (but not at the extension
  // installation time).
  const char kManifest[] = R"(
      {
        "name": "CrossOriginReadBlockingTest - Extension",
        "version": "1.0",
        "manifest_version": 2,
        "optional_permissions": [ "<all_urls>", "http://example.com/" ],
        "background": {"scripts": ["background_script.js"]}
      } )";
  ASSERT_TRUE(InstallExtensionWithManifest(kManifest));

  // Navigate a tab to the extension origin.
  GURL extension_resource = GetExtensionResource("page.html");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), extension_resource));
  content::RenderFrameHost* test_frame =
      active_web_contents()->GetPrimaryMainFrame();
  ASSERT_EQ(GetExtensionOrigin(), test_frame->GetLastCommittedOrigin());

  // Perform a cross-origin fetch from an extension frame and verify that it got
  // blocked by CORS.
  GURL cross_site_url(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  url::Origin cross_site_origin = url::Origin::Create(cross_site_url);
  {
    ObserveResourceLoads();
    std::string fetch_result = FetchViaFrame(cross_site_url, test_frame);
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);

    // In presence of optional HTTP permissions ORB is disabled.
    VerifyOrbWasDisabled(cross_site_url);
  }

  // Grant the optional permissions to the extension.
  {
    auto dialog_action_reset =
        PermissionsRequestFunction::SetDialogActionForTests(
            PermissionsRequestFunction::DialogAction::kAutoConfirm);
    const char kPermissionGrantingScriptTemplate[] = R"(
        new Promise(resolve => {
          chrome.permissions.request(
            { origins: [$1] },
            granted => { resolve(granted); });
        });
    )";
    std::string script = content::JsReplace(kPermissionGrantingScriptTemplate,
                                            cross_site_origin.GetURL());
    ASSERT_EQ(true, content::EvalJs(test_frame, script));
  }

  // Performs a cross-origin fetch from the background page and verify that it
  // didn't get blocked by CORS.
  {
    ObserveResourceLoads();
    std::string fetch_result = FetchViaFrame(cross_site_url, test_frame);
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);

    // In presence of optional HTTP permissions ORB is disabled.
    VerifyOrbWasDisabled(cross_site_url);
  }
}

// Test that requests from an extension page hosted in a foreground tab use
// relaxed ORB processing.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromForegroundPage_NoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // This test covers the default incognito mode (spanning mode) where there is
  // only a single background page (i.e. no separate incognito background page),
  // but multiple processes (one per profile) for extension frames.
  EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension()));

  // Open an extension frame both in the regular window and in a new incognito
  // window.
  GURL extension_resource = GetExtensionResource("page.html");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), extension_resource));
  content::WebContents* incognito_contents = nullptr;
  {
    GURL http_test_page = GetTestPageUrl("fetch-initiator.com");
    BrowserWindowInterface* incognito_browser =
        OpenURLOffTheRecord(profile(), http_test_page);
    incognito_contents =
        incognito_browser->GetTabStripModel()->GetActiveWebContents();
    ASSERT_EQ(http_test_page, incognito_contents->GetLastCommittedURL());

    // Open an extension *subframe*.  Spanning-mode extensions cannot load in
    // main frames of incognito tabs as enforced by ExtensionCanLoadInIncognito
    // in //extensions/browser/extension_protocols.cc and described in
    // https://developer.chrome.com/docs/extensions/mv2/manifest/incognito/#spanning
    const char kScriptTemplate[] = R"(
        var iframe = document.createElement('iframe');
        iframe.src = $1;
        document.body.appendChild(iframe);
    )";
    {
      content::TestNavigationObserver navigation_observer(incognito_contents);
      content::ExecuteScriptAsync(
          incognito_contents,
          content::JsReplace(kScriptTemplate, extension_resource));
      navigation_observer.Wait();
      ASSERT_TRUE(navigation_observer.last_navigation_succeeded());
      ASSERT_EQ(extension_resource, navigation_observer.last_navigation_url());
    }
  }
  content::RenderFrameHost* regular_frame =
      active_web_contents()->GetPrimaryMainFrame();
  ASSERT_EQ(GetExtensionOrigin(), regular_frame->GetLastCommittedOrigin());
  content::RenderFrameHost* incognito_frame =
      content::ChildFrameAt(incognito_contents->GetPrimaryMainFrame(), 0);
  ASSERT_TRUE(incognito_frame);
  ASSERT_EQ(GetExtensionOrigin(), incognito_frame->GetLastCommittedOrigin());

  // Test case #1: Fetch from a regular profile's foreground tab.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  {
    SCOPED_TRACE("Regular profile's foreground tab - main frame");
    std::string fetch_result =
        FetchViaFrame(cross_site_resource, regular_frame);

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }

  // Test case #2: Fetch from an about:srcdoc subframe of a
  // chrome-extension://... frame.
  {
    SCOPED_TRACE("Regular profile's foreground tab - srcdoc frame");
    std::string fetch_result =
        FetchViaSrcDocFrame(cross_site_resource, regular_frame);

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }

  // Test case #3: Fetch from an extension subframe in an incognito foreground
  // tab.
  {
    SCOPED_TRACE("Incognito profile's foreground tab - subframe");
    std::string fetch_result =
        FetchViaFrame(cross_site_resource, incognito_frame);

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
}

// Test that requests from an extension page hosted in a foreground tab use
// relaxed ORB processing.  This test covers split-mode extensions - see:
// https://developer.chrome.com/docs/extensions/mv2/manifest/incognito/#split
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromForegroundPage_IncognitoSplitMode) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Install a split-mode extension with permission to cross-site.com.
  TestExtensionDir extension_dir;
  constexpr char kManifest[] = R"(
      {
        "name": "Split-mode CORS-testing extension",
        "version": "1.0",
        "manifest_version": 2,
        "incognito": "split",
        "permissions": [ "*://cross-site.com/*" ],
        "background": { "scripts": ["bg_script.js"] }
      } )";
  extension_dir.WriteManifest(kManifest);
  extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), "");
  extension_dir.WriteFile(FILE_PATH_LITERAL("page.html"),
                          "<body>Hello World!</body>");
  const Extension* extension =
      LoadExtension(extension_dir.UnpackedPath(), {.allow_in_incognito = true});
  ASSERT_TRUE(extension);

  // This test covers the split-mode incognito mode where there is a separate
  // background page for the regular profile and a separate background page for
  // the incognito profile.
  EXPECT_TRUE(IncognitoInfo::IsSplitMode(extension));

  // Open an extension tab both in the regular window and in a new incognito
  // window.
  GURL extension_page = extension->GetResourceURL("page.html");
  content::WebContents* regular_contents = active_web_contents();
  BrowserWindowInterface* incognito_browser =
      OpenURLOffTheRecord(profile(), extension_page);
  ASSERT_TRUE(NavigateToURL(regular_contents, extension_page));
  content::WebContents* incognito_contents =
      incognito_browser->GetTabStripModel()->GetActiveWebContents();
  ASSERT_EQ(extension->origin(),
            regular_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());
  ASSERT_EQ(
      extension->origin(),
      incognito_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Test fetching a cross-site resource.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  {
    SCOPED_TRACE("Regular profile's foreground tab");
    std::string fetch_result =
        FetchViaFrame(cross_site_resource, regular_contents);

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
  {
    SCOPED_TRACE("Incognito profile's foreground tab");
    std::string fetch_result =
        FetchViaFrame(cross_site_resource, incognito_contents);

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
}

// Test that requests from an extension's service worker to the network use
// relaxed ORB processing (both in the case of requests that 1) are initiated
// by the service worker and/or 2) are ignored by the service worker and fall
// back to the network).
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromRegisteredServiceWorker_NoSniffXml) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Register the service worker which injects "SERVICE WORKER INTERCEPT: "
  // prefix to the body of each response.
  const char kServiceWorkerScript[] = R"(
      self.addEventListener('fetch', function(event) {
          // Intercept all http requests to cross-site.com and inject
          // 'SERVICE WORKER INTERCEPT:' prefix.
          if (event.request.url.startsWith('http://cross-site.com')) {
            event.respondWith(
                // By using the 'fetch' call below, the service worker initiates
                // a network request that will go through the URLLoaderFactory
                // created via CreateFactoryBundle called / posted indirectly
                // from EmbeddedWorkerInstance::StartTask::Start.
                fetch(event.request)
                    .then(response => response.text())
                    .then(text => new Response(
                        'SERVICE WORKER INTERCEPT: >>>' + text + '<<<')));
          }

          // Let the request go directly to the network in all the other cases,
          // like:
          // - loading the extension resources like page.html (avoiding going
          //   through the service worker is required for correctness of test
          //   setup),
          // - handling the cross-origin fetch to other.com in test case #2.
          // Note that these requests will use the URLLoaderFactory owned by
          // ServiceWorkerSubresourceLoader which can be different to the
          // network loader factory owned by the ServiceWorker thread (which is
          // used for fetch initiated by the service worker above).
      }); )";
  ASSERT_TRUE(RegisterServiceWorkerForExtension(kServiceWorkerScript));

  // Navigate a tab to an extension page.
  ASSERT_TRUE(
      NavigateToURL(active_web_contents(), GetExtensionResource("page.html")));
  ASSERT_EQ(
      GetExtensionOrigin(),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Verify that the service worker controls the fetches.
  ASSERT_EQ(true, EvalJs(active_web_contents(),
                         "!!navigator.serviceWorker.controller"));

  // Test case #1: Network fetch initiated by the service worker.
  //
  // This covers URLLoaderFactory owned by the ServiceWorker thread and created
  // created via CreateFactoryBundle called / posted indirectly from
  // EmbeddedWorkerInstance::StartTask::Start.
  {
    // Perform a cross-origin fetch from the foreground extension page.
    // This should be intercepted by the service worker installed above.
    GURL cross_site_resource_intercepted_by_service_worker(
        embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
    std::string fetch_result =
        FetchViaFrame(cross_site_resource_intercepted_by_service_worker,
                      active_web_contents());

    // Verify that no blocking occurred (and that the response really did go
    // through the service worker).
    EXPECT_EQ("SERVICE WORKER INTERCEPT: >>>nosniff.xml - body\n<<<",
              fetch_result);
  }

  // Test case #2: Network fetch used as a fallback when service worker ignores
  // the 'fetch' event.
  //
  // This covers URLLoaderFactory owned by the ServiceWorkerSubresourceLoader,
  // which can be different to the network loader factory owned by the
  // ServiceWorker thread (which is used in test case #1).
  {
    // Perform a cross-origin fetch from the foreground extension page.
    // This should be intercepted by the service worker installed above.
    GURL cross_site_resource_ignored_by_service_worker(
        embedded_test_server()->GetURL("other-with-permission.com",
                                       "/nosniff.xml"));
    std::string fetch_result = FetchViaFrame(
        cross_site_resource_ignored_by_service_worker, active_web_contents());

    // Verify that no blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || \
    BUILDFLAG(IS_MAC)
// Flaky on Linux, especially under sanitizers: https://crbug.com/40127384
// Flaky UAF on Mac under ASAN: https://crbug.com/40691871
#define MAYBE_FromBackgroundServiceWorker_NoSniffXml \
  DISABLED_FromBackgroundServiceWorker_NoSniffXml
#else
#define MAYBE_FromBackgroundServiceWorker_NoSniffXml \
  FromBackgroundServiceWorker_NoSniffXml
#endif

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       MAYBE_FromBackgroundServiceWorker_NoSniffXml) {
  // Install the extension with a service worker that can be asked to start a
  // fetch to an arbitrary URL.
  const char kManifestTemplate[] = R"(
      {
        "name": "CrossOriginReadBlockingTest - Extension/BgServiceWorker",
        "version": "1.0",
        "manifest_version": 2,
        "permissions": [
            "*://cross-site.com/*"
            // This list intentionally does NOT include
            // other-without-permission.com.
        ],
        "background": {"service_worker": "sw.js"}
      } )";
  const char kServiceWorker[] = R"(
      chrome.runtime.onMessage.addListener(
          function(request, sender, sendResponse) {
            if (request.url) {
              fetch(request.url)
                .then(response => response.text())
                .then(text => sendResponse(text))
                .catch(err => sendResponse('error: ' + err));
              return true;
            }
          });
  )";
  dir_.WriteManifest(kManifestTemplate);
  dir_.WriteFile(FILE_PATH_LITERAL("sw.js"), kServiceWorker);
  dir_.WriteFile(FILE_PATH_LITERAL("page.html"), "<body>Hello World!</body>");
  const Extension* extension = LoadExtension(dir_.UnpackedPath());
  ASSERT_TRUE(extension);

  // Navigate a foreground tab to an extension URL, so that from this tab we can
  // ask the background service worker to initiate test fetches.
  ASSERT_TRUE(NavigateToURL(active_web_contents(),
                            extension->GetResourceURL("page.html")));
  const char kFetchTemplate[] = R"(
      chrome.runtime.sendMessage({url: $1}, function(response) {
          domAutomationController.send(response);
      });
  )";
  ASSERT_TRUE(embedded_test_server()->Start());

  // Test a request to a website covered by extension permissions.
  {
    GURL nosniff_xml_with_permission(
        embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
    content::DOMMessageQueue queue(active_web_contents());
    ObserveResourceLoads();
    content::ExecuteScriptAsync(
        active_web_contents(),
        content::JsReplace(kFetchTemplate, nosniff_xml_with_permission));
    std::string fetch_result = PopString(&queue);

    // Verify that no ORB or CORS blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);

    // ORB should be disabled for extension origins.
    VerifyOrbWasDisabled(nosniff_xml_with_permission);
  }

  // Test a request to a website *not* covered by extension permissions.
  {
    GURL nosniff_xml_with_permission(embedded_test_server()->GetURL(
        "other-without-permission.com", "/nosniff.xml"));
    content::DOMMessageQueue queue(active_web_contents());
    ObserveResourceLoads();
    ServiceWorkerConsoleObserver console_observer(
        active_web_contents()->GetBrowserContext());
    content::ExecuteScriptAsync(
        active_web_contents(),
        content::JsReplace(kFetchTemplate, nosniff_xml_with_permission));
    std::string fetch_result = PopString(&queue);

    // Verify that CORS blocked the response.
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    console_observer.WaitForMessages();
    VerifyFetchWasBlockedByCors(console_observer);

    // ORB should be disabled for extension origins.
    VerifyOrbWasDisabled(nosniff_xml_with_permission);
  }
}

class ReadyToCommitWaiter : public content::WebContentsObserver {
 public:
  explicit ReadyToCommitWaiter(content::WebContents* web_contents)
      : content::WebContentsObserver(web_contents) {}

  ReadyToCommitWaiter(const ReadyToCommitWaiter&) = delete;
  ReadyToCommitWaiter& operator=(const ReadyToCommitWaiter&) = delete;

  ~ReadyToCommitWaiter() override = default;

  void Wait() { run_loop_.Run(); }

  void ReadyToCommitNavigation(
      content::NavigationHandle* navigation_handle) override {
    run_loop_.Quit();
  }

 private:
  base::RunLoop run_loop_;
};

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ProgrammaticContentScriptVsWebUI) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Try to inject a content script just as we are about to commit a WebUI page.
  // This will cause ExecuteCodeInTabFunction::CanExecuteScriptOnPage to execute
  // while RenderFrameHost::GetLastCommittedOrigin() still corresponds to the
  // old page.
  {
    // Initiate navigating a new, blank tab (this avoids process swaps which
    // would otherwise occur when navigating to a WebUI pages from either the
    // NTP or from a web page).  This simulates choosing "Settings" from the
    // main menu.
    GURL web_ui_url("chrome://settings");
    NavigateParams nav_params(
        browser(), web_ui_url,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED));
    nav_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
    content::WebContentsAddedObserver new_web_contents_observer;
    Navigate(&nav_params);

    // Capture the new WebContents.
    content::WebContents* new_web_contents =
        new_web_contents_observer.GetWebContents();
    ReadyToCommitWaiter ready_to_commit_waiter(new_web_contents);
    content::TestNavigationObserver navigation_observer(new_web_contents, 1);

    // Repro of https://crbug.com/41420364 requires that no cross-process swap
    // takes place - this is what happens when navigating an initial/blank tab.

    // Wait until ReadyToCommit happens.
    //
    // For the repro to happen, content script injection needs to run
    // 1) after RenderFrameHostImpl::CommitNavigation
    //    (which runs in the same revolution of the message pump as
    //    WebContentsObserver::ReadyToCommitNavigation)
    // 2) before RenderFrameHostImpl::DidCommitProvisionalLoad
    //    (task posted from ReadyToCommitNavigation above should execute
    //     before any IPC responses that come after PostTask call).
    ready_to_commit_waiter.Wait();
    DCHECK_NE(
        web_ui_url,
        active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());

    // Inject the content script (simulating chrome.tabs.executeScript, but
    // using TabsExecuteScriptFunction directly to ensure the right timing).
    int tab_id = ExtensionTabUtil::GetTabId(active_web_contents());
    const char kArgsTemplate[] = R"(
        [%d, {"code": "
            var p = document.createElement('p');
            p.innerText = 'content script injection succeeded unexpectedly';
            p.id = 'content-script-injection-result';
            document.body.appendChild(p);
        "}] )";
    std::string args = base::StringPrintf(kArgsTemplate, tab_id);
    auto function = base::MakeRefCounted<TabsExecuteScriptFunction>();
    function->set_extension(extension());
    std::string actual_error = api_test_utils::RunFunctionAndReturnError(
        function.get(), args, profile());
    std::string expected_error =
        "Cannot access contents of url \"chrome://settings/\". "
        "Extension manifest must request permission to access this host.";
    EXPECT_EQ(expected_error, actual_error);

    // Wait until the navigation completes.
    navigation_observer.Wait();
    EXPECT_EQ(
        web_ui_url,
        active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  }

  // Check if the injection above succeeded (it shouldn't have, because of
  // renderer-side checks).
  const char kInjectionVerificationScript[] =
      "!!document.getElementById('content-script-injection-result');";
  EXPECT_EQ(false, content::EvalJs(active_web_contents(),
                                   kInjectionVerificationScript));

  // Try to fetch a WebUI resource (i.e. verify that the unsuccessful content
  // script injection above didn't clobber the WebUI-specific URLLoaderFactory).
  const char kScript[] = R"(
      var img = document.createElement('img');
      img.src = 'chrome://resources/images/arrow_down.svg';
      new Promise(resolve => {
        img.onload = () => resolve('LOADED');
        img.onerror = e => resolve('ERROR: ' + e);
      });
  )";
  EXPECT_EQ("LOADED", content::EvalJs(active_web_contents(), kScript));
}

// The following test is executed as Chrome App, which is only supported on
// ChromeOS.
#if BUILDFLAG(IS_CHROMEOS)
class OrbAndCorsAppBrowserTest : public PlatformAppBrowserTest {
 public:
  OrbAndCorsAppBrowserTest() = default;

  void SetUpOnMainThread() override {
    PlatformAppBrowserTest::SetUpOnMainThread();

    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }
};

IN_PROC_BROWSER_TEST_F(OrbAndCorsAppBrowserTest, WebViewContentScript) {
  // Load the test app.
  const char kManifest[] = R"(
      {
        "name": "CrossOriginReadBlockingTest - App",
        "version": "1.0",
        "manifest_version": 2,
        "permissions": ["*://*/*", "webview"],
        "app": {
          "background": {
            "scripts": ["background_script.js"]
          }
        }
      } )";
  TestExtensionDir dir;
  dir.WriteManifest(kManifest);
  const char kBackgroundScript[] = R"(
      chrome.app.runtime.onLaunched.addListener(function() {
        chrome.app.window.create('page.html', {}, function () {});
      });
  )";
  dir.WriteFile(FILE_PATH_LITERAL("background_script.js"), kBackgroundScript);
  const char kPage[] = R"(
      <div id="webview-tag-container"></div>
  )";
  dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPage);
  const Extension* app = LoadExtension(dir.UnpackedPath());
  ASSERT_TRUE(app);

#if BUILDFLAG(IS_CHROMEOS)
  apps::chrome_app_deprecation::ScopedAddAppToAllowlistForTesting allowlist(
      app->id());
#endif

  // Launch the test app and grab its WebContents.
  content::WebContents* app_contents = nullptr;
  {
    content::WebContentsAddedObserver new_contents_observer;
    LaunchPlatformApp(app);
    app_contents = new_contents_observer.GetWebContents();
  }
  ASSERT_TRUE(content::WaitForLoadStop(app_contents));

  // Inject a <webview> script and declare desire to inject
  // cross-origin-fetching content scripts into the guest.
  const char kWebViewInjectionScriptTemplate[] = R"(
      document.querySelector('#webview-tag-container').innerHTML =
          '<webview style="width: 100px; height: 100px;"></webview>';
      var webview = document.querySelector('webview');
      webview.addContentScripts([{
          name: 'rule',
          matches: ['*://*/*'],
          js: { code: $1 },
          run_at: 'document_start'}]);
  )";
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
  std::string web_view_injection_script = content::JsReplace(
      kWebViewInjectionScriptTemplate, CreateFetchScript(cross_site_resource));
  ASSERT_TRUE(ExecJs(app_contents, web_view_injection_script));

  // Navigate <webview>, which should trigger content script execution.
  GURL guest_url(
      embedded_test_server()->GetURL("fetch-initiator.com", "/title1.html"));
  const char kWebViewNavigationScriptTemplate[] = R"(
      var webview = document.querySelector('webview');
      webview.src = $1;
  )";
  std::string web_view_navigation_script =
      content::JsReplace(kWebViewNavigationScriptTemplate, guest_url);
  {
    // NOTE: The Dom message will be emitted in a new WebContents instance that
    // is created when setting webview.src. Hence, we need to listen for the
    // message in that instance.
    content::WebContents* webview_contents = nullptr;
    {
      content::WebContentsAddedObserver webview_contents_added_observer;
      content::ExecuteScriptAsync(app_contents, web_view_navigation_script);
      webview_contents = webview_contents_added_observer.GetWebContents();
    }
    content::DOMMessageQueue queue(webview_contents);

    std::string fetch_result = PopString(&queue);

    // Verify that no ORB or CORS blocking occurred.
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }
}
#endif  // BUILDFLAG(IS_CHROMEOS)

using OriginHeaderExtensionBrowserTest = OrbAndCorsExtensionBrowserTest;

IN_PROC_BROWSER_TEST_F(OriginHeaderExtensionBrowserTest,
                       OriginHeaderInCrossOriginGetRequest) {
  const char kResourcePath[] = "/simulated-resource";
  net::test_server::ControllableHttpResponse http_request(
      embedded_test_server(), kResourcePath);
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin GET fetch to
  // cross-site.com.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", kResourcePath));
  const char* kScriptTemplate = R"(
      fetch($1, {method: 'GET', mode:'cors'})
          .then(response => response.text())
          .then(text => domAutomationController.send(text))
          .catch(err => domAutomationController.send('ERROR: ' + err));
  )";
  ExecuteContentScript(
      active_web_contents(),
      content::JsReplace(kScriptTemplate, cross_site_resource));

  // Extract the Origin header.
  http_request.WaitForRequest();
  std::string actual_origin_header = "<none>";
  const auto& headers_map = http_request.http_request()->headers;
  auto it = headers_map.find("Origin");
  if (it != headers_map.end()) {
    actual_origin_header = it->second;
  }

  // Verify the Origin header uses the page's origin (not the extension
  // origin).
  EXPECT_EQ(url::Origin::Create(page_url).Serialize(), actual_origin_header);

  // Regression test against https://crbug.com/41448402.
  EXPECT_THAT(actual_origin_header,
              ::testing::Not(::testing::HasSubstr("chrome-extension")));
}

IN_PROC_BROWSER_TEST_F(OriginHeaderExtensionBrowserTest,
                       OriginHeaderInCrossOriginPostRequest) {
  const char kResourcePath[] = "/simulated-resource";
  net::test_server::ControllableHttpResponse http_request(
      embedded_test_server(), kResourcePath);
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin POST fetch to
  // cross-site.com.
  GURL cross_site_resource(
      embedded_test_server()->GetURL("cross-site.com", kResourcePath));
  const char* kScriptTemplate = R"(
      fetch($1, {method: 'POST', mode:'cors'})
          .then(response => response.text())
          .then(text => domAutomationController.send(text))
          .catch(err => domAutomationController.send('ERROR: ' + err));
  )";
  ExecuteContentScript(
      active_web_contents(),
      content::JsReplace(kScriptTemplate, cross_site_resource));

  // Extract the Origin header.
  http_request.WaitForRequest();
  std::string actual_origin_header = "<none>";
  const auto& headers_map = http_request.http_request()->headers;
  auto it = headers_map.find("Origin");
  if (it != headers_map.end()) {
    actual_origin_header = it->second;
  }

  // Verify the Origin header uses the page's origin (not the extension
  // origin).
  EXPECT_EQ(url::Origin::Create(page_url).Serialize(), actual_origin_header);

  // Regression test against https://crbug.com/41448402.
  EXPECT_THAT(actual_origin_header,
              ::testing::Not(::testing::HasSubstr("chrome-extension")));
}

IN_PROC_BROWSER_TEST_F(OriginHeaderExtensionBrowserTest,
                       OriginHeaderInSameOriginPostRequest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to a fetch-initiator.com page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a same-origin POST fetch to
  // fetch-initiator.com.
  GURL same_origin_resource(
      embedded_test_server()->GetURL("fetch-initiator.com", "/echoall"));
  const char* kScriptTemplate = R"(
      fetch($1, {method: 'POST', mode:'cors'})
          .then(response => response.text())
          .then(text => domAutomationController.send(text))
          .catch(err => domAutomationController.send('ERROR: ' + err));
  )";
  content::DOMMessageQueue message_queue(active_web_contents());
  ExecuteContentScript(
      active_web_contents(),
      content::JsReplace(kScriptTemplate, same_origin_resource));
  std::string fetch_result = PopString(&message_queue);

  // Verify the Origin header.
  //
  // According to the Fetch spec, POST should always set the Origin header (even
  // for same-origin requests).
  EXPECT_THAT(fetch_result,
              ::testing::HasSubstr("Origin: http://fetch-initiator.com"));

  // Regression test against https://crbug.com/41448402.
  EXPECT_THAT(fetch_result,
              ::testing::Not(::testing::HasSubstr("Origin: chrome-extension")));
}

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       RequestHeaders_InSameOriginFetch_FromContentScript) {
  // Sec-Fetch-Site only works on secure origins - setting up a https test
  // server to help with this.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
  net::test_server::ControllableHttpResponse subresource_request(
      &https_server, "/subresource");
  ASSERT_TRUE(https_server.Start());

  // Load the test extension.
  ASSERT_TRUE(InstallExtension());

  // Navigate to https test page.
  GURL page_url = https_server.GetURL("/title1.html");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a same-origin GET fetch.
  GURL same_origin_resource(https_server.GetURL("/subresource"));
  EXPECT_EQ(url::Origin::Create(page_url),
            url::Origin::Create(same_origin_resource));
  const char* kScriptTemplate = R"(
      fetch($1, {method: 'GET', mode: 'no-cors'}) )";
  ExecuteContentScript(
      active_web_contents(),
      content::JsReplace(kScriptTemplate, same_origin_resource));

  // Verify the Referrer and Sec-Fetch-* header values.
  subresource_request.WaitForRequest();
  EXPECT_THAT(
      subresource_request.http_request()->headers,
      testing::IsSupersetOf({testing::Pair("Referer", page_url.spec().c_str()),
                             testing::Pair("Sec-Fetch-Mode", "no-cors"),
                             testing::Pair("Sec-Fetch-Site", "same-origin")}));
}

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       RequestHeaders_InSameOriginXhr_FromContentScript) {
  // Sec-Fetch-Site only works on secure origins - setting up a https test
  // server to help with this.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
  net::test_server::ControllableHttpResponse subresource_request(
      &https_server, "/subresource");
  ASSERT_TRUE(https_server.Start());

  // Load the test extension.
  ASSERT_TRUE(InstallExtension());

  // Navigate to https test page.
  GURL page_url = https_server.GetURL("/title1.html");
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      url::Origin::Create(page_url),
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a same-origin GET XHR.
  GURL same_origin_resource(https_server.GetURL("/subresource"));
  EXPECT_EQ(url::Origin::Create(page_url),
            url::Origin::Create(same_origin_resource));
  const char* kScriptTemplate = R"(
    var req = new XMLHttpRequest();
    req.open('GET', $1, true);
    req.send(null); )";
  ExecuteContentScript(
      active_web_contents(),
      content::JsReplace(kScriptTemplate, same_origin_resource));

  // Verify the Referrer and Sec-Fetch-* header values.
  subresource_request.WaitForRequest();
  EXPECT_THAT(
      subresource_request.http_request()->headers,
      testing::IsSupersetOf({testing::Pair("Referer", page_url.spec().c_str()),
                             testing::Pair("Sec-Fetch-Mode", "cors"),
                             testing::Pair("Sec-Fetch-Site", "same-origin")}));
}

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest, CorsFromContentScript) {
  std::string cors_resource_path = "/cors-subresource-to-intercept";
  net::test_server::ControllableHttpResponse cors_request(
      embedded_test_server(), cors_resource_path);
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // Navigate to test page.
  GURL page_url = GetTestPageUrl("fetch-initiator.com");
  url::Origin page_origin = url::Origin::Create(page_url);
  std::string page_origin_string = page_origin.Serialize();
  ASSERT_TRUE(NavigateToURL(active_web_contents(), page_url));
  ASSERT_EQ(
      page_url,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  ASSERT_EQ(
      page_origin,
      active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Inject a content script that performs a cross-origin GET fetch.
  content::DOMMessageQueue message_queue(active_web_contents());
  GURL cors_resource_url(
      embedded_test_server()->GetURL("cross-site.com", cors_resource_path));
  EXPECT_TRUE(ExecuteContentScript(active_web_contents(),
                                   CreateFetchScript(cors_resource_url)));

  // Verify the request headers (e.g. Origin and Sec-Fetch-Site headers).
  //
  // Content scripts should participate in regular CORS, just as if the request
  // was issued from the webpage that the content script got injected into.
  // Therefore we should expect the Origin header to be present and have the
  // right value.
  cors_request.WaitForRequest();
  EXPECT_THAT(
      cors_request.http_request()->headers,
      testing::Contains(testing::Pair("Origin", page_origin_string.c_str())));

  // Respond with Access-Control-Allow-Origin that matches the origin of the web
  // page.
  cors_request.Send("HTTP/1.1 200 OK\r\n");
  cors_request.Send("Content-Type: text/xml; charset=utf-8\r\n");
  cors_request.Send("X-Content-Type-Options: nosniff\r\n");
  cors_request.Send("Access-Control-Allow-Origin: " + page_origin_string +
                    "\r\n");
  cors_request.Send("\r\n");
  cors_request.Send("cors-allowed-body");
  cors_request.Done();

  // Verify that no ORB blocking occurred.
  //
  // ORB blocks responses based on Access-Control-Allow-Origin, oblivious to
  // whether the Origin request header was present (and/or if the extension is
  // exempted from CORS).  The Access-Control-Allow-Origin header is compared
  // with the request_initiator of the fetch (the origin of |page_url|) and the
  // test responds with "*" which matches all origins.
  std::string fetch_result = PopString(&message_queue);
  EXPECT_EQ("cors-allowed-body", fetch_result);
}

IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_ActiveTabPermission) {
  ASSERT_TRUE(embedded_test_server()->Start());

  TestExtensionDir extension_dir;
  constexpr char kManifest[] = R"(
      {
        "name": "ActiveTab permissions vs CORS from extension background page",
        "version": "1.0",
        "manifest_version": 2,
        "browser_action": {
          "default_title": "activeTab"
        },
        "permissions": ["activeTab"],
        "background": {
          "scripts": ["bg_script.js"]
        }
      } )";
  extension_dir.WriteManifest(kManifest);
  extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), "");
  const Extension* extension = LoadExtension(extension_dir.UnpackedPath(),
                                             {.allow_in_incognito = false});
  ASSERT_TRUE(extension);

  // This test covers the default incognito mode (spanning mode) where there is
  // only a single background page (i.e. no separate incognito background page).
  EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension));

  // Set up a test scenario:
  // - top-level frame: kActiveTabHost
  constexpr char kActiveTabHost[] = "active-tab.example";
  GURL original_document_url =
      embedded_test_server()->GetURL(kActiveTabHost, "/title1.html");
  GURL cross_site_resource(
      embedded_test_server()->GetURL(kActiveTabHost, "/nosniff.xml"));
  ASSERT_TRUE(NavigateToURL(active_web_contents(), original_document_url));

  // Open an incognito window.  Since the extension is not enabled for
  // incognito, OriginAccessList should not be sent to the incognito-related
  // NetworkContext (this is verified by a DCHECK in
  // SetCorsOriginAccessListForExtensionHelper in
  // //extensions/browser/extension_util.cc.
  CreateIncognitoBrowser();

  // CORS exception shouldn't be initially granted based on ActiveTab.
  {
    SCOPED_TRACE("TEST STEP 1: Initial fetch.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
  }

  // Do one pass of BrowserAction without granting ActiveTab permission,
  // extension still shouldn't have ability to bypass CORS.
  ExtensionActionRunner::GetForWebContents(active_web_contents())
      ->RunAction(extension, false);
  {
    SCOPED_TRACE("TEST STEP 2: After BrowserAction without granting access.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
  }

  // Granting ActiveTab permission to the extension should give it the ability
  // to bypass CORS.
  ExtensionActionRunner::GetForWebContents(active_web_contents())
      ->RunAction(extension, true);
  {
    // ActiveTab access (just like OOR-CORS access) extends to the background
    // page.  This is desirable, because
    // 1) there is no security boundary between A) extension background pages
    //    and B) extension frames in the tab
    // 2) it seems best to highlight #1 by simplistically granting extra
    //    capabilities to the whole extension (rather than forcing the extension
    //    authors to jump through extra hurdles to utilize the new capability).
    SCOPED_TRACE("TEST STEP 3: After granting ActiveTab access.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }

  // Navigating the tab to a different, same-origin document should retain
  // extension's access to the origin.
  GURL another_document_url =
      embedded_test_server()->GetURL(kActiveTabHost, "/title2.html");
  EXPECT_NE(another_document_url, original_document_url);
  EXPECT_EQ(url::Origin::Create(another_document_url),
            url::Origin::Create(original_document_url));
  ASSERT_TRUE(NavigateToURL(active_web_contents(), another_document_url));
  {
    SCOPED_TRACE(
        "TEST STEP 4: After navigating the tab cross-document, "
        "but still same-origin.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);
  }

  // Navigating the tab to a different origin should revoke extension's access
  // to the tab.
  GURL cross_origin_url =
      embedded_test_server()->GetURL("other.com", "/title1.html");
  EXPECT_NE(url::Origin::Create(cross_origin_url),
            url::Origin::Create(original_document_url));
  ASSERT_TRUE(NavigateToURL(active_web_contents(), cross_origin_url));
  {
    SCOPED_TRACE("TEST STEP 5: After navigating the tab cross-origin.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
  }
}

// Similar to FromBackgroundPage_ActiveTabPermission, but focuses on interaction
// between the regular background page and the separate incognito background
// page in "split" mode.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_ActiveTabPermission_SplitMode) {
  TestExtensionDir extension_dir;
  constexpr char kManifest[] = R"(
      {
        "name": "ActiveTab permissions vs CORS from extension background page",
        "version": "1.0",
        "manifest_version": 2,
        "browser_action": {
          "default_title": "activeTab"
        },
        "incognito": "split",
        "permissions": ["activeTab"],
        "background": {
          "scripts": ["bg_script.js"]
        }
      } )";
  extension_dir.WriteManifest(kManifest);
  extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), "");
  const Extension* extension =
      LoadExtension(extension_dir.UnpackedPath(), {.allow_in_incognito = true});
  ASSERT_TRUE(extension);

  // Set up a test scenario:
  // - regular window: empty initial tab
  // - incognito window: top-level frame: kActiveTabHost
  ASSERT_TRUE(embedded_test_server()->Start());
  constexpr char kActiveTabHost[] = "active-tab.example";
  GURL original_document_url =
      embedded_test_server()->GetURL(kActiveTabHost, "/title1.html");
  BrowserWindowInterface* incognito_browser =
      OpenURLOffTheRecord(profile(), original_document_url);

  // CORS exception shouldn't be initially granted based on ActiveTab.
  GURL cross_site_resource(
      embedded_test_server()->GetURL(kActiveTabHost, "/nosniff.xml"));
  {
    SCOPED_TRACE("TEST STEP 1: Initial fetch.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(cross_site_resource, extension, browser());
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
    {
      SCOPED_TRACE("Incognito profile's background page");
      std::string fetch_result = FetchViaBackgroundPage(
          cross_site_resource, extension, incognito_browser);
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
  }

  // Granting ActiveTab permission in the incognito window should give the
  // extension access to the tab's origin, but only in the incognito profile
  // (since the extension uses "split" mode).
  ExtensionActionRunner::GetForWebContents(
      incognito_browser->GetTabStripModel()->GetActiveWebContents())
      ->RunAction(extension, true);
  {
    SCOPED_TRACE("TEST STEP 2: After granting ActiveTab access.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(cross_site_resource, extension, browser());
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
    {
      SCOPED_TRACE("Incognito profile's background page");
      std::string fetch_result = FetchViaBackgroundPage(
          cross_site_resource, extension, incognito_browser);
      EXPECT_EQ("nosniff.xml - body\n", fetch_result);
    }
  }

  // Navigating the tab to a different origin should revoke extension's access
  // to the tab.
  GURL cross_origin_url =
      embedded_test_server()->GetURL("other.com", "/title1.html");
  EXPECT_NE(url::Origin::Create(cross_origin_url),
            url::Origin::Create(original_document_url));
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(incognito_browser, cross_origin_url));
  {
    SCOPED_TRACE("TEST STEP 3: After navigating the tab cross-origin.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(cross_site_resource, extension, browser());
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
    {
      SCOPED_TRACE("Incognito profile's background page");
      std::string fetch_result = FetchViaBackgroundPage(
          cross_site_resource, extension, incognito_browser);
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
  }
}

// Similar to FromBackgroundPage_ActiveTabPermission_SplitMode, but goes through
// steps that (at one point) forced additional, persistent leaking of incognito
// permission into the regular profile's background page.  See also
// https://crbug.com/40742611.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_ActiveTabPermission_SplitMode2) {
  TestExtensionDir extension_dir;
  constexpr char kManifest[] = R"(
      {
        "name": "ActiveTab permissions vs CORS from extension background page",
        "version": "1.0",
        "manifest_version": 2,
        "browser_action": {
          "default_title": "activeTab"
        },
        "incognito": "split",
        "permissions": ["activeTab"],
        "background": {
          "scripts": ["bg_script.js"]
        }
      } )";
  extension_dir.WriteManifest(kManifest);
  extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), "");
  const Extension* extension =
      LoadExtension(extension_dir.UnpackedPath(), {.allow_in_incognito = true});
  ASSERT_TRUE(extension);

  // Set up a test scenario:
  // - regular window: top-level frame: kRegularHost
  // - incognito window: top-level frame: kIncognitoHost
  ASSERT_TRUE(embedded_test_server()->Start());
  constexpr char kIncognitoHost[] = "active-tab-in-incognito-profile.example";
  constexpr char kRegularHost[] = "active-tab-in-regular-profile.example";
  GURL incognito_page_url =
      embedded_test_server()->GetURL(kIncognitoHost, "/title1.html");
  GURL incognito_resource_url =
      embedded_test_server()->GetURL(kIncognitoHost, "/nosniff.xml");
  GURL regular_page_url =
      embedded_test_server()->GetURL(kRegularHost, "/title2.html");
  GURL regular_resource_url =
      embedded_test_server()->GetURL(kRegularHost, "/nosniff.xml");
  BrowserWindowInterface* incognito_browser =
      OpenURLOffTheRecord(profile(), incognito_page_url);
  ASSERT_TRUE(NavigateToURL(active_web_contents(), regular_page_url));

  // No CORS exception for `kIncognitoHost` should be initially granted based on
  // ActiveTab.
  {
    SCOPED_TRACE("TEST STEP 1: Initial fetch.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(incognito_resource_url, extension, browser());
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
    {
      SCOPED_TRACE("Incognito profile's background page");
      std::string fetch_result = FetchViaBackgroundPage(
          incognito_resource_url, extension, incognito_browser);
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
  }

  // Granting ActiveTab permission in the *incognito* window should give the
  // extension access to the tab's origin, but only in the incognito profile
  // (since the extension uses "split" mode).
  ExtensionActionRunner::GetForWebContents(
      incognito_browser->GetTabStripModel()->GetActiveWebContents())
      ->RunAction(extension, true);
  {
    SCOPED_TRACE("TEST STEP 2: After granting 'incognito' ActiveTab access.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(incognito_resource_url, extension, browser());
      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
    }
    {
      SCOPED_TRACE("Incognito profile's background page");
      std::string fetch_result = FetchViaBackgroundPage(
          incognito_resource_url, extension, incognito_browser);
      EXPECT_EQ("nosniff.xml - body\n", fetch_result);
    }
  }

  // Granting ActiveTab permission in the *regular* window (for a separate,
  // `kRegularHost`) should not affect how CORS behaved in the previous step
  // (unless there is a bug and we leak incognito permissions to the regular
  // background page).
  content::WebContents* regular_contents = active_web_contents();
  EXPECT_EQ(
      kRegularHost,
      regular_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin().host());
  EXPECT_NE(kRegularHost, kIncognitoHost);
  ExtensionActionRunner::GetForWebContents(regular_contents)
      ->RunAction(extension, true);
  {
    SCOPED_TRACE("TEST STEP 3: After granting 'regular' ActiveTab access.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(incognito_resource_url, extension, browser());
      // TODO(crbug.com/40742611): Change to EXPECT_EQ after fixing the
      // leak of permissions from incognito profile to regular profile.
      EXPECT_NE(kCorsErrorWhenFetching, fetch_result);
    }
    {
      SCOPED_TRACE("Incognito profile's background page");
      std::string fetch_result = FetchViaBackgroundPage(
          incognito_resource_url, extension, incognito_browser);
      EXPECT_EQ("nosniff.xml - body\n", fetch_result);
    }
  }

  // After closing the incognito window, the regular background page should
  // still have no access to the `kIncognitoHost` (or, hopefully, the potential
  // leaks of permissions from the previous steps should be fixed/recovered-from
  // at this point).
  incognito_browser->GetTabStripModel()->GetActiveWebContents()->Close();
  {
    SCOPED_TRACE("TEST STEP 4: After closing the incognito tab.");
    {
      SCOPED_TRACE("Regular profile's background page");
      std::string fetch_result =
          FetchViaBackgroundPage(incognito_resource_url, extension, browser());
      // TODO(crbug.com/40742611): Change to EXPECT_EQ after fixing the
      // leak of permissions from incognito profile to regular profile.
      EXPECT_NE(kCorsErrorWhenFetching, fetch_result);
    }
  }
}

// Similar to FromBackgroundPage_ActiveTabPermission, but focuses on behavior
// of the background page when it is shared between the regular and the
// incognito profiles in "spanning" mode.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       FromBackgroundPage_ActiveTabPermission_SpanningMode) {
  TestExtensionDir extension_dir;
  constexpr char kManifest[] = R"(
      {
        "name": "ActiveTab permissions vs CORS from extension background page",
        "version": "1.0",
        "manifest_version": 2,
        "browser_action": {
          "default_title": "activeTab"
        },
        "incognito": "spanning",
        "permissions": ["activeTab"],
        "background": {
          "scripts": ["bg_script.js"]
        }
      } )";
  extension_dir.WriteManifest(kManifest);
  extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), "");
  const Extension* extension =
      LoadExtension(extension_dir.UnpackedPath(), {.allow_in_incognito = true});
  ASSERT_TRUE(extension);

  // Set up a test scenario:
  // - regular window: empty initial tab
  // - incognito window: top-level frame: kActiveTabHost
  ASSERT_TRUE(embedded_test_server()->Start());
  constexpr char kActiveTabHost[] = "active-tab.example";
  GURL original_document_url =
      embedded_test_server()->GetURL(kActiveTabHost, "/title1.html");
  BrowserWindowInterface* incognito_browser =
      OpenURLOffTheRecord(profile(), original_document_url);

  // CORS exception shouldn't be initially granted based on ActiveTab.
  GURL cross_site_resource(
      embedded_test_server()->GetURL(kActiveTabHost, "/nosniff.xml"));
  {
    SCOPED_TRACE("TEST STEP 1: Initial fetch.");
    SCOPED_TRACE("Regular profile's background page");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);

    // There is no separate incognito background page in "spanning" mode.
  }

  // Granting ActiveTab permission in the incognito window should give the
  // extension access to the tab's origin.
  ExtensionActionRunner::GetForWebContents(
      incognito_browser->GetTabStripModel()->GetActiveWebContents())
      ->RunAction(extension, true);
  {
    SCOPED_TRACE("TEST STEP 2: After granting ActiveTab access.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ("nosniff.xml - body\n", fetch_result);

    // There is no separate incognito background page in "spanning" mode.
  }

  // Navigating the tab to a different origin should revoke extension's access
  // to the tab.
  GURL cross_origin_url =
      embedded_test_server()->GetURL("other.com", "/title1.html");
  EXPECT_NE(url::Origin::Create(cross_origin_url),
            url::Origin::Create(original_document_url));
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(incognito_browser, cross_origin_url));
  {
    SCOPED_TRACE("TEST STEP 3: After navigating the tab cross-origin.");
    std::string fetch_result =
        FetchViaBackgroundPage(cross_site_resource, extension, browser());
    EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);

    // There is no separate incognito background page in "spanning" mode.
  }
}

// Test that "active tab" permission allows bypassing ORB, but only for granted
// origins.
IN_PROC_BROWSER_TEST_F(OrbAndCorsExtensionBrowserTest,
                       ActiveTabPersmissionVsOrb) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(InstallExtension());

  // 1. Navigate a tab to an http origin
  GURL tab_url = embedded_test_server()->GetURL("bar.com", "/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));

  // 2. Grant active tab permission to that tab.
  // This is now asynchronous (it waits for Network Service to be updated).
  PermissionsManagerWaiter waiter(
      PermissionsManager::Get(browser()->GetProfile()));
  ActiveTabPermissionGranter* granter =
      ActiveTabPermissionGranter::FromWebContents(active_web_contents());
  ASSERT_TRUE(granter);
  granter->GrantIfRequested(extension());
  waiter.WaitForActiveTabPermissionGranted(extension()->id());

  // 3. Navigate a separate, new tab to an extension origin.
  GURL extension_resource = GetExtensionResource("page.html");
  content::WebContents* extension_web_contents = nullptr;
  {
    NavigateParams nav_params(
        browser(), extension_resource,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED));
    nav_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
    content::WebContentsAddedObserver new_web_contents_observer;
    Navigate(&nav_params);
    extension_web_contents = new_web_contents_observer.GetWebContents();
    content::TestNavigationObserver navigation_observer(extension_web_contents,
                                                        1);
    navigation_observer.Wait();
    EXPECT_EQ(extension_web_contents->GetLastCommittedURL(),
              extension_resource);
  }

  // 4. Monitor resource loads in the extension page.
  ObserveResourceLoads(extension_web_contents);

  // 5. In the extension page do a no-cors fetch of a resource for which the
  // extension has gained access via ActiveTab permission.
  //
  // TODO(https://crbug.com/502415811): Remove `console.log` after debugging
  // and fixing the issue.
  const char kScript[] = R"(
      fetch($1, {mode: 'no-cors'})
        .then(r => r.text())
        .then(() => 'LOADED')
        .catch(e => 'ERROR: ' + e);
  )";
  GURL active_tab_origin_url =
      embedded_test_server()->GetURL("bar.com", "/nosniff.xml");
  std::ignore =
      content::EvalJs(extension_web_contents,
                      content::JsReplace(kScript, active_tab_origin_url));

  // 5b. Verify that ORB didn't block the response.
  VerifyFetchWasAllowedByOrb(active_tab_origin_url);

  // 6. Same as step 5, but for an origin that hasn't been granted ActiveTab
  // permission.
  GURL other_url = embedded_test_server()->GetURL("other.com", "/nosniff.xml");
  std::ignore = content::EvalJs(extension_web_contents,
                                content::JsReplace(kScript, other_url));

  // 6b. Verify that ORB blocked the last response.
  VerifyFetchWasBlockedByOrb(other_url);
}

}  // namespace extensions
