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

#include <set>
#include <string>

#include "base/callback_helpers.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/ranges/algorithm.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/mock_log.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/isolated_origin_util.h"
#include "content/browser/origin_agent_cluster_isolation_state.h"
#include "content/browser/process_lock.h"
#include "content/browser/site_info.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "content/test/storage_partition_test_helpers.h"
#include "content/test/test_content_browser_client.h"
#include "storage/browser/file_system/file_permission_policy.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/file_system/isolated_context.h"
#include "storage/common/file_system/file_system_types.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"

namespace content {
namespace {

using IsolatedOriginSource = ChildProcessSecurityPolicy::IsolatedOriginSource;

const int kRendererID = 42;

#if defined(FILE_PATH_USES_DRIVE_LETTERS)
#define TEST_PATH(x) FILE_PATH_LITERAL("c:") FILE_PATH_LITERAL(x)
#else
#define TEST_PATH(x) FILE_PATH_LITERAL(x)
#endif

class ChildProcessSecurityPolicyTestBrowserClient
    : public TestContentBrowserClient {
 public:
  ChildProcessSecurityPolicyTestBrowserClient() {}

  bool IsHandledURL(const GURL& url) override {
    return schemes_.find(url.scheme()) != schemes_.end();
  }

  void ClearSchemes() {
    schemes_.clear();
  }

  void AddScheme(const std::string& scheme) {
    schemes_.insert(scheme);
  }

 private:
  std::set<std::string> schemes_;
};

bool IsCitadelProtectionEnabled() {
#if !BUILDFLAG(IS_ANDROID)
  // TODO(lukasza): https://crbug.com/566091: Once remote NTP is capable of
  // embedding OOPIFs, start enforcing citadel-style checks on desktop
  // platforms.
  return false;
#else
  return true;
#endif
}

void LockProcessIfNeeded(int process_id,
                         BrowserContext* browser_context,
                         const GURL& url) {
  scoped_refptr<SiteInstanceImpl> site_instance =
      SiteInstanceImpl::CreateForTesting(browser_context, url);
  if (site_instance->RequiresDedicatedProcess() &&
      site_instance->GetSiteInfo().ShouldLockProcessToSite(
          site_instance->GetIsolationContext())) {
    ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess(
        site_instance->GetIsolationContext(), process_id, false,
        ProcessLock::FromSiteInfo(site_instance->GetSiteInfo()));
  }
}

}  // namespace

class ChildProcessSecurityPolicyTest : public testing::Test {
 public:
  ChildProcessSecurityPolicyTest()
      : task_environment_(BrowserTaskEnvironment::REAL_IO_THREAD),
        old_browser_client_(nullptr) {}

  void SetUp() override {
    old_browser_client_ = SetBrowserClientForTesting(&test_browser_client_);

    // Claim to always handle chrome:// URLs because the CPSP's notion of
    // allowing WebUI bindings is hard-wired to this particular scheme.
    test_browser_client_.AddScheme(kChromeUIScheme);

    // Claim to always handle file:// URLs like the browser would.
    // net::URLRequest::IsHandledURL() no longer claims support for default
    // protocols as this is the responsibility of the browser (which is
    // responsible for adding the appropriate ProtocolHandler).
    test_browser_client_.AddScheme(url::kFileScheme);
    SiteIsolationPolicy::DisableFlagCachingForTesting();
  }

  void TearDown() override {
    test_browser_client_.ClearSchemes();
    SetBrowserClientForTesting(old_browser_client_);
  }

  // Helpers to construct (key, value) entries used to validate the
  // isolated_origins_ map.  The key is a site URL, calculated from the
  // provided origin, and the value is a list of IsolatedOriginEntries. These
  // helpers are members of ChildProcessSecurityPolicyTest so they can access
  // the private IsolatedOriginEntry struct.
  using IsolatedOriginEntry =
      ChildProcessSecurityPolicyImpl::IsolatedOriginEntry;
  // Converts |browsing_instance_id|, |origin| -> (site_url, {entry}) where
  // site_url is created from |origin|, and {entry} contains |origin|
  // and |browsing_instance_id|.
  auto GetIsolatedOriginEntry(BrowsingInstanceId browsing_instance_id,
                              const url::Origin& origin,
                              bool isolate_all_subdomains = false) {
    return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
        SiteInfo::GetSiteForOrigin(origin),
        {IsolatedOriginEntry(
            origin, true /* applies_to_future_browsing_instances */,
            browsing_instance_id, nullptr, nullptr, isolate_all_subdomains,
            IsolatedOriginSource::TEST)});
  }
  auto GetIsolatedOriginEntry(int browsing_instance_id,
                              const url::Origin& origin,
                              bool isolate_all_subdomains = false) {
    return GetIsolatedOriginEntry(BrowsingInstanceId(browsing_instance_id),
                                  origin, isolate_all_subdomains);
  }
  // Converts the provided params into a (site_url, {entry}) tuple, where
  // site_url is created from |origin| and {entry} contains |origin| and
  // matches the provided BrowserContext, BrowsingInstance ID, and whether the
  // isolation applies to future BrowsingInstances.
  auto GetIsolatedOriginEntry(BrowserContext* browser_context,
                              bool applies_to_future_browsing_instances,
                              BrowsingInstanceId browsing_instance_id,
                              const url::Origin& origin) {
    return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
        SiteInfo::GetSiteForOrigin(origin),
        {IsolatedOriginEntry(
            origin, applies_to_future_browsing_instances, browsing_instance_id,
            browser_context,
            browser_context ? browser_context->GetResourceContext() : nullptr,
            false /* isolate_all_subdomains */, IsolatedOriginSource::TEST)});
  }
  // Converts |origin| -> (site_url, {entry})
  //     where site_url is created from |origin| and
  //           entry contains |origin| and the latest BrowsingInstance ID.
  auto GetIsolatedOriginEntry(const url::Origin& origin,
                              bool isolate_all_subdomains = false) {
    return GetIsolatedOriginEntry(SiteInstanceImpl::NextBrowsingInstanceId(),
                                  origin, isolate_all_subdomains);
  }
  // Converts |origin1|, |origin2| -> (site_url, {entry1, entry2})
  //     where |site_url| is created from |origin1|, but is assumed to be the
  //               same for |origin2| (i.e., |origin1| and |origin2| are
  //               same-site),
  //           entry1 contains |origin1| and the latest BrowsingInstance ID,
  //           entry2 contains |origin2| and the latest BrowsingInstance ID.
  auto GetIsolatedOriginEntry(const url::Origin& origin1,
                              const url::Origin& origin2,
                              bool origin1_isolate_all_subdomains = false,
                              bool origin2_isolate_all_subdomains = false) {
    EXPECT_EQ(SiteInfo::GetSiteForOrigin(origin1),
              SiteInfo::GetSiteForOrigin(origin2));
    return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
        SiteInfo::GetSiteForOrigin(origin1),
        {IsolatedOriginEntry(
             origin1, true /* applies_to_future_browsing_contexts */,
             SiteInstanceImpl::NextBrowsingInstanceId(), nullptr, nullptr,
             origin1_isolate_all_subdomains, IsolatedOriginSource::TEST),
         IsolatedOriginEntry(
             origin2, true /* applies_to_future_browsing_contexts */,
             SiteInstanceImpl::NextBrowsingInstanceId(), nullptr, nullptr,
             origin2_isolate_all_subdomains, IsolatedOriginSource::TEST)});
  }

  bool IsIsolatedOrigin(BrowserContext* context,
                        int browsing_instance_id,
                        const url::Origin& origin) {
    return IsIsolatedOrigin(context, BrowsingInstanceId(browsing_instance_id),
                            origin);
  }

  bool IsIsolatedOrigin(BrowserContext* context,
                        BrowsingInstanceId browsing_instance_id,
                        const url::Origin& origin) {
    ChildProcessSecurityPolicyImpl* p =
        ChildProcessSecurityPolicyImpl::GetInstance();
    return p->IsIsolatedOrigin(
        IsolationContext(browsing_instance_id, context,
                         /*is_guest=*/false, /*is_fenced=*/false),
        origin, false /* origin_requests_isolation */);
  }

  // Returns the number of isolated origin entries for a particular origin.
  // There may be more than one such entry if each is associated with a
  // different profile.
  int GetIsolatedOriginEntryCount(const url::Origin& origin) {
    ChildProcessSecurityPolicyImpl* p =
        ChildProcessSecurityPolicyImpl::GetInstance();
    GURL key(SiteInfo::GetSiteForOrigin(origin));
    base::AutoLock isolated_origins_lock(p->isolated_origins_lock_);
    auto origins_for_key = p->isolated_origins_[key];
    return base::ranges::count(origins_for_key, origin,
                               &IsolatedOriginEntry::origin);
  }

  void CheckGetSiteForURL(BrowserContext* context,
                          std::map<GURL, GURL> to_test) {
    for (const auto& entry : to_test) {
      auto site_info =
          SiteInfo::CreateForTesting(IsolationContext(context), entry.first);
      EXPECT_EQ(site_info.site_url(), entry.second);
    }
  }

 protected:
  void RegisterTestScheme(const std::string& scheme) {
    test_browser_client_.AddScheme(scheme);
  }

  void GrantPermissionsForFile(ChildProcessSecurityPolicyImpl* p,
                               int child_id,
                               const base::FilePath& file,
                               int permissions) {
    p->GrantPermissionsForFile(child_id, file, permissions);
  }

  void CheckHasNoFileSystemPermission(ChildProcessSecurityPolicyImpl* p,
                                      const std::string& child_id) {
    EXPECT_FALSE(p->CanReadFileSystem(kRendererID, child_id));
    EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, child_id));
    EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, child_id));
    EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, child_id));
  }

  void CheckHasNoFileSystemFilePermission(ChildProcessSecurityPolicyImpl* p,
                                          const base::FilePath& file,
                                          const storage::FileSystemURL& url) {
    EXPECT_FALSE(p->CanReadFile(kRendererID, file));
    EXPECT_FALSE(p->CanCreateReadWriteFile(kRendererID, file));
    EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
    EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
    EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
    EXPECT_FALSE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
    EXPECT_FALSE(p->CanCopyIntoFileSystemFile(kRendererID, url));
    EXPECT_FALSE(p->CanDeleteFileSystemFile(kRendererID, url));

    auto handle = p->CreateHandle(kRendererID);
    EXPECT_FALSE(handle.CanReadFile(file));
    EXPECT_FALSE(handle.CanReadFileSystemFile(url));
  }

  BrowserContext* browser_context() { return &browser_context_; }

 private:
  BrowserTaskEnvironment task_environment_;
  TestBrowserContext browser_context_;
  ChildProcessSecurityPolicyTestBrowserClient test_browser_client_;
  raw_ptr<ContentBrowserClient> old_browser_client_;
};

TEST_F(ChildProcessSecurityPolicyTest, ChildID) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  p->AddForTesting(kRendererID, browser_context());
  auto handle = p->CreateHandle(kRendererID);
  EXPECT_EQ(handle.child_id(), kRendererID);
  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, IsWebSafeSchemeTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  EXPECT_TRUE(p->IsWebSafeScheme(url::kHttpScheme));
  EXPECT_TRUE(p->IsWebSafeScheme(url::kHttpsScheme));
  EXPECT_TRUE(p->IsWebSafeScheme(url::kDataScheme));
  EXPECT_TRUE(p->IsWebSafeScheme(url::kBlobScheme));
  EXPECT_TRUE(p->IsWebSafeScheme(url::kFileSystemScheme));

  EXPECT_FALSE(p->IsWebSafeScheme("registered-web-safe-scheme"));
  p->RegisterWebSafeScheme("registered-web-safe-scheme");
  EXPECT_TRUE(p->IsWebSafeScheme("registered-web-safe-scheme"));

  EXPECT_FALSE(p->IsWebSafeScheme(kChromeUIScheme));
}

TEST_F(ChildProcessSecurityPolicyTest, IsPseudoSchemeTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  EXPECT_TRUE(p->IsPseudoScheme(url::kAboutScheme));
  EXPECT_TRUE(p->IsPseudoScheme(url::kJavaScriptScheme));
  EXPECT_TRUE(p->IsPseudoScheme(kViewSourceScheme));
  EXPECT_TRUE(p->IsPseudoScheme(kGoogleChromeScheme));

  EXPECT_FALSE(p->IsPseudoScheme("registered-pseudo-scheme"));
  p->RegisterPseudoScheme("registered-pseudo-scheme");
  EXPECT_TRUE(p->IsPseudoScheme("registered-pseudo-scheme"));

  EXPECT_FALSE(p->IsPseudoScheme(kChromeUIScheme));
}

TEST_F(ChildProcessSecurityPolicyTest, StandardSchemesTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());

  auto handle = p->CreateHandle(kRendererID);

  // Safe to request, redirect or commit.
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("http://www.google.com/")));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("https://www.paypal.com/")));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("data:text/html,<b>Hi</b>")));
  EXPECT_TRUE(p->CanRequestURL(
      kRendererID, GURL("filesystem:http://localhost/temporary/a.gif")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("http://www.google.com/")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("https://www.paypal.com/")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("data:text/html,<b>Hi</b>")));
  EXPECT_TRUE(
      p->CanRedirectToURL(GURL("filesystem:http://localhost/temporary/a.gif")));

  const std::vector<std::string> kCommitURLs({
      "http://www.google.com/",
      "https://www.paypal.com/",
      "data:text/html,<b>Hi</b>",
      "filesystem:http://localhost/temporary/a.gif",
  });
  for (const auto& url_string : kCommitURLs) {
    const GURL commit_url(url_string);
    if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
      // A non-locked process cannot access URL (because with
      // site-per-process all the URLs need to be isolated).
      EXPECT_FALSE(p->CanCommitURL(kRendererID, commit_url)) << commit_url;
      EXPECT_FALSE(handle.CanCommitURL(commit_url)) << commit_url;
    } else {
      EXPECT_TRUE(p->CanCommitURL(kRendererID, commit_url)) << commit_url;
      EXPECT_TRUE(handle.CanCommitURL(commit_url)) << commit_url;
    }
  }

  // Dangerous to request, commit, or set as origin header.
  EXPECT_FALSE(p->CanRequestURL(kRendererID,
                                GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GetWebUIURL("foo/bar")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID,
                                GURL("view-source:http://www.google.com/")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
  EXPECT_TRUE(p->CanRedirectToURL(GetWebUIURL("foo/bar")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:http://www.google.com/")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL(kUnreachableWebDataURL)));

  const std::vector<std::string> kFailedCommitURLs(
      {"file:///etc/passwd", "view-source:http://www.google.com/",
       kUnreachableWebDataURL, GetWebUIURL("foo/bar").spec()});
  for (const auto& url_string : kFailedCommitURLs) {
    const GURL commit_url(url_string);
    EXPECT_FALSE(p->CanCommitURL(kRendererID, commit_url)) << commit_url;
    EXPECT_FALSE(handle.CanCommitURL(commit_url)) << commit_url;
  }

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, BlobSchemeTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL localhost_url("http://localhost/");
  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), localhost_url);

  EXPECT_TRUE(
      p->CanRequestURL(kRendererID, GURL("blob:http://localhost/some-guid")));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("blob:null/some-guid")));
  EXPECT_TRUE(
      p->CanRequestURL(kRendererID, GURL("blob:http://localhost/some-guid")));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("blob:NulL/some-guid")));
  EXPECT_TRUE(
      p->CanRequestURL(kRendererID, GURL("blob:NulL/some-guid#fragment")));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("blob:NulL/some-guid?query")));
  EXPECT_FALSE(p->CanRequestURL(
      kRendererID, GURL("blob:http://username@localhost/some-guid")));
  EXPECT_FALSE(p->CanRequestURL(
      kRendererID, GURL("blob:http://username     @localhost/some-guid")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("blob:blob:some-guid")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("blob:some-guid")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID,
                                GURL("blob:filesystem:http://localhost/path")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID,
                                GURL("filesystem:blob:http://localhost/guid")));

  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:http://localhost/some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:null/some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:http://localhost/some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:NulL/some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:NulL/some-guid#fragment")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:NulL/some-guid?query")));
  EXPECT_TRUE(
      p->CanRedirectToURL(GURL("blob:http://username@localhost/some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(
      GURL("blob:http://username     @localhost/some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:blob:some-guid")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:some-guid")));
  EXPECT_TRUE(
      p->CanRedirectToURL(GURL("blob:filesystem:http://localhost/path")));
  EXPECT_FALSE(
      p->CanRedirectToURL(GURL("filesystem:blob:http://localhost/guid")));

  EXPECT_TRUE(
      p->CanCommitURL(kRendererID, GURL("blob:http://localhost/some-guid")));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("blob:null/some-guid")));
  EXPECT_TRUE(
      p->CanCommitURL(kRendererID, GURL("blob:http://localhost/some-guid")));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("blob:NulL/some-guid")));
  EXPECT_TRUE(
      p->CanCommitURL(kRendererID, GURL("blob:NulL/some-guid#fragment")));
  EXPECT_FALSE(p->CanCommitURL(
      kRendererID, GURL("blob:http://username@localhost/some-guid")));
  EXPECT_FALSE(p->CanCommitURL(
      kRendererID, GURL("blob:http://username     @localhost/some-guid")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("blob:blob:some-guid")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("blob:some-guid")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID,
                               GURL("blob:filesystem:http://localhost/path")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID,
                               GURL("filesystem:blob:http://localhost/guid")));

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, AboutTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());

  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:blank")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:BlAnK")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("aBouT:BlAnK")));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("aBouT:blank")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("about:blank")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:BlAnK")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("aBouT:BlAnK")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("aBouT:blank")));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("about:blank")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:BlAnK")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("aBouT:BlAnK")));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("aBouT:blank")));

  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:srcdoc")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:srcdoc")));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("about:srcdoc")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:SRCDOC")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:SRCDOC")));

  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:cache")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:hang")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:version")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:crash")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:cache")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:hang")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:version")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:crash")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:cache")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:hang")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:version")));

  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("aBoUt:version")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:CrASh")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("abOuT:cAChe")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("aBoUt:version")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:CrASh")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("abOuT:cAChe")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("aBoUt:version")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:CrASh")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("abOuT:cAChe")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("aBoUt:version")));

  // Requests for about: pages should be denied.
  p->GrantCommitURL(kRendererID, GURL("about:crash"));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("about:crash")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:crash")));

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, JavaScriptTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());

  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("javascript:alert('xss')")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("javascript:alert('xss')")));
  p->GrantCommitURL(kRendererID, GURL("javascript:alert('xss')"));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("javascript:alert('xss')")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("javascript:alert('xss')")));

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, RegisterWebSafeSchemeTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());

  // Currently, "asdf" is destined for ShellExecute, so it is allowed to be
  // requested but not committed.
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("asdf:rockers")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));

  // Once we register "asdf", we default to deny.
  RegisterTestScheme("asdf");
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("asdf:rockers")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));

  // We can allow new schemes by adding them to the whitelist.
  p->RegisterWebSafeScheme("asdf");
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("asdf:rockers")));
  if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
    // With site-per-process, all URLs (including the one below) will ask to be
    // hosted in isolated processes.  Since |p| is not locked, CanCommitURL
    // should return false.
    EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));

    // After locking the process, CanCommitURL should start returning true.
    LockProcessIfNeeded(kRendererID, browser_context(), GURL("asdf:rockers"));
    EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
  } else {
    EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
  }

  // Cleanup.
  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, CanServiceCommandsTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL file_url("file:///etc/passwd");
  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), file_url);

  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
  p->GrantCommitURL(kRendererID, GURL("file:///etc/passwd"));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));

  // We should forget our state if we repeat a renderer id.
  p->Remove(kRendererID);
  p->AddForTesting(kRendererID, browser_context());
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, ViewSource) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());

  // Child processes cannot request view source URLs.
  EXPECT_FALSE(p->CanRequestURL(kRendererID,
                                GURL("view-source:http://www.google.com/")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID,
                                GURL("view-source:file:///etc/passwd")));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanRequestURL(
      kRendererID, GURL("view-source:view-source:http://www.google.com/")));

  // Child processes cannot be redirected to view source URLs.
  EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:http://www.google.com/")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:file:///etc/passwd")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanRedirectToURL(
      GURL("view-source:view-source:http://www.google.com/")));

  // View source URLs don't actually commit; the renderer is put into view
  // source mode, and the inner URL commits.
  EXPECT_FALSE(p->CanCommitURL(kRendererID,
                               GURL("view-source:http://www.google.com/")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID,
                               GURL("view-source:file:///etc/passwd")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanCommitURL(
      kRendererID, GURL("view-source:view-source:http://www.google.com/")));

  p->GrantCommitURL(kRendererID, GURL("view-source:file:///etc/passwd"));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
  EXPECT_FALSE(
      p->CanRequestURL(kRendererID, GURL("view-source:file:///etc/passwd")));
  EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:file:///etc/passwd")));
  EXPECT_FALSE(p->CanCommitURL(kRendererID,
                               GURL("view-source:file:///etc/passwd")));
  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, GoogleChromeScheme) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());

  GURL test_url("googlechrome://whatever");

  EXPECT_FALSE(p->CanRequestURL(kRendererID, test_url));
  EXPECT_FALSE(p->CanRedirectToURL(test_url));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, test_url));

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, GrantCommitURLToNonStandardScheme) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  const GURL url("httpxml://awesome");
  const GURL url2("httpxml://also-awesome");

  ASSERT_TRUE(url::Origin::Create(url).opaque());
  ASSERT_TRUE(url::Origin::Create(url2).opaque());
  RegisterTestScheme("httpxml");

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), url);

  EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url2));
  EXPECT_TRUE(p->CanRedirectToURL(url));
  EXPECT_TRUE(p->CanRedirectToURL(url2));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url2));

  // GrantCommitURL with a non-standard scheme should grant commit access to the
  // entire scheme.
  p->GrantCommitURL(kRendererID, url);

  EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, url2));
  EXPECT_TRUE(p->CanRedirectToURL(url));
  EXPECT_TRUE(p->CanRedirectToURL(url2));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, url2));

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, SpecificFile) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL icon_url("file:///tmp/foo.png");
  GURL sensitive_url("file:///etc/passwd");

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), sensitive_url);

  EXPECT_FALSE(p->CanRequestURL(kRendererID, icon_url));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, sensitive_url));
  EXPECT_TRUE(p->CanRedirectToURL(icon_url));
  EXPECT_TRUE(p->CanRedirectToURL(sensitive_url));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, icon_url));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, sensitive_url));

  p->GrantRequestSpecificFileURL(kRendererID, icon_url);
  EXPECT_TRUE(p->CanRequestURL(kRendererID, icon_url));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, sensitive_url));
  EXPECT_TRUE(p->CanRedirectToURL(icon_url));
  EXPECT_TRUE(p->CanRedirectToURL(sensitive_url));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, icon_url));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, sensitive_url));

  p->GrantCommitURL(kRendererID, icon_url);
  EXPECT_TRUE(p->CanRequestURL(kRendererID, icon_url));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, sensitive_url));
  EXPECT_TRUE(p->CanRedirectToURL(icon_url));
  EXPECT_TRUE(p->CanRedirectToURL(sensitive_url));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, icon_url));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, sensitive_url));

  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, FileSystemGrantsTest) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->AddForTesting(kRendererID, browser_context());
  std::string read_id =
      storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
          storage::kFileSystemTypeTest, "read_filesystem", base::FilePath());
  std::string read_write_id =
      storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
          storage::kFileSystemTypeTest,
          "read_write_filesystem",
          base::FilePath());
  std::string copy_into_id =
      storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
          storage::kFileSystemTypeTest,
          "copy_into_filesystem",
          base::FilePath());
  std::string delete_from_id =
      storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
          storage::kFileSystemTypeTest,
          "delete_from_filesystem",
          base::FilePath());

  // Test initially having no permissions.
  CheckHasNoFileSystemPermission(p, read_id);
  CheckHasNoFileSystemPermission(p, read_write_id);
  CheckHasNoFileSystemPermission(p, copy_into_id);
  CheckHasNoFileSystemPermission(p, delete_from_id);

  // Testing varying combinations of grants and checks.
  p->GrantReadFileSystem(kRendererID, read_id);
  EXPECT_TRUE(p->CanReadFileSystem(kRendererID, read_id));
  EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_id));
  EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_id));
  EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, read_id));

  p->GrantReadFileSystem(kRendererID, read_write_id);
  p->GrantWriteFileSystem(kRendererID, read_write_id);
  EXPECT_TRUE(p->CanReadFileSystem(kRendererID, read_write_id));
  EXPECT_TRUE(p->CanReadWriteFileSystem(kRendererID, read_write_id));
  EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_write_id));
  EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, read_write_id));

  p->GrantCopyIntoFileSystem(kRendererID, copy_into_id);
  EXPECT_FALSE(p->CanReadFileSystem(kRendererID, copy_into_id));
  EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, copy_into_id));
  EXPECT_TRUE(p->CanCopyIntoFileSystem(kRendererID, copy_into_id));
  EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, copy_into_id));

  p->GrantDeleteFromFileSystem(kRendererID, delete_from_id);
  EXPECT_FALSE(p->CanReadFileSystem(kRendererID, delete_from_id));
  EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, delete_from_id));
  EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, delete_from_id));
  EXPECT_TRUE(p->CanDeleteFromFileSystem(kRendererID, delete_from_id));

  // Test revoke permissions on renderer ID removal.
  p->Remove(kRendererID);
  CheckHasNoFileSystemPermission(p, read_id);
  CheckHasNoFileSystemPermission(p, read_write_id);
  CheckHasNoFileSystemPermission(p, copy_into_id);
  CheckHasNoFileSystemPermission(p, delete_from_id);

  // Test having no permissions upon re-adding same renderer ID.
  p->AddForTesting(kRendererID, browser_context());
  CheckHasNoFileSystemPermission(p, read_id);
  CheckHasNoFileSystemPermission(p, read_write_id);
  CheckHasNoFileSystemPermission(p, copy_into_id);
  CheckHasNoFileSystemPermission(p, delete_from_id);

  // Cleanup.
  p->Remove(kRendererID);
  storage::IsolatedContext::GetInstance()->RevokeFileSystem(read_id);
  storage::IsolatedContext::GetInstance()->RevokeFileSystem(read_write_id);
  storage::IsolatedContext::GetInstance()->RevokeFileSystem(copy_into_id);
  storage::IsolatedContext::GetInstance()->RevokeFileSystem(delete_from_id);
}

TEST_F(ChildProcessSecurityPolicyTest, FilePermissionGrantingAndRevoking) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  p->RegisterFileSystemPermissionPolicy(
      storage::kFileSystemTypeTest,
      storage::FILE_PERMISSION_USE_FILE_PERMISSION);

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), GURL("http://foo/"));

  base::FilePath file(TEST_PATH("/dir/testfile"));
  file = file.NormalizePathSeparators();
  storage::FileSystemURL url = storage::FileSystemURL::CreateForTest(
      blink::StorageKey::CreateFromStringForTesting("http://foo/"),
      storage::kFileSystemTypeTest, file);

  // Test initially having no permissions.
  CheckHasNoFileSystemFilePermission(p, file, url);

  // Testing every combination of permissions granting and revoking.
  p->GrantReadFile(kRendererID, file);
  EXPECT_TRUE(p->CanReadFile(kRendererID, file));
  EXPECT_FALSE(p->CanCreateReadWriteFile(kRendererID, file));
  EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
  EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
  EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
  EXPECT_FALSE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
  EXPECT_FALSE(p->CanCopyIntoFileSystemFile(kRendererID, url));
  EXPECT_FALSE(p->CanDeleteFileSystemFile(kRendererID, url));
  p->RevokeAllPermissionsForFile(kRendererID, file);
  CheckHasNoFileSystemFilePermission(p, file, url);

  p->GrantCreateReadWriteFile(kRendererID, file);
  EXPECT_TRUE(p->CanReadFile(kRendererID, file));
  EXPECT_TRUE(p->CanCreateReadWriteFile(kRendererID, file));
  EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanCopyIntoFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanDeleteFileSystemFile(kRendererID, url));
  p->RevokeAllPermissionsForFile(kRendererID, file);
  CheckHasNoFileSystemFilePermission(p, file, url);

  // Test revoke permissions on renderer ID removal.
  p->GrantCreateReadWriteFile(kRendererID, file);
  EXPECT_TRUE(p->CanReadFile(kRendererID, file));
  EXPECT_TRUE(p->CanCreateReadWriteFile(kRendererID, file));
  EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanCopyIntoFileSystemFile(kRendererID, url));
  EXPECT_TRUE(p->CanDeleteFileSystemFile(kRendererID, url));
  p->Remove(kRendererID);
  CheckHasNoFileSystemFilePermission(p, file, url);

  // Test having no permissions upon re-adding same renderer ID.
  p->AddForTesting(kRendererID, browser_context());
  CheckHasNoFileSystemFilePermission(p, file, url);
  LockProcessIfNeeded(kRendererID, browser_context(), GURL("http://foo/"));
  CheckHasNoFileSystemFilePermission(p, file, url);

  // Cleanup.
  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, FilePermissions) {
  base::FilePath granted_file = base::FilePath(TEST_PATH("/home/joe"));
  base::FilePath sibling_file = base::FilePath(TEST_PATH("/home/bob"));
  base::FilePath child_file = base::FilePath(TEST_PATH("/home/joe/file"));
  base::FilePath parent_file = base::FilePath(TEST_PATH("/home"));
  base::FilePath parent_slash_file = base::FilePath(TEST_PATH("/home/"));
  base::FilePath child_traversal1 =
      base::FilePath(TEST_PATH("/home/joe/././file"));
  base::FilePath child_traversal2 = base::FilePath(
      TEST_PATH("/home/joe/file/../otherfile"));
  base::FilePath evil_traversal1 =
      base::FilePath(TEST_PATH("/home/joe/../../etc/passwd"));
  base::FilePath evil_traversal2 = base::FilePath(
      TEST_PATH("/home/joe/./.././../etc/passwd"));
  base::FilePath self_traversal =
      base::FilePath(TEST_PATH("/home/joe/../joe/file"));
  base::FilePath relative_file = base::FilePath(FILE_PATH_LITERAL("home/joe"));

  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Grant permissions for a file.
  p->AddForTesting(kRendererID, browser_context());
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_OPEN));

  GrantPermissionsForFile(p, kRendererID, granted_file,
                             base::File::FLAG_OPEN |
                             base::File::FLAG_OPEN_TRUNCATED |
                             base::File::FLAG_READ |
                             base::File::FLAG_WRITE);
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
                                       base::File::FLAG_OPEN |
                                       base::File::FLAG_OPEN_TRUNCATED |
                                       base::File::FLAG_READ |
                                       base::File::FLAG_WRITE));
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
                                       base::File::FLAG_OPEN |
                                       base::File::FLAG_READ));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_CREATE));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file, 0));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_CREATE |
                                        base::File::FLAG_OPEN_TRUNCATED |
                                        base::File::FLAG_READ |
                                        base::File::FLAG_WRITE));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, sibling_file,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, parent_file,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_file,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_traversal1,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_traversal2,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, evil_traversal1,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, evil_traversal2,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  // CPSP doesn't allow this case for the sake of simplicity.
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, self_traversal,
                                        base::File::FLAG_OPEN |
                                        base::File::FLAG_READ));
  p->Remove(kRendererID);

  // Grant permissions for the directory the file is in.
  p->AddForTesting(kRendererID, browser_context());
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_OPEN));
  GrantPermissionsForFile(p, kRendererID, parent_file,
                             base::File::FLAG_OPEN |
                             base::File::FLAG_READ);
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_OPEN));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_READ |
                                        base::File::FLAG_WRITE));
  p->Remove(kRendererID);

  // Grant permissions for the directory the file is in (with trailing '/').
  p->AddForTesting(kRendererID, browser_context());
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_OPEN));
  GrantPermissionsForFile(p, kRendererID, parent_slash_file,
                             base::File::FLAG_OPEN |
                             base::File::FLAG_READ);
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_OPEN));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_READ |
                                        base::File::FLAG_WRITE));

  // Grant permissions for the file (should overwrite the permissions granted
  // for the directory).
  GrantPermissionsForFile(p, kRendererID, granted_file,
                          base::File::FLAG_WIN_TEMPORARY);
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_OPEN));
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
                                       base::File::FLAG_WIN_TEMPORARY));

  // Revoke all permissions for the file (it should inherit its permissions
  // from the directory again).
  p->RevokeAllPermissionsForFile(kRendererID, granted_file);
  EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
                                       base::File::FLAG_OPEN |
                                       base::File::FLAG_READ));
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
                                        base::File::FLAG_WIN_TEMPORARY));
  p->Remove(kRendererID);

  p->AddForTesting(kRendererID, browser_context());
  GrantPermissionsForFile(p, kRendererID, relative_file,
                             base::File::FLAG_OPEN);
  EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, relative_file,
                                        base::File::FLAG_OPEN));
  p->Remove(kRendererID);
}

TEST_F(ChildProcessSecurityPolicyTest, CanServiceWebUIBindings) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  const GURL url(GetWebUIURL("thumb/http://www.google.com/"));
  const GURL other_url(GetWebUIURL("not-thumb/"));
  const url::Origin origin = url::Origin::Create(url);
  {
    p->AddForTesting(kRendererID, browser_context());
    LockProcessIfNeeded(kRendererID, browser_context(), url);

    EXPECT_FALSE(p->HasWebUIBindings(kRendererID));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->GrantWebUIBindings(kRendererID, BINDINGS_POLICY_WEB_UI);

    EXPECT_TRUE(p->HasWebUIBindings(kRendererID));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->GrantCommitOrigin(kRendererID, origin);

    EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
    EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->Remove(kRendererID);
  }

  {
    p->AddForTesting(kRendererID, browser_context());
    LockProcessIfNeeded(kRendererID, browser_context(), url);

    EXPECT_FALSE(p->HasWebUIBindings(kRendererID));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->GrantWebUIBindings(kRendererID, BINDINGS_POLICY_MOJO_WEB_UI);

    EXPECT_TRUE(p->HasWebUIBindings(kRendererID));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->GrantCommitOrigin(kRendererID, origin);

    EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
    EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->Remove(kRendererID);
  }

  {
    p->AddForTesting(kRendererID, browser_context());
    LockProcessIfNeeded(kRendererID, browser_context(), url);

    EXPECT_FALSE(p->HasWebUIBindings(kRendererID));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->GrantWebUIBindings(kRendererID,
                          BINDINGS_POLICY_WEB_UI | BINDINGS_POLICY_MOJO_WEB_UI);

    EXPECT_TRUE(p->HasWebUIBindings(kRendererID));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->GrantCommitOrigin(kRendererID, origin);

    EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
    EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
    EXPECT_TRUE(p->CanRedirectToURL(url));

    EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
    EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
    EXPECT_TRUE(p->CanRedirectToURL(other_url));

    p->Remove(kRendererID);
  }
}

TEST_F(ChildProcessSecurityPolicyTest, RemoveRace) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL url("file:///etc/passwd");
  base::FilePath file(TEST_PATH("/etc/passwd"));

  p->AddForTesting(kRendererID, browser_context());

  p->GrantCommitURL(kRendererID, url);
  p->GrantReadFile(kRendererID, file);
  p->GrantWebUIBindings(kRendererID,
                        BINDINGS_POLICY_WEB_UI | BINDINGS_POLICY_MOJO_WEB_UI);

  EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
  EXPECT_TRUE(p->CanRedirectToURL(url));
  EXPECT_TRUE(p->CanReadFile(kRendererID, file));
  EXPECT_TRUE(p->HasWebUIBindings(kRendererID));

  p->Remove(kRendererID);

  // Renderers are added and removed on the UI thread, but the policy can be
  // queried on the IO thread.  The ChildProcessSecurityPolicy needs to be
  // prepared to answer policy questions about renderers who no longer exist.

  // In this case, we default to secure behavior.
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
  EXPECT_TRUE(p->CanRedirectToURL(url));
  EXPECT_FALSE(p->CanReadFile(kRendererID, file));
  EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
}

// Tests behavior of CanAccessDataForOrigin() during race conditions that
// can occur during Remove(). It verifies that permissions for a child ID are
// preserved after a Remove() call until the task, that Remove() has posted to
// the IO thread, has run.
//
// We use a combination of waitable events and extra tasks posted to the
// threads to capture permission state from the UI & IO threads during the
// removal process. It is intended to simulate pending tasks that could be
// run on each thread during removal.
TEST_F(ChildProcessSecurityPolicyTest, RemoveRace_CanAccessDataForOrigin) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL url("file:///etc/passwd");

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), url);

  base::WaitableEvent ready_for_remove_event;
  base::WaitableEvent remove_called_event;
  base::WaitableEvent pending_remove_complete_event;

  // Keep track of the return value for CanAccessDataForOrigin at various
  // points in time during the test.
  bool io_before_remove = false;
  bool io_while_io_task_pending = false;
  bool io_after_io_task_completed = false;
  bool ui_before_remove = false;
  bool ui_while_io_task_pending = false;
  bool ui_after_io_task_completed = false;

  // Post a task that will run on the IO thread before the task that
  // Remove() will post to the IO thread.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        // Capture state on the IO thread before Remove() is called.
        io_before_remove =
            p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));

        // Tell the UI thread we are ready for Remove() to be called.
        ready_for_remove_event.Signal();

        // Wait for Remove() to be called on the UI thread.
        remove_called_event.Wait();

        // Capture state after Remove() is called, but before its task on
        // the IO thread runs.
        io_while_io_task_pending =
            p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));
      }));

  ready_for_remove_event.Wait();

  ui_before_remove =
      p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));

  p->Remove(kRendererID);

  // Post a task to run after the task Remove() posted on the IO thread.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        io_after_io_task_completed =
            p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));

        // Tell the UI thread that the task from Remove()
        // has completed on the IO thread.
        pending_remove_complete_event.Signal();
      }));

  // Capture state after Remove() has been called, but before its IO thread
  // task has run. We know the IO thread task hasn't run yet because the
  // task we posted before the Remove() call is waiting for us to signal
  // |remove_called_event|.
  ui_while_io_task_pending =
      p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));

  // Unblock the IO thread so the pending remove events can run.
  remove_called_event.Signal();

  pending_remove_complete_event.Wait();

  // Capture state after IO thread task has run.
  ui_after_io_task_completed =
      p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));

  // Run pending UI thread tasks.
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  bool ui_after_remove_complete =
      p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));
  bool io_after_remove_complete = false;
  base::WaitableEvent after_remove_complete_event;

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        io_after_remove_complete =
            p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url));

        // Tell the UI thread that this task has
        // has completed on the IO thread.
        after_remove_complete_event.Signal();
      }));

  // Wait for the task we just posted to the IO thread to complete.
  after_remove_complete_event.Wait();

  // Verify expected states at various parts of the removal.
  // Note: IO thread is expected to keep pre-Remove() permissions until
  // the task Remove() posted runs on the IO thread.
  EXPECT_TRUE(io_before_remove);
  EXPECT_TRUE(io_while_io_task_pending);
  EXPECT_FALSE(io_after_io_task_completed);

  EXPECT_TRUE(ui_before_remove);
  EXPECT_FALSE(ui_while_io_task_pending);
  EXPECT_FALSE(ui_after_io_task_completed);

  EXPECT_FALSE(ui_after_remove_complete);
  EXPECT_FALSE(io_after_remove_complete);
}

// This test is similar to the one above that verifies CanAccessDataForOrigin()
// behavior during process shutdown. This particular test verifies that a
// ChildProcessSecurityPolicyImpl::Handle extends the lifetime of the security
// state beyond the Remove() call. This represents the case where a Mojo service
// on the IO thread still receives calls after the RPHI that created it has
// been destroyed.
//
// We use a combination of waitable events and extra tasks posted to the
// threads to capture permission state from the UI & IO threads during the
// removal process. It is intended to simulate pending tasks that could be
// run on each thread during removal.
TEST_F(ChildProcessSecurityPolicyTest, HandleExtendsSecurityStateLifetime) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL url("file:///etc/passwd");

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), url);

  auto handle = p->CreateHandle(kRendererID);

  base::WaitableEvent ready_for_remove_event;
  base::WaitableEvent remove_called_event;
  base::WaitableEvent ready_for_handle_invalidation_event;

  // Keep track of the return value for CanAccessDataForOrigin at various
  // points in time during the test.
  bool io_before_remove = false;
  bool io_after_remove = false;
  bool ui_before_remove = false;
  bool ui_after_remove = false;

  // Post a task that will run on the IO thread before the task that
  // Remove() will post to the IO thread.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        // Capture state on the IO thread before Remove() is called.
        io_before_remove =
            handle.CanAccessDataForOrigin(url::Origin::Create(url));

        // Tell the UI thread we are ready for Remove() to be called.
        ready_for_remove_event.Signal();
      }));

  ready_for_remove_event.Wait();

  ui_before_remove = handle.CanAccessDataForOrigin(url::Origin::Create(url));

  p->Remove(kRendererID);

  ui_after_remove = handle.CanAccessDataForOrigin(url::Origin::Create(url));

  // Post a task to verify post-Remove() state on the IO thread.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        io_after_remove =
            handle.CanAccessDataForOrigin(url::Origin::Create(url));

        // Tell the UI thread that we are ready to invalidate the
        // handle.
        ready_for_handle_invalidation_event.Signal();
      }));

  ready_for_handle_invalidation_event.Wait();

  // Invalidate the handle so it triggers destruction of the security state.
  handle = ChildProcessSecurityPolicyImpl::Handle();

  bool ui_after_handle_invalidation =
      handle.CanAccessDataForOrigin(url::Origin::Create(url));
  bool io_after_handle_invalidation = false;
  base::WaitableEvent after_invalidation_complete_event;

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        io_after_handle_invalidation =
            handle.CanAccessDataForOrigin(url::Origin::Create(url));

        // Tell the UI thread that this task has
        // has completed on the IO thread.
        after_invalidation_complete_event.Signal();
      }));

  // Wait for the task we just posted to the IO thread to complete.
  after_invalidation_complete_event.Wait();

  // Verify expected states at various parts of the removal.
  // Note: IO thread is expected to keep pre-Remove() permissions until
  // |handle| is invalidated and the task RemoveProcessReferenceLocked() posted
  // runs on the IO thread.
  EXPECT_TRUE(io_before_remove);
  EXPECT_TRUE(ui_before_remove);

  EXPECT_TRUE(io_after_remove);
  EXPECT_TRUE(ui_after_remove);

  EXPECT_FALSE(io_after_handle_invalidation);
  EXPECT_FALSE(ui_after_handle_invalidation);
}

TEST_F(ChildProcessSecurityPolicyTest, HandleDuplicate) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL url("file:///etc/passwd");

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), url);

  auto handle = p->CreateHandle(kRendererID);

  EXPECT_TRUE(handle.CanAccessDataForOrigin(url::Origin::Create(url)));

  // Verify that a valid duplicate can be created and allows access.
  auto duplicate_handle = handle.Duplicate();
  EXPECT_TRUE(duplicate_handle.is_valid());
  EXPECT_TRUE(
      duplicate_handle.CanAccessDataForOrigin(url::Origin::Create(url)));

  p->Remove(kRendererID);

  // Verify that both handles still work even after Remove() has been called.
  EXPECT_TRUE(handle.CanAccessDataForOrigin(url::Origin::Create(url)));
  EXPECT_TRUE(
      duplicate_handle.CanAccessDataForOrigin(url::Origin::Create(url)));

  // Verify that a new duplicate can be created after Remove().
  auto duplicate_handle2 = handle.Duplicate();
  EXPECT_TRUE(duplicate_handle2.is_valid());
  EXPECT_TRUE(
      duplicate_handle2.CanAccessDataForOrigin(url::Origin::Create(url)));

  // Verify that a new valid Handle cannot be created after Remove().
  EXPECT_FALSE(p->CreateHandle(kRendererID).is_valid());

  // Invalidate the original Handle and verify that the duplicates still work.
  handle = ChildProcessSecurityPolicyImpl::Handle();
  EXPECT_FALSE(handle.CanAccessDataForOrigin(url::Origin::Create(url)));
  EXPECT_TRUE(
      duplicate_handle.CanAccessDataForOrigin(url::Origin::Create(url)));
  EXPECT_TRUE(
      duplicate_handle2.CanAccessDataForOrigin(url::Origin::Create(url)));
}

TEST_F(ChildProcessSecurityPolicyTest, CanAccessDataForOrigin_URL) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL file_url("file:///etc/passwd");
  GURL foo_http_url("http://foo.com/index.html");
  GURL foo_blob_url("blob:http://foo.com/43d75119-d7af-4471-a293-07c6b3d7e61a");
  GURL foo_filesystem_url("filesystem:http://foo.com/temporary/test.html");
  GURL bar_http_url("http://bar.com/index.html");

  const std::vector<GURL> kAllTestUrls = {file_url, foo_http_url, foo_blob_url,
                                          foo_filesystem_url, bar_http_url};

  // Test invalid ID and invalid Handle cases.
  auto handle = p->CreateHandle(kRendererID);
  for (auto url : kAllTestUrls) {
    EXPECT_FALSE(
        p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url)))
        << url;
    EXPECT_FALSE(
        handle.CanAccessDataForOrigin(url::Origin::Create(bar_http_url)))
        << url;
  }

  TestBrowserContext browser_context;
  p->AddForTesting(kRendererID, &browser_context);

  // Replace the old invalid handle with a new valid handle.
  handle = p->CreateHandle(kRendererID);

  // Verify unlocked origin permissions.
  for (auto url : kAllTestUrls) {
    if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
      // A non-locked process cannot access URLs below (because with
      // site-per-process all the URLs need to be isolated).
      EXPECT_FALSE(
          p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url)))
          << url;
      EXPECT_FALSE(handle.CanAccessDataForOrigin(url::Origin::Create(url)))
          << url;
    } else {
      EXPECT_TRUE(
          p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url)))
          << url;
      EXPECT_TRUE(handle.CanAccessDataForOrigin(url::Origin::Create(url)))
          << url;
    }
  }

  // Isolate |http_url| so we can't get a default SiteInstance.
  p->AddFutureIsolatedOrigins({url::Origin::Create(foo_http_url)},
                              IsolatedOriginSource::TEST, &browser_context);

  // Lock process to |http_url| origin.
  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&browser_context, foo_http_url);
  EXPECT_FALSE(foo_instance->IsDefaultSiteInstance());
  LockProcessIfNeeded(kRendererID, &browser_context, foo_http_url);

  // Verify that file access is no longer allowed.
  EXPECT_FALSE(
      p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(file_url)));
  EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID,
                                        url::Origin::Create(foo_http_url)));
  EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID,
                                        url::Origin::Create(foo_blob_url)));
  EXPECT_TRUE(p->CanAccessDataForOrigin(
      kRendererID, url::Origin::Create(foo_filesystem_url)));
  EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID,
                                         url::Origin::Create(bar_http_url)));
  EXPECT_FALSE(handle.CanAccessDataForOrigin(url::Origin::Create(file_url)));
  EXPECT_TRUE(handle.CanAccessDataForOrigin(url::Origin::Create(foo_http_url)));
  EXPECT_TRUE(handle.CanAccessDataForOrigin(url::Origin::Create(foo_blob_url)));
  EXPECT_TRUE(
      handle.CanAccessDataForOrigin(url::Origin::Create(foo_filesystem_url)));
  EXPECT_FALSE(
      handle.CanAccessDataForOrigin(url::Origin::Create(bar_http_url)));

  // Invalidate handle so it does not preserve security state beyond Remove().
  handle = ChildProcessSecurityPolicyImpl::Handle();

  p->Remove(kRendererID);

  // Post a task to the IO loop that then posts a task to the UI loop.
  // This should cause the |run_loop| to return after the removal has completed.
  base::RunLoop run_loop;
  GetIOThreadTaskRunner({})->PostTaskAndReply(FROM_HERE, base::DoNothing(),
                                              run_loop.QuitClosure());
  run_loop.Run();

  // Verify invalid ID is rejected now that Remove() has completed.
  for (auto url : kAllTestUrls) {
    EXPECT_FALSE(
        p->CanAccessDataForOrigin(kRendererID, url::Origin::Create(url)))
        << url;
    EXPECT_FALSE(handle.CanAccessDataForOrigin(url::Origin::Create(url)))
        << url;
  }
}

TEST_F(ChildProcessSecurityPolicyTest, CanAccessDataForOrigin_Origin) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  const std::vector<const char*> foo_urls = {
      "http://foo.com/index.html",
      "blob:http://foo.com/43d75119-d7af-4471-a293-07c6b3d7e61a",
      "filesystem:http://foo.com/temporary/test.html",
      // Port differences considered equal.
      "http://foo.com:1234/index.html",
      "blob:http://foo.com:1234/43d75119-d7af-4471-a293-07c6b3d7e61a",
      "filesystem:http://foo.com:1234/temporary/test.html",
      // TODO(acolwell): data: should be in |non_foo_urls| in the long-term.
      "data:text/html,Hello!"};

  const std::vector<const char*> non_foo_urls = {
      "file:///etc/passwd",
      "http://bar.com/index.html",
      "blob:http://bar.com/43d75119-d7af-4471-a293-07c6b3d7e61a",
      "filesystem:http://bar.com/temporary/test.html",
      // foo.com with a different scheme not considered equal.
      "https://foo.com/index.html",
      "blob:https://foo.com/43d75119-d7af-4471-a293-07c6b3d7e61a",
      "filesystem:https://foo.com/temporary/test.html"};

  std::vector<url::Origin> foo_origins;
  std::vector<url::Origin> non_foo_origins;
  std::vector<url::Origin> all_origins;
  for (auto* url : foo_urls) {
    auto origin = url::Origin::Create(GURL(url));
    foo_origins.push_back(origin);
    all_origins.push_back(origin);
  }
  auto foo_origin = url::Origin::Create(GURL("http://foo.com"));
  auto opaque_with_foo_precursor = foo_origin.DeriveNewOpaqueOrigin();
  foo_origins.push_back(opaque_with_foo_precursor);
  all_origins.push_back(opaque_with_foo_precursor);

  for (auto* url : non_foo_urls) {
    auto origin = url::Origin::Create(GURL(url));
    non_foo_origins.push_back(origin);
    all_origins.push_back(origin);
  }
  url::Origin opaque_origin_without_precursor;
  // TODO(acolwell): This should be in |non_foo_origins| in the long-term.
  foo_origins.push_back(opaque_origin_without_precursor);
  all_origins.push_back(opaque_origin_without_precursor);

  auto opaque_with_bar_precursor =
      url::Origin::Create(GURL("http://bar.com")).DeriveNewOpaqueOrigin();
  non_foo_origins.push_back(opaque_with_bar_precursor);
  all_origins.push_back(opaque_with_bar_precursor);

  // Test invalid process ID for all cases.
  for (const auto& origin : all_origins)
    EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;

  TestBrowserContext browser_context;
  p->AddForTesting(kRendererID, &browser_context);

  // Verify unlocked process permissions.
  for (const auto& origin : all_origins) {
    if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
      if (origin.opaque() &&
          !origin.GetTupleOrPrecursorTupleIfOpaque().IsValid()) {
        EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
      } else {
        EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
      }
    } else {
      EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
    }
  }

  // Isolate |foo_origin| so we can't get a default SiteInstance.
  p->AddFutureIsolatedOrigins({foo_origin}, IsolatedOriginSource::TEST,
                              &browser_context);

  // Lock process to |foo_origin| origin.
  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&browser_context, foo_origin.GetURL());
  EXPECT_FALSE(foo_instance->IsDefaultSiteInstance());
  LockProcessIfNeeded(kRendererID, &browser_context, foo_origin.GetURL());

  // Verify that access is no longer allowed for origins that are not associated
  // with foo.com.
  for (const auto& origin : foo_origins)
    EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;

  for (const auto& origin : non_foo_origins)
    EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;

  p->Remove(kRendererID);

  // Post a task to the IO loop that then posts a task to the UI loop.
  // This should cause the |run_loop| to return after the removal has completed.
  base::RunLoop run_loop;
  GetIOThreadTaskRunner({})->PostTaskAndReply(FROM_HERE, base::DoNothing(),
                                              run_loop.QuitClosure());
  run_loop.Run();

  // Verify invalid ID is rejected now that Remove() has completed.
  for (const auto& origin : all_origins)
    EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
}

// Test the granting of origin permissions, and their interactions with
// granting scheme permissions.
TEST_F(ChildProcessSecurityPolicyTest, OriginGranting) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  GURL url_foo1(GetWebUIURL("foo/resource1"));
  GURL url_foo2(GetWebUIURL("foo/resource2"));
  GURL url_bar(GetWebUIURL("bar/resource3"));

  p->AddForTesting(kRendererID, browser_context());
  LockProcessIfNeeded(kRendererID, browser_context(), url_foo1);

  EXPECT_FALSE(p->CanRequestURL(kRendererID, url_foo1));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
  EXPECT_TRUE(p->CanRedirectToURL(url_bar));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo1));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));

  p->GrantRequestOrigin(kRendererID, url::Origin::Create(url_foo1));

  EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo1));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
  EXPECT_TRUE(p->CanRedirectToURL(url_bar));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo1));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));

  p->GrantCommitOrigin(kRendererID, url::Origin::Create(url_foo1));

  EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo1));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
  EXPECT_TRUE(p->CanRedirectToURL(url_bar));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo1));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));

  // Make sure this doesn't overwrite the earlier commit grants.
  p->GrantRequestOrigin(kRendererID, url::Origin::Create(url_foo1));

  EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo1));
  EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
  EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
  EXPECT_TRUE(p->CanRedirectToURL(url_bar));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo1));
  EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo2));
  EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));

  p->Remove(kRendererID);
}

#define LOCKED_EXPECT_THAT(lock, value, matcher) \
  do {                                           \
    base::AutoLock auto_lock(lock);              \
    EXPECT_THAT(value, matcher);                 \
  } while (0);

// Verifies ChildProcessSecurityPolicyImpl::AddFutureIsolatedOrigins method.
TEST_F(ChildProcessSecurityPolicyTest, AddFutureIsolatedOrigins) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
  url::Origin quxfoo = url::Origin::Create(GURL("https://qux.foo.com/"));
  url::Origin baz_http = url::Origin::Create(GURL("http://baz.com/"));
  url::Origin baz_http_8000 = url::Origin::Create(GURL("http://baz.com:8000/"));
  url::Origin baz_https_8000 =
      url::Origin::Create(GURL("https://baz.com:8000/"));
  url::Origin invalid_etld = url::Origin::Create(GURL("https://gov/"));

  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Verify deduplication of the argument.
  p->AddFutureIsolatedOrigins({foo, bar, bar}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(foo),
                                    GetIsolatedOriginEntry(bar)));

  // Verify that the old set is extended (not replaced).
  p->AddFutureIsolatedOrigins({baz}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(foo),
                                    GetIsolatedOriginEntry(bar),
                                    GetIsolatedOriginEntry(baz)));

  // Verify deduplication against the old set.
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(foo),
                                    GetIsolatedOriginEntry(bar),
                                    GetIsolatedOriginEntry(baz)));

  // Verify deduplication considers scheme and port differences.  Note that
  // origins that differ only in ports map to the same key.
  p->AddFutureIsolatedOrigins({baz, baz_http_8000, baz_https_8000},
                              IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(
          GetIsolatedOriginEntry(foo), GetIsolatedOriginEntry(bar),
          GetIsolatedOriginEntry(baz), GetIsolatedOriginEntry(baz_http)));

  // Verify that adding an origin that is invalid for isolation will 1) log a
  // warning and 2) won't CHECK or crash the browser process, 3) will not add
  // the invalid origin, but will add the remaining origins passed to
  // AddFutureIsolatedOrigins.  Note that the new |quxfoo| origin should map to
  // the same key (i.e., the https://foo.com/ site URL) as the existing |foo|
  // origin.
  {
    base::test::MockLog mock_log;
    EXPECT_CALL(mock_log,
                Log(::logging::LOG_ERROR, testing::_, testing::_, testing::_,
                    testing::HasSubstr(invalid_etld.Serialize())))
        .Times(1);

    mock_log.StartCapturingLogs();
    p->AddFutureIsolatedOrigins({quxfoo, invalid_etld},
                                IsolatedOriginSource::TEST);
    LOCKED_EXPECT_THAT(
        p->isolated_origins_lock_, p->isolated_origins_,
        testing::UnorderedElementsAre(
            GetIsolatedOriginEntry(foo, quxfoo), GetIsolatedOriginEntry(bar),
            GetIsolatedOriginEntry(baz), GetIsolatedOriginEntry(baz_http)));
  }

  // Verify that adding invalid origins via the string variant of
  // AddFutureIsolatedOrigins() logs a warning.
  {
    base::test::MockLog mock_log;
    EXPECT_CALL(mock_log, Log(::logging::LOG_ERROR, testing::_, testing::_,
                              testing::_, testing::HasSubstr("about:blank")))
        .Times(1);

    mock_log.StartCapturingLogs();
    p->AddFutureIsolatedOrigins("about:blank", IsolatedOriginSource::TEST);
  }

  p->RemoveIsolatedOriginForTesting(foo);
  p->RemoveIsolatedOriginForTesting(quxfoo);
  p->RemoveIsolatedOriginForTesting(bar);
  p->RemoveIsolatedOriginForTesting(baz);
  p->RemoveIsolatedOriginForTesting(baz_http);

  // We should have removed all isolated origins at this point.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());
}

TEST_F(ChildProcessSecurityPolicyTest, IsolateAllSuborigins) {
  url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
  IsolatedOriginPattern etld1_wild("https://[*.]foo.com");
  IsolatedOriginPattern etld2_wild("https://[*.]bar.foo.com");
  url::Origin etld1 = url::Origin::Create(GURL("https://foo.com"));
  url::Origin etld2 = url::Origin::Create(GURL("https://bar.foo.com"));

  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Check we can add a single wildcard origin.
  p->AddFutureIsolatedOrigins({etld1_wild}, IsolatedOriginSource::TEST);

  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld1, true)));

  // Add a conventional origin and check they can live side by side.
  p->AddFutureIsolatedOrigins({qux}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld1, true),
                                    GetIsolatedOriginEntry(qux, false)));

  // Check that a wildcard domain within another wildcard domain can be added.
  p->AddFutureIsolatedOrigins({etld2_wild}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::UnorderedElementsAre(
                         GetIsolatedOriginEntry(etld1, etld2, true, true),
                         GetIsolatedOriginEntry(qux, false)));

  // Check that removing a single wildcard domain, that contains another
  // wildcard domain, doesn't affect the isolating behavior of the original
  // wildcard domain.
  p->RemoveIsolatedOriginForTesting(etld1);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld2, true),
                                    GetIsolatedOriginEntry(qux, false)));

  // Removing remaining domains.
  p->RemoveIsolatedOriginForTesting(qux);
  p->RemoveIsolatedOriginForTesting(etld2);

  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());
}

// Verify that the isolation behavior for wildcard and non-wildcard origins,
// singly or in concert, behaves correctly via calls to GetSiteForURL().
TEST_F(ChildProcessSecurityPolicyTest, WildcardAndNonWildcardOrigins) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // There should be no isolated origins before this test starts.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Construct a simple case, a single isolated origin.
  //  IsolatedOriginPattern isolated("https://isolated.com");
  IsolatedOriginPattern inner_isolated("https://inner.isolated.com");
  IsolatedOriginPattern wildcard("https://[*.]wildcard.com");
  IsolatedOriginPattern inner_wildcard("https://[*.]inner.wildcard.com");

  GURL isolated_url("https://isolated.com");
  GURL inner_isolated_url("https://inner.isolated.com");
  GURL host_inner_isolated_url("https://host.inner.isolated.com");
  GURL wildcard_url("https://wildcard.com");
  GURL inner_wildcard_url("https://inner.wildcard.com");
  GURL host_inner_wildcard_url("https://host.inner.wildcard.com");
  GURL unrelated_url("https://unrelated.com");

  // Verify the isolation behavior of the test patterns before isolating any
  // domains.
  std::map<GURL, GURL> origins_site_test_map{
      {isolated_url, isolated_url},
      {inner_isolated_url, isolated_url},
      {host_inner_isolated_url, isolated_url},
      {wildcard_url, wildcard_url},
      {inner_wildcard_url, wildcard_url},
      {host_inner_wildcard_url, wildcard_url},
      {unrelated_url, unrelated_url},
  };
  CheckGetSiteForURL(browser_context(), origins_site_test_map);

  // Add |wildcard|, a wildcard origin from a different domain, then verify that
  // the existing behavior of |isolated_url| and |inner_isolated_url| remains
  // unaffected, while all subdomains of wildcard.com are returned as unique
  // sites.
  p->AddFutureIsolatedOrigins({wildcard}, IsolatedOriginSource::TEST);
  origins_site_test_map[inner_wildcard_url] = inner_wildcard_url;
  origins_site_test_map[host_inner_wildcard_url] = host_inner_wildcard_url;
  CheckGetSiteForURL(browser_context(), origins_site_test_map);

  // Add |inner_isolated|, then verify that querying for |inner_isolated_url|
  // returns |inner_isolated_url| while leaving the wildcard origins unaffected.
  p->AddFutureIsolatedOrigins({inner_isolated}, IsolatedOriginSource::TEST);
  origins_site_test_map[inner_isolated_url] = inner_isolated_url;
  origins_site_test_map[host_inner_isolated_url] = inner_isolated_url;
  CheckGetSiteForURL(browser_context(), origins_site_test_map);

  // Add |inner_wildcard|. This should not change the behavior of the test
  // above as all subdomains of |inner_wildcard| are contained within
  // |wildcard|.
  p->AddFutureIsolatedOrigins({inner_wildcard}, IsolatedOriginSource::TEST);
  CheckGetSiteForURL(browser_context(), origins_site_test_map);

  p->RemoveIsolatedOriginForTesting(wildcard.origin());
  p->RemoveIsolatedOriginForTesting(inner_isolated.origin());
  p->RemoveIsolatedOriginForTesting(inner_wildcard.origin());

  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());
}

TEST_F(ChildProcessSecurityPolicyTest, WildcardAndNonWildcardEmbedded) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // There should be no isolated origins before this test starts.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  {
    // Test the behavior of a wildcard origin contained within a single
    // isolated origin. Removing the isolated origin should have no effect on
    // the wildcard origin.
    IsolatedOriginPattern isolated("https://isolated.com");
    IsolatedOriginPattern wildcard_isolated(
        "https://[*.]wildcard.isolated.com");

    GURL isolated_url("https://isolated.com");
    GURL a_isolated_url("https://a.isolated.com");
    GURL wildcard_isolated_url("https://wildcard.isolated.com");
    GURL a_wildcard_isolated_url("https://a.wildcard.isolated.com");

    p->AddFutureIsolatedOrigins({isolated, wildcard_isolated},
                                IsolatedOriginSource::TEST);
    std::map<GURL, GURL> origin_site_map{
        {isolated_url, isolated_url},
        {a_isolated_url, isolated_url},
        {wildcard_isolated_url, wildcard_isolated_url},
        {a_wildcard_isolated_url, a_wildcard_isolated_url},
    };

    CheckGetSiteForURL(browser_context(), origin_site_map);

    p->RemoveIsolatedOriginForTesting(isolated.origin());
    p->RemoveIsolatedOriginForTesting(wildcard_isolated.origin());
  }

  // No isolated origins should persist between tests.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  {
    // A single isolated origin is nested within a wildcard origin. In this
    // scenario the wildcard origin supersedes isolated origins.
    IsolatedOriginPattern wildcard("https://[*.]wildcard.com");
    IsolatedOriginPattern isolated_wildcard("https://isolated.wildcard.com");

    GURL wildcard_url("https://wildcard.com");
    GURL a_wildcard_url("https://a.wildcard.com");
    GURL isolated_wildcard_url("https://isolated.wildcard.com");
    GURL a_isolated_wildcard_url("https://a.isolated.wildcard.com");

    p->AddFutureIsolatedOrigins({wildcard, isolated_wildcard},
                                IsolatedOriginSource::TEST);
    std::map<GURL, GURL> origin_site_map{
        {wildcard_url, wildcard_url},
        {a_wildcard_url, a_wildcard_url},
        {isolated_wildcard_url, isolated_wildcard_url},
        {a_isolated_wildcard_url, a_isolated_wildcard_url},
    };

    CheckGetSiteForURL(browser_context(), origin_site_map);

    p->RemoveIsolatedOriginForTesting(wildcard.origin());
    p->RemoveIsolatedOriginForTesting(isolated_wildcard.origin());
  }

  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  {
    // Nest wildcard isolated origins within each other. Verify that removing
    // the outer wildcard origin doesn't affect the inner one.
    IsolatedOriginPattern outer("https://[*.]outer.com");
    IsolatedOriginPattern inner("https://[*.]inner.outer.com");

    GURL outer_url("https://outer.com");
    GURL a_outer_url("https://a.outer.com");
    GURL inner_url("https://inner.outer.com");
    GURL a_inner_url("https://a.inner.outer.com");

    p->AddFutureIsolatedOrigins({inner, outer}, IsolatedOriginSource::TEST);

    std::map<GURL, GURL> origin_site_map{
        {outer_url, outer_url},
        {a_outer_url, a_outer_url},
        {inner_url, inner_url},
        {a_inner_url, a_inner_url},
    };

    CheckGetSiteForURL(browser_context(), origin_site_map);
    p->RemoveIsolatedOriginForTesting(outer.origin());
    p->RemoveIsolatedOriginForTesting(inner.origin());
  }

  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Verify that adding a wildcard domain then a then a conventional domain
  // doesn't affect the isolating behavior of the wildcard, i.e. whichever
  // isolated domain is added entered 'wins'.
  {
    IsolatedOriginPattern wild("https://[*.]bar.foo.com");
    IsolatedOriginPattern single("https://bar.foo.com");

    GURL host_url("https://host.bar.foo.com");

    p->AddFutureIsolatedOrigins({wild}, IsolatedOriginSource::TEST);
    std::map<GURL, GURL> origin_site_map{
        {host_url, host_url},
    };

    CheckGetSiteForURL(browser_context(), origin_site_map);

    p->AddFutureIsolatedOrigins({single}, IsolatedOriginSource::TEST);

    CheckGetSiteForURL(browser_context(), origin_site_map);

    p->RemoveIsolatedOriginForTesting(wild.origin());
    p->RemoveIsolatedOriginForTesting(single.origin());
  }

  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Verify the first domain added remains dominant in the case of differing
  // wildcard and non-wildcard statuses.
  {
    IsolatedOriginPattern wild("https://[*.]bar.foo.com");
    IsolatedOriginPattern single("https://bar.foo.com");

    GURL host_url("https://host.bar.foo.com");
    GURL domain_url("https://bar.foo.com");

    p->AddFutureIsolatedOrigins({single}, IsolatedOriginSource::TEST);
    std::map<GURL, GURL> origin_site_map{
        {host_url, domain_url},
    };

    CheckGetSiteForURL(browser_context(), origin_site_map);

    p->AddFutureIsolatedOrigins({wild}, IsolatedOriginSource::TEST);

    CheckGetSiteForURL(browser_context(), origin_site_map);

    p->RemoveIsolatedOriginForTesting(wild.origin());
    p->RemoveIsolatedOriginForTesting(single.origin());
  }

  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());
}

// Verifies that isolated origins only apply to future BrowsingInstances.
TEST_F(ChildProcessSecurityPolicyTest, DynamicIsolatedOrigins) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
  url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Save the next BrowsingInstance ID to be created.  Because unit tests run
  // in batches, this isn't guaranteed to always be 1, for example if a
  // previous test in the same batch had already created a SiteInstance and
  // BrowsingInstance.
  BrowsingInstanceId initial_id(SiteInstanceImpl::NextBrowsingInstanceId());

  // Isolate foo.com and bar.com.
  p->AddFutureIsolatedOrigins({foo, bar}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(initial_id, foo),
                                    GetIsolatedOriginEntry(initial_id, bar)));

  // Isolating bar.com again should have no effect.
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(initial_id, foo),
                                    GetIsolatedOriginEntry(initial_id, bar)));

  // Create a new BrowsingInstance.  Its ID will be |initial_id|.
  TestBrowserContext context;
  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://foo.com/"));
  EXPECT_EQ(initial_id,
            foo_instance->GetIsolationContext().browsing_instance_id());
  EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id.value() + 1),
            SiteInstanceImpl::NextBrowsingInstanceId());

  // Isolate baz.com.  This will apply to BrowsingInstances with IDs
  // |initial_id + 1| and above.
  p->AddFutureIsolatedOrigins({baz}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::UnorderedElementsAre(
                         GetIsolatedOriginEntry(initial_id, foo),
                         GetIsolatedOriginEntry(initial_id, bar),
                         GetIsolatedOriginEntry(initial_id.value() + 1, baz)));

  // Isolating bar.com again should not update the old BrowsingInstance ID.
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::UnorderedElementsAre(
                         GetIsolatedOriginEntry(initial_id, foo),
                         GetIsolatedOriginEntry(initial_id, bar),
                         GetIsolatedOriginEntry(initial_id.value() + 1, baz)));

  // Create another BrowsingInstance.
  scoped_refptr<SiteInstanceImpl> bar_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://bar.com/"));
  EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id.value() + 1),
            bar_instance->GetIsolationContext().browsing_instance_id());
  EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id.value() + 2),
            SiteInstanceImpl::NextBrowsingInstanceId());

  // Isolate qux.com.
  p->AddFutureIsolatedOrigins({qux}, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::UnorderedElementsAre(
                         GetIsolatedOriginEntry(initial_id, foo),
                         GetIsolatedOriginEntry(initial_id, bar),
                         GetIsolatedOriginEntry(initial_id.value() + 1, baz),
                         GetIsolatedOriginEntry(initial_id.value() + 2, qux)));

  // Check IsIsolatedOrigin() only returns isolated origins if they apply to
  // the provided BrowsingInstance. foo and bar should apply in
  // BrowsingInstance ID |initial_id| and above, baz in IDs |initial_id + 1|
  // and above, and qux in |initial_id + 2| and above.
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id, qux));

  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 1, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 1, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 1, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id.value() + 1, qux));

  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 2, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 2, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 2, baz));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 2, qux));

  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 42, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 42, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 42, baz));
  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id.value() + 42, qux));

  // An IsolationContext constructed without a BrowsingInstance ID should
  // return the latest available isolated origins.
  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), foo,
                                  false /* origin_requests_isolation */));
  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), bar,
                                  false /* origin_requests_isolation */));
  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), baz,
                                  false /* origin_requests_isolation */));
  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), qux,
                                  false /* origin_requests_isolation */));

  p->RemoveIsolatedOriginForTesting(foo);
  p->RemoveIsolatedOriginForTesting(bar);
  p->RemoveIsolatedOriginForTesting(baz);
  p->RemoveIsolatedOriginForTesting(qux);
}

// Check that an unsuccessful isolated origin lookup for a URL with an empty
// host doesn't crash. See https://crbug.com/882686.
TEST_F(ChildProcessSecurityPolicyTest, IsIsolatedOriginWithEmptyHost) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  TestBrowserContext context;
  EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
                                   url::Origin::Create(GURL()),
                                   false /* origin_requests_isolation */));
  EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
                                   url::Origin::Create(GURL("file:///foo")),
                                   false /* origin_requests_isolation */));
}

// Verifies the API for restricting isolated origins to a specific
// BrowserContext (profile).  Namely, the same origin may be added for
// different BrowserContexts, possibly with different BrowsingInstanceId
// cutoffs.  Attempts to re-add an origin for the same profile should be
// ignored.  Also, once an isolated origin is added globally for all profiles,
// future attempts to re-add it (for any profile) should also be ignored.
TEST_F(ChildProcessSecurityPolicyTest,
       IsolatedOriginsForSpecificBrowserContexts) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Save the next BrowsingInstance ID to be created.  Because unit tests run
  // in batches, this isn't guaranteed to always be 1, for example if a
  // previous test in the same batch had already created a SiteInstance and
  // BrowsingInstance.
  BrowsingInstanceId initial_id(SiteInstanceImpl::NextBrowsingInstanceId());

  // Isolate foo.com globally (for all BrowserContexts).
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST);

  TestBrowserContext context1, context2;

  // Isolate bar.com in |context1|.
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context1);

  // bar.com should be isolated for |context1|, but not |context2|. foo.com
  // should be isolated for all contexts.
  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));

  // Create a new BrowsingInstance.  Its ID will be |initial_id|.
  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&context1, GURL("https://foo.com/"));
  EXPECT_EQ(initial_id,
            foo_instance->GetIsolationContext().browsing_instance_id());
  EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id.value() + 1),
            SiteInstanceImpl::NextBrowsingInstanceId());
  EXPECT_EQ(&context1, foo_instance->GetIsolationContext()
                           .browser_or_resource_context()
                           .ToBrowserContext());

  // Isolating foo.com in |context1| is allowed and should add a new
  // IsolatedOriginEntry.  This wouldn't introduce any additional isolation,
  // since foo.com is already isolated globally, but the new entry is
  // important, e.g. for persisting profile-specific isolated origins across
  // restarts.
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST, &context1);
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id, foo));

  // Isolating bar.com in |context1| again should have no effect.
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context1);
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));

  // Isolate bar.com for |context2|, which should add a new
  // IsolatedOriginEntry.  Verify that the isolation took effect for
  // |initial_id + 1| (the current BrowsingInstance ID cutoff) only.
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context2);
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id.value() + 1, bar));

  // Verify the bar.com is still isolated in |context1| starting with
  // |initial_id|.
  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id.value() + 1, bar));

  // Create another BrowserContext; only foo.com should be isolated there.
  TestBrowserContext context3;
  EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id.value() + 1, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id.value() + 1, bar));

  // Now, add bar.com as a globally isolated origin.  This should make it apply
  // to context3 as well, but only in initial_id + 1 (the current
  // BrowsingInstance ID cutoff).
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST);
  EXPECT_EQ(3, GetIsolatedOriginEntryCount(bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id.value() + 1, bar));

  // An attempt to re-add bar.com for a new profile should create a new
  // IsolatedOriginEntry, though it wouldn't provide any additional isolation,
  // since bar.com is already isolated globally.
  TestBrowserContext context4;
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context4);
  EXPECT_EQ(4, GetIsolatedOriginEntryCount(bar));

  p->RemoveIsolatedOriginForTesting(foo);
  p->RemoveIsolatedOriginForTesting(bar);
}

// This test ensures that isolated origins associated with a specific
// BrowserContext are removed when that BrowserContext is destroyed.
TEST_F(ChildProcessSecurityPolicyTest,
       IsolatedOriginsRemovedWhenBrowserContextDestroyed) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin sub_foo = url::Origin::Create(GURL("https://sub.foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Save the next BrowsingInstance ID to be created.  Because unit tests run
  // in batches, this isn't guaranteed to always be 1, for example if a
  // previous test in the same batch had already created a SiteInstance and
  // BrowsingInstance.
  BrowsingInstanceId initial_id(SiteInstanceImpl::NextBrowsingInstanceId());

  std::unique_ptr<TestBrowserContext> context1(new TestBrowserContext());
  std::unique_ptr<TestBrowserContext> context2(new TestBrowserContext());

  // Isolate foo.com in |context1|.  Note that sub.foo.com should also be
  // considered isolated in |context1|, since it's a subdomain of foo.com.
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST,
                              context1.get());
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
  EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, sub_foo));
  EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));

  // Isolate sub.foo.com and bar.com in |context2|.
  p->AddFutureIsolatedOrigins({sub_foo, bar}, IsolatedOriginSource::TEST,
                              context2.get());
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(sub_foo));
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, foo));

  // Isolate baz.com in both BrowserContexts.
  p->AddFutureIsolatedOrigins({baz}, IsolatedOriginSource::TEST,
                              context1.get());
  p->AddFutureIsolatedOrigins({baz}, IsolatedOriginSource::TEST,
                              context2.get());

  EXPECT_EQ(2, GetIsolatedOriginEntryCount(baz));
  EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, baz));
  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, baz));

  // Remove |context1|.  foo.com should no longer be in the isolated_origins_
  // map, and the other origins should be isolated only in |context2|.
  context1.reset();

  EXPECT_EQ(0, GetIsolatedOriginEntryCount(foo));
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(sub_foo));
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(baz));
  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, baz));

  // Remove |context2| and ensure the remaining entries are removed.
  context2.reset();
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());
}

TEST_F(ChildProcessSecurityPolicyTest, IsolatedOriginPattern) {
  const base::StringPiece etld1_wild("https://[*.]foo.com");
  url::Origin etld1_wild_origin = url::Origin::Create(GURL("https://foo.com"));
  IsolatedOriginPattern p(etld1_wild);
  EXPECT_TRUE(p.isolate_all_subdomains());
  EXPECT_TRUE(p.is_valid());
  EXPECT_EQ(p.origin(), etld1_wild_origin);

  const base::StringPiece etld2_wild("https://[*.]bar.foo.com");
  url::Origin etld2_wild_origin =
      url::Origin::Create(GURL("https://bar.foo.com"));
  bool result = p.Parse(etld2_wild);
  EXPECT_TRUE(result);
  EXPECT_TRUE(p.isolate_all_subdomains());
  EXPECT_TRUE(p.is_valid());
  EXPECT_EQ(p.origin(), etld2_wild_origin);
  EXPECT_FALSE(p.origin().opaque());

  const base::StringPiece etld1("https://baz.com");
  url::Origin etld1_origin = url::Origin::Create(GURL("https://baz.com"));
  result = p.Parse(etld1);
  EXPECT_TRUE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_TRUE(p.is_valid());
  EXPECT_EQ(p.origin(), etld1_origin);
  EXPECT_FALSE(p.origin().opaque());

  const base::StringPiece bad_scheme("ftp://foo.com");
  result = p.Parse(bad_scheme);
  EXPECT_FALSE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_FALSE(p.is_valid());
  EXPECT_TRUE(p.origin().opaque());

  const base::StringPiece no_scheme_sep("httpsfoo.com");
  result = p.Parse(no_scheme_sep);
  EXPECT_FALSE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_FALSE(p.is_valid());
  EXPECT_TRUE(p.origin().opaque());

  const base::StringPiece bad_registry("https://co.uk");
  result = p.Parse(bad_registry);
  EXPECT_FALSE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_FALSE(p.is_valid());
  EXPECT_TRUE(p.origin().opaque());

  const base::StringPiece trailing_dot("https://bar.com.");
  result = p.Parse(trailing_dot);
  EXPECT_FALSE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_FALSE(p.is_valid());
  EXPECT_TRUE(p.origin().opaque());

  const base::StringPiece ip_addr("https://10.20.30.40");
  url::Origin ip_origin = url::Origin::Create(GURL("https://10.20.30.40"));
  result = p.Parse(ip_addr);
  EXPECT_TRUE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_FALSE(p.origin().opaque());
  EXPECT_TRUE(p.is_valid());
  EXPECT_EQ(p.origin(), ip_origin);

  const base::StringPiece wild_ip_addr("https://[*.]10.20.30.40");
  result = p.Parse(wild_ip_addr);
  EXPECT_FALSE(result);
  EXPECT_FALSE(p.isolate_all_subdomains());
  EXPECT_FALSE(p.is_valid());

  const url::Origin bad_origin;
  IsolatedOriginPattern bad_pattern(bad_origin);
  EXPECT_FALSE(bad_pattern.isolate_all_subdomains());
  EXPECT_TRUE(bad_pattern.origin().opaque());
  EXPECT_FALSE(p.is_valid());
}

// This test adds isolated origins from various sources and verifies that
// GetIsolatedOrigins() properly restricts lookups by source.
TEST_F(ChildProcessSecurityPolicyTest, GetIsolatedOrigins) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
  url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());

  // Add isolated origins from various sources, and verify that
  // GetIsolatedOrigins properly restricts lookups by source.
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST);
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::FIELD_TRIAL);

  EXPECT_THAT(p->GetIsolatedOrigins(), testing::UnorderedElementsAre(foo, bar));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST),
              testing::UnorderedElementsAre(foo));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::FIELD_TRIAL),
              testing::UnorderedElementsAre(bar));

  p->AddFutureIsolatedOrigins({baz}, IsolatedOriginSource::POLICY);
  p->AddFutureIsolatedOrigins({qux}, IsolatedOriginSource::COMMAND_LINE);

  EXPECT_THAT(p->GetIsolatedOrigins(),
              testing::UnorderedElementsAre(foo, bar, baz, qux));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST),
              testing::UnorderedElementsAre(foo));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::FIELD_TRIAL),
              testing::UnorderedElementsAre(bar));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY),
              testing::UnorderedElementsAre(baz));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::COMMAND_LINE),
              testing::UnorderedElementsAre(qux));

  p->RemoveIsolatedOriginForTesting(foo);
  p->RemoveIsolatedOriginForTesting(bar);
  p->RemoveIsolatedOriginForTesting(baz);
  p->RemoveIsolatedOriginForTesting(qux);
  EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
}

// This test adds isolated origins from various sources as well as restricted
// to particular profiles, and verifies that GetIsolatedOrigins() properly
// restricts lookups by both source and profile.
TEST_F(ChildProcessSecurityPolicyTest, GetIsolatedOriginsWithProfile) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
  url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  TestBrowserContext context1, context2;

  // Initially there should be no isolated origins.
  EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());

  // Add a global isolated origin.  Note that since it applies to all profiles,
  // GetIsolatedOrigins() should return it for any passed-in profile.
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST);

  // Add some per-profile isolated origins.
  p->AddFutureIsolatedOrigins({bar}, IsolatedOriginSource::USER_TRIGGERED,
                              &context1);
  p->AddFutureIsolatedOrigins({baz}, IsolatedOriginSource::POLICY, &context2);
  p->AddFutureIsolatedOrigins({qux}, IsolatedOriginSource::USER_TRIGGERED,
                              &context1);
  p->AddFutureIsolatedOrigins({qux}, IsolatedOriginSource::USER_TRIGGERED,
                              &context2);

  EXPECT_THAT(p->GetIsolatedOrigins(), testing::UnorderedElementsAre(foo));

  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST),
              testing::UnorderedElementsAre(foo));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST, &context1),
              testing::UnorderedElementsAre(foo));
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST, &context2),
              testing::UnorderedElementsAre(foo));

  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::USER_TRIGGERED),
              testing::IsEmpty());
  EXPECT_THAT(
      p->GetIsolatedOrigins(IsolatedOriginSource::USER_TRIGGERED, &context1),
      testing::UnorderedElementsAre(bar, qux));
  EXPECT_THAT(
      p->GetIsolatedOrigins(IsolatedOriginSource::USER_TRIGGERED, &context2),
      testing::UnorderedElementsAre(qux));

  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY),
              testing::IsEmpty());
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY, &context1),
              testing::IsEmpty());
  EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY, &context2),
              testing::UnorderedElementsAre(baz));

  p->RemoveIsolatedOriginForTesting(foo);
  p->RemoveIsolatedOriginForTesting(bar);
  p->RemoveIsolatedOriginForTesting(baz);
  p->RemoveIsolatedOriginForTesting(qux);
  EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
}

TEST_F(ChildProcessSecurityPolicyTest, IsolatedOriginPatternEquality) {
  std::string foo("https://foo.com");
  std::string foo_port("https://foo.com:8000");
  std::string foo_path("https://foo.com/some/path");

  EXPECT_EQ(IsolatedOriginPattern(foo), IsolatedOriginPattern(foo_port));
  EXPECT_EQ(IsolatedOriginPattern(foo), IsolatedOriginPattern(foo_path));

  std::string wild_foo("https://[*.]foo.com");
  std::string wild_foo_port("https://[*.]foo.com:8000");
  std::string wild_foo_path("https://[*.]foo.com/some/path");

  EXPECT_EQ(IsolatedOriginPattern(wild_foo),
            IsolatedOriginPattern(wild_foo_port));
  EXPECT_EQ(IsolatedOriginPattern(wild_foo),
            IsolatedOriginPattern(wild_foo_path));

  EXPECT_FALSE(IsolatedOriginPattern(foo) == IsolatedOriginPattern(wild_foo));
}

// Verifies parsing logic in SiteIsolationPolicy::ParseIsolatedOrigins.
TEST_F(ChildProcessSecurityPolicyTest, ParseIsolatedOrigins) {
  EXPECT_THAT(ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(""),
              testing::IsEmpty());

  // Single simple, valid origin.
  EXPECT_THAT(
      ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
          "http://isolated.foo.com"),
      testing::ElementsAre(IsolatedOriginPattern("http://isolated.foo.com")));

  // Multiple comma-separated origins.
  EXPECT_THAT(
      ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
          "http://a.com,https://b.com,,https://c.com:8000"),
      testing::ElementsAre(IsolatedOriginPattern("http://a.com"),
                           IsolatedOriginPattern("https://b.com"),
                           IsolatedOriginPattern("https://c.com:8000")));

  // ParseIsolatedOrigins should not do any deduplication (that is the job of
  // ChildProcessSecurityPolicyImpl::AddFutureIsolatedOrigins).
  EXPECT_THAT(
      ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
          "https://b.com,https://b.com,https://b.com:1234"),
      testing::ElementsAre(IsolatedOriginPattern("https://b.com"),
                           IsolatedOriginPattern("https://b.com"),
                           IsolatedOriginPattern("https://b.com:1234")));

  // A single wildcard origin.
  EXPECT_THAT(
      ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
          "https://[*.]wild.foo.com"),
      testing::ElementsAre(IsolatedOriginPattern("https://[*.]wild.foo.com")));

  // A mixture of wildcard and non-wildcard origins.
  EXPECT_THAT(
      ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
          "https://[*.]wild.foo.com,https://isolated.foo.com"),
      testing::ElementsAre(IsolatedOriginPattern("https://[*.]wild.foo.com"),
                           IsolatedOriginPattern("https://isolated.foo.com")));
}

// Verify that the default port for an isolated origin's scheme is returned
// during a lookup, not the port of the origin requested.
TEST_F(ChildProcessSecurityPolicyTest, WildcardDefaultPort) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());

  url::Origin isolated_origin_with_port =
      url::Origin::Create(GURL("https://isolated.com:1234"));
  url::Origin isolated_origin =
      url::Origin::Create(GURL("https://isolated.com"));

  url::Origin wild_with_port =
      url::Origin::Create(GURL("https://a.wild.com:5678"));
  url::Origin wild_origin = url::Origin::Create(GURL("https://a.wild.com"));
  IsolatedOriginPattern wild_pattern("https://[*.]wild.com:5678");

  p->AddFutureIsolatedOrigins({isolated_origin_with_port},
                              IsolatedOriginSource::TEST);
  p->AddFutureIsolatedOrigins({wild_pattern}, IsolatedOriginSource::TEST);

  IsolationContext isolation_context(browser_context());
  url::Origin lookup_origin;

  // Requesting isolated_origin_with_port should return the same origin but with
  // the default port for the scheme.
  const bool kOriginRequestsIsolation = false;
  EXPECT_TRUE(p->GetMatchingProcessIsolatedOrigin(
      isolation_context, isolated_origin_with_port, kOriginRequestsIsolation,
      &lookup_origin));
  EXPECT_EQ(url::DefaultPortForScheme(lookup_origin.scheme().data(),
                                      lookup_origin.scheme().length()),
            lookup_origin.port());
  EXPECT_EQ(isolated_origin, lookup_origin);

  p->RemoveIsolatedOriginForTesting(isolated_origin);

  // Similarly, looking up matching isolated origins for wildcard origins must
  // also return the default port for the origin's scheme, not the report of the
  // requested origin.
  EXPECT_TRUE(p->GetMatchingProcessIsolatedOrigin(
      isolation_context, wild_with_port, kOriginRequestsIsolation,
      &lookup_origin));
  EXPECT_EQ(url::DefaultPortForScheme(lookup_origin.scheme().data(),
                                      lookup_origin.scheme().length()),
            lookup_origin.port());
  EXPECT_EQ(wild_origin, lookup_origin);

  p->RemoveIsolatedOriginForTesting(wild_pattern.origin());

  EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
}

TEST_F(ChildProcessSecurityPolicyTest, ProcessLockMatching) {
  GURL nonapp_url("https://bar.com/");
  GURL app_url("https://some.app.foo.com/");
  GURL app_effective_url("https://app.com/");
  EffectiveURLContentBrowserClient modified_client(
      app_url, app_effective_url, /* requires_dedicated_process */ true);
  ContentBrowserClient* original_client =
      SetBrowserClientForTesting(&modified_client);

  IsolationContext isolation_context(browser_context());

  auto nonapp_urlinfo = UrlInfo::CreateForTesting(
      nonapp_url, CreateStoragePartitionConfigForTesting());
  auto ui_nonapp_url_siteinfo =
      SiteInfo::Create(isolation_context, nonapp_urlinfo);
  auto ui_nonapp_url_lock =
      ProcessLock::Create(isolation_context, nonapp_urlinfo);

  auto app_urlinfo = UrlInfo::CreateForTesting(
      app_url, CreateStoragePartitionConfigForTesting());
  auto ui_app_url_lock = ProcessLock::Create(isolation_context, app_urlinfo);
  auto ui_app_url_siteinfo = SiteInfo::Create(isolation_context, app_urlinfo);

  SiteInfo io_nonapp_url_siteinfo(browser_context());
  ProcessLock io_nonapp_url_lock;
  SiteInfo io_app_url_siteinfo(browser_context());
  ProcessLock io_app_url_lock;

  base::WaitableEvent io_locks_set_event;

  // Post a task that will compute ProcessLocks for the same URLs in the
  // IO thread.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        io_nonapp_url_siteinfo =
            SiteInfo::CreateOnIOThread(isolation_context, nonapp_urlinfo);
        io_nonapp_url_lock =
            ProcessLock::Create(isolation_context, nonapp_urlinfo);

        io_app_url_siteinfo =
            SiteInfo::CreateOnIOThread(isolation_context, app_urlinfo);
        io_app_url_lock = ProcessLock::Create(isolation_context, app_urlinfo);

        // Tell the UI thread have computed the locks.
        io_locks_set_event.Signal();
      }));

  io_locks_set_event.Wait();

  // Expect URLs with effective URLs that match the original URL to have
  // matching SiteInfos and matching ProcessLocks.
  EXPECT_EQ(ui_nonapp_url_siteinfo, io_nonapp_url_siteinfo);
  EXPECT_EQ(ui_nonapp_url_lock, io_nonapp_url_lock);

  // Expect hosted app URLs where the effective URL does not match the original
  // URL to have different SiteInfos but matching process locks. The SiteInfos,
  // are expected to be different because the effective URL cannot be computed
  // from the IO thread. This means the site_url fields will differ.
  EXPECT_NE(ui_app_url_siteinfo, io_app_url_siteinfo);
  EXPECT_NE(ui_app_url_siteinfo.site_url(), io_app_url_siteinfo.site_url());
  EXPECT_EQ(ui_app_url_siteinfo.process_lock_url(),
            io_app_url_siteinfo.process_lock_url());
  EXPECT_EQ(ui_app_url_lock, io_app_url_lock);

  SetBrowserClientForTesting(original_client);
}

// Verify the mechanism that allows non-origin-keyed isolated origins to be
// associated with a single BrowsingInstance.
TEST_F(ChildProcessSecurityPolicyTest,
       IsolatedOriginsForSpecificBrowsingInstances) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Create SiteInstances for foo.com, bar.com, and baz.com, with each
  // SiteInstance in a new BrowsingInstance.
  TestBrowserContext context;
  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://foo.com/"));
  auto foo_browsing_instance_id =
      foo_instance->GetIsolationContext().browsing_instance_id();
  scoped_refptr<SiteInstanceImpl> bar_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://bar.com/"));
  auto bar_browsing_instance_id =
      bar_instance->GetIsolationContext().browsing_instance_id();
  scoped_refptr<SiteInstanceImpl> baz_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://baz.com/"));
  auto baz_browsing_instance_id =
      baz_instance->GetIsolationContext().browsing_instance_id();

  // Isolate foo.com for `foo_instance`'s BrowsingInstance only.
  p->AddCoopIsolatedOriginForBrowsingInstance(
      foo_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(
          &context, false /* applies_to_future_browsing_instances */,
          foo_browsing_instance_id, foo)));

  // Verify that foo.com is isolated only in the `foo_instance`'s
  // BrowsingInstance, and no other origins are isolated in any other
  // BrowsingInstances.
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, baz));

  // Verify that subdomains of foo.com are part of the foo.com
  // isolated origin (i.e., that foo.com is not origin-keyed).
  EXPECT_TRUE(
      IsIsolatedOrigin(&context, foo_browsing_instance_id,
                       url::Origin::Create(GURL("https://sub.foo.com"))));
  EXPECT_TRUE(
      IsIsolatedOrigin(&context, foo_browsing_instance_id,
                       url::Origin::Create(GURL("https://sub2.sub.foo.com"))));

  // Isolating foo.com again in the same BrowsingInstance should have no
  // effect.
  p->AddCoopIsolatedOriginForBrowsingInstance(
      foo_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(GetIsolatedOriginEntry(
          &context, false /* applies_to_future_browsing_instances */,
          foo_browsing_instance_id, foo)));

  // Isolate baz.com in `baz_browsing_instance`'s BrowsingInstance.
  p->AddCoopIsolatedOriginForBrowsingInstance(
      baz_instance->GetIsolationContext(), baz, IsolatedOriginSource::TEST);
  LOCKED_EXPECT_THAT(
      p->isolated_origins_lock_, p->isolated_origins_,
      testing::UnorderedElementsAre(
          GetIsolatedOriginEntry(
              &context, false /* applies_to_future_browsing_instances */,
              foo_browsing_instance_id, foo),
          GetIsolatedOriginEntry(
              &context, false /* applies_to_future_browsing_instances */,
              baz_browsing_instance_id, baz)));

  // Verify that foo.com is isolated in the `foo_instance`'s BrowsingInstance,
  // and baz.com is isolated in `baz_instance`'s BrowsingInstance.
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, baz_browsing_instance_id, baz));

  // Isolate bar.com in foo.com (not bar.com)'s BrowsingInstance.
  p->AddCoopIsolatedOriginForBrowsingInstance(
      foo_instance->GetIsolationContext(), bar, IsolatedOriginSource::TEST);

  // Verify that foo.com and bar.com are both isolated in `foo_instance`'s
  // BrowsingInstance, nothing is isolated in bar_instance's BrowsingInstance,
  // and baz.com is isolated in `baz_instance`'s BrowsingInstance.
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, baz));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, baz_browsing_instance_id, baz));

  // Isolate foo.com in `bar_instance` and `baz_instance`'s BrowsingInstances
  // and verify that this takes effect.  This should result in having three
  // entries for foo.com, one for each BrowsingInstance.
  p->AddCoopIsolatedOriginForBrowsingInstance(
      bar_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  p->AddCoopIsolatedOriginForBrowsingInstance(
      baz_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, baz));
  EXPECT_TRUE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, baz));
  EXPECT_TRUE(IsIsolatedOrigin(&context, baz_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, baz_browsing_instance_id, baz));
  EXPECT_EQ(3, GetIsolatedOriginEntryCount(foo));

  // Simulate foo_instance and its BrowsingInstance going away.  This should
  // remove the corresponding BrowsingInstance-specific entries in
  // ChildProcessSecurityPolicy, since they are no longer needed.
  p->SetBrowsingInstanceCleanupDelayForTesting(0);
  foo_instance.reset();
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, baz));

  // Other BrowsingInstances shouldn't be affected.
  EXPECT_TRUE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, baz));
  EXPECT_TRUE(IsIsolatedOrigin(&context, baz_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, baz_browsing_instance_id, bar));
  EXPECT_TRUE(IsIsolatedOrigin(&context, baz_browsing_instance_id, baz));

  p->ClearIsolatedOriginsForTesting();
}

// Verify isolated origins associated with a single BrowsingInstance can be
// combined with isolated origins that apply to future BrowsingInstances.
TEST_F(ChildProcessSecurityPolicyTest,
       IsolatedOriginsForCurrentAndFutureBrowsingInstances) {
  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();

  // Initially there should be no isolated origins.
  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
                     testing::IsEmpty());

  // Create a SiteInstance for foo.com in a new BrowsingInstance.
  TestBrowserContext context;
  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://foo.com/"));
  auto foo_browsing_instance_id =
      foo_instance->GetIsolationContext().browsing_instance_id();

  // Isolate foo.com for `foo_instance`'s BrowsingInstance only.
  p->AddCoopIsolatedOriginForBrowsingInstance(
      foo_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));

  // Create a SiteInstance for bar.com in a new BrowsingInstance.
  scoped_refptr<SiteInstanceImpl> bar_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://bar.com/"));
  auto bar_browsing_instance_id =
      bar_instance->GetIsolationContext().browsing_instance_id();

  // Isolate foo.com for all future BrowsingInstances (with IDs `future_id` or
  // above). Note that this shouldn't apply to the existing BrowsingInstances
  // for foo_instance and bar_instance.
  BrowsingInstanceId future_id(SiteInstanceImpl::NextBrowsingInstanceId());
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST, &context);

  // We should now have two entries for foo.com, one for
  // foo_browsing_instance_id, and one for future_id.
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));

  // Verify that foo.com is isolated in the `foo_instance`'s BrowsingInstance,
  // as well as future BrowsingInstance IDs.
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id.value() + 42, foo));

  // Other origins shouldn't be isolated.
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, bar_browsing_instance_id, bar));
  EXPECT_FALSE(IsIsolatedOrigin(&context, future_id, bar));

  // An attempt to add foo.com for a specific BrowsingInstance which has ID
  // greater than `future_id` should be ignored, since that's already covered
  // by the second foo.com entry that applies to future BrowsingInstances.
  scoped_refptr<SiteInstanceImpl> future_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://foo.com/"));
  EXPECT_EQ(future_id,
            future_instance->GetIsolationContext().browsing_instance_id());
  p->AddCoopIsolatedOriginForBrowsingInstance(
      future_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));

  // Likewise, an attempt to re-add foo.com for future BrowsingInstances should
  // be ignored.
  p->AddFutureIsolatedOrigins({foo}, IsolatedOriginSource::TEST, &context);
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));

  // However, we can still add foo.com isolation to a BrowsingInstance that
  // precedes `future_id` and doesn't match `foo_browsing_instance_id`.  Check
  // this with `bar_instance`'s BrowsingInstance.
  EXPECT_LT(bar_browsing_instance_id, future_id);
  p->AddCoopIsolatedOriginForBrowsingInstance(
      bar_instance->GetIsolationContext(), foo, IsolatedOriginSource::TEST);
  EXPECT_EQ(3, GetIsolatedOriginEntryCount(foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id.value() + 42, foo));

  // When foo_instance and its BrowsingInstance goes away, the corresponding
  // entry just for that BrowsingInstance entry should be destroyed, but other
  // entries should remain.
  p->SetBrowsingInstanceCleanupDelayForTesting(0);
  foo_instance.reset();
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id.value() + 42, foo));

  // Destroying a BrowsingInstance with ID `future_id` shouldn't affect the
  // entry that applies to future BrowsingInstances.
  future_instance.reset();
  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));
  EXPECT_FALSE(IsIsolatedOrigin(&context, foo_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, bar_browsing_instance_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id, foo));
  EXPECT_TRUE(IsIsolatedOrigin(&context, future_id.value() + 42, foo));

  p->ClearIsolatedOriginsForTesting();
}

// This test verifies that CanAccessDataForOrigin returns true for a process id
// even if all BrowsingInstanceIDs for that process have been deleted, so long
// as the request matches the process' lock. This test sets an origin-keyed
// lock.
TEST_F(ChildProcessSecurityPolicyTest, NoBrowsingInstanceIDs_OriginKeyed) {
  url::Origin foo = url::Origin::Create(GURL("https://sub.foo.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  p->SetBrowsingInstanceCleanupDelayForTesting(0);

  // Create a SiteInstance for sub.foo.com in a new BrowsingInstance.
  TestBrowserContext context;
  {
    auto origin_isolation_request =
        static_cast<UrlInfo::OriginIsolationRequest>(
            UrlInfo::OriginIsolationRequest::kOriginAgentCluster |
            UrlInfo::OriginIsolationRequest::kRequiresOriginKeyedProcess);
    UrlInfo url_info(UrlInfoInit(foo.GetURL())
                         .WithOriginIsolationRequest(origin_isolation_request));
    scoped_refptr<SiteInstanceImpl> foo_instance =
        SiteInstanceImpl::CreateForUrlInfo(&context, url_info,
                                           /*is_guest=*/false,
                                           /*is_fenced=*/false);

    p->Add(kRendererID, &context);
    p->LockProcess(foo_instance->GetIsolationContext(), kRendererID,
                   /*is_process_used=*/false,
                   ProcessLock::FromSiteInfo(foo_instance->GetSiteInfo()));

    EXPECT_TRUE(p->GetProcessLock(kRendererID).is_locked_to_site());
    EXPECT_TRUE(p->GetProcessLock(kRendererID).is_origin_keyed_process());
    EXPECT_EQ(foo.GetURL(), p->GetProcessLock(kRendererID).lock_url());

    EXPECT_TRUE(ProcessLock::FromSiteInfo(foo_instance->GetSiteInfo())
                    .is_origin_keyed_process());
    EXPECT_TRUE(p->DetermineOriginAgentClusterIsolation(
                     foo_instance->GetIsolationContext(), foo,
                     OriginAgentClusterIsolationState::CreateNonIsolated())
                    .requires_origin_keyed_process());
  }
  // At this point foo_instance has gone away, and all BrowsingInstanceIDs
  // associated with kRendererID have been cleaned up.
  EXPECT_EQ(static_cast<size_t>(0),
            p->BrowsingInstanceIdCountForTesting(kRendererID));

  // Because the ProcessLock is origin-keyed, we expect sub.foo.com to match but
  // not foo.com.
  EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, foo));
  EXPECT_FALSE(p->CanAccessDataForOrigin(
      kRendererID, url::Origin::Create(GURL("https://foo.com/"))));
  EXPECT_FALSE(p->CanAccessDataForOrigin(
      kRendererID, url::Origin::Create(GURL("https://bar.com/"))));

  // We need to remove it otherwise other tests may fail.
  p->Remove(kRendererID);
}

// This test verifies that CanAccessDataForOrigin returns true for a process id
// even if all BrowsingInstanceIDs for that process have been deleted, so long
// as the request matches the process' lock. This test sets a site-keyed lock.
TEST_F(ChildProcessSecurityPolicyTest, NoBrowsingInstanceIDs_SiteKeyed) {
  url::Origin foo = url::Origin::Create(GURL("https://sub.foo.com/"));
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  p->SetBrowsingInstanceCleanupDelayForTesting(0);

  // Create a SiteInstance for sub.foo.com in a new BrowsingInstance.
  TestBrowserContext context;
  {
    p->Add(kRendererID, &context);
    // Isolate foo.com so we can't get a default SiteInstance. This will mean
    // that https://sub.foo.com will end up in a site-keyed SiteInstance, which
    // is what we need.
    p->AddFutureIsolatedOrigins({url::Origin::Create(GURL("https://foo.com"))},
                                IsolatedOriginSource::TEST, &context);

    UrlInfo url_info(UrlInfoInit(foo.GetURL()));
    scoped_refptr<SiteInstanceImpl> foo_instance =
        SiteInstanceImpl::CreateForUrlInfo(&context, url_info,
                                           /*is_guest=*/false,
                                           /*is_fenced=*/false);
    p->LockProcess(foo_instance->GetIsolationContext(), kRendererID,
                   /*is_process_used=*/false,
                   ProcessLock::FromSiteInfo(foo_instance->GetSiteInfo()));

    EXPECT_TRUE(p->GetProcessLock(kRendererID).is_locked_to_site());
    EXPECT_FALSE(p->GetProcessLock(kRendererID).is_origin_keyed_process());
    EXPECT_EQ(SiteInfo::GetSiteForOrigin(foo),
              p->GetProcessLock(kRendererID).lock_url());

    EXPECT_FALSE(ProcessLock::FromSiteInfo(foo_instance->GetSiteInfo())
                     .is_origin_keyed_process());
    EXPECT_FALSE(p->DetermineOriginAgentClusterIsolation(
                      foo_instance->GetIsolationContext(), foo,
                      OriginAgentClusterIsolationState::CreateNonIsolated())
                     .requires_origin_keyed_process());
  }
  // At this point foo_instance has gone away, and all BrowsingInstanceIDs
  // associated with kRendererID have been cleaned up.
  EXPECT_EQ(static_cast<size_t>(0),
            p->BrowsingInstanceIdCountForTesting(kRendererID));

  // Because the ProcessLock is site-keyed, it should match foo.com and all
  // sub-origins.
  EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, foo));
  EXPECT_TRUE(p->CanAccessDataForOrigin(
      kRendererID, url::Origin::Create(GURL("https://foo.com/"))));
  EXPECT_FALSE(p->CanAccessDataForOrigin(
      kRendererID, url::Origin::Create(GURL("https://bar.com/"))));

  // We need to remove it otherwise other tests may fail.
  p->Remove(kRendererID);
}

// This test verifies that CanAccessDataForOrigin returns false for a process id
// when all BrowsingInstanceIDs for that process have been deleted, and the
// ProcessLock has is_locked_to_site() = false, regardless of the url requested.
TEST_F(ChildProcessSecurityPolicyTest, NoBrowsingInstanceIDs_UnlockedProcess) {
  GURL foo_url = GURL("https://foo.com/");
  url::Origin foo = url::Origin::Create(foo_url);

  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  p->SetBrowsingInstanceCleanupDelayForTesting(0);

  // Make sure feature list command-line options are set in a way that forces
  // default SiteInstance creation on all platforms.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /* enable */ {features::kProcessSharingWithDefaultSiteInstances},
      /* disable */ {features::kProcessSharingWithStrictSiteInstances});
  EXPECT_TRUE(base::FeatureList::IsEnabled(
      features::kProcessSharingWithDefaultSiteInstances));
  EXPECT_FALSE(base::FeatureList::IsEnabled(
      features::kProcessSharingWithStrictSiteInstances));

  base::test::ScopedCommandLine scoped_command_line;
  // Disable site isolation so we can get default SiteInstances on all
  // platforms.
  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
      switches::kDisableSiteIsolation);
  // If --site-per-process was manually appended, remove it; this interferes
  // with default SiteInstances.
  scoped_command_line.GetProcessCommandLine()->RemoveSwitch(
      switches::kSitePerProcess);

  EXPECT_FALSE(SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
  EXPECT_EQ(static_cast<size_t>(0),
            p->BrowsingInstanceIdCountForTesting(kRendererID));

  TestBrowserContext context;
  {
    scoped_refptr<SiteInstanceImpl> foo_instance =
        SiteInstanceImpl::CreateForTesting(&context, foo_url);
    // Adds the process with an "allow_any_site" lock.
    // The next two statements are basically AddForTesting(...), but with a
    // BrowsingInstanceId based on `foo_instance` and not pinned to '1'.
    // This is important when this test is run with other tests, as then
    // BrowsingInstanceId will not be '1' in general.
    p->Add(kRendererID, &context);
    p->LockProcess(foo_instance->GetIsolationContext(), kRendererID,
                   /*is_process_used=*/false,
                   ProcessLock::CreateAllowAnySite(
                       StoragePartitionConfig::CreateDefault(&context),
                       WebExposedIsolationInfo::CreateNonIsolated()));

    EXPECT_TRUE(foo_instance->IsDefaultSiteInstance());
    EXPECT_TRUE(foo_instance->HasSite());
    EXPECT_EQ(foo_instance->GetSiteInfo(),
              SiteInfo::CreateForDefaultSiteInstance(
                  foo_instance->GetIsolationContext(),
                  StoragePartitionConfig::CreateDefault(&context),
                  WebExposedIsolationInfo::CreateNonIsolated()));
    EXPECT_FALSE(foo_instance->RequiresDedicatedProcess());
  }
  // At this point foo_instance has gone away, and all BrowsingInstanceIDs
  // associated with kRendererID have been cleaned up.
  EXPECT_EQ(static_cast<size_t>(0),
            p->BrowsingInstanceIdCountForTesting(kRendererID));

  EXPECT_FALSE(p->GetProcessLock(kRendererID).is_locked_to_site());
  // Ensure that we don't allow the process to keep accessing data for foo after
  // all of the BrowsingInstances are gone, since that would require checking
  // whether foo itself requires a dedicated process.
  EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, foo));

  // We need to remove it otherwise other tests may fail.
  p->Remove(kRendererID);
}

// Regression test for https://crbug.com/1324407.
TEST_F(ChildProcessSecurityPolicyTest, CannotLockUsedProcessToSite) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  TestBrowserContext context;

  scoped_refptr<SiteInstanceImpl> foo_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://foo.com"));
  scoped_refptr<SiteInstanceImpl> bar_instance =
      SiteInstanceImpl::CreateForTesting(&context, GURL("https://bar.com"));

  // Start by putting foo.com into an allows-any-site process.
  p->Add(kRendererID, &context);
  p->LockProcess(foo_instance->GetIsolationContext(), kRendererID,
                 /*is_process_used=*/false,
                 ProcessLock::CreateAllowAnySite(
                     StoragePartitionConfig::CreateDefault(&context),
                     WebExposedIsolationInfo::CreateNonIsolated()));
  EXPECT_TRUE(p->GetProcessLock(kRendererID).allows_any_site());
  EXPECT_FALSE(p->GetProcessLock(kRendererID).is_locked_to_site());

  // If the process is then considered used (e.g., by loading content), it
  // should not be possible to lock it to another site.
  EXPECT_CHECK_DEATH_WITH(
      {
        p->LockProcess(bar_instance->GetIsolationContext(), kRendererID,
                       /*is_process_used=*/true,
                       ProcessLock::FromSiteInfo(bar_instance->GetSiteInfo()));
      },
      "Cannot lock an already used process to .*bar\\.com");
}

}  // namespace content