| // Copyright 2019 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <map> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/json/json_reader.h" | 
 | #include "base/path_service.h" | 
 | #include "base/strings/strcat.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/test/scoped_feature_list.h" | 
 | #include "chrome/browser/extensions/chrome_test_extension_loader.h" | 
 | #include "chrome/browser/extensions/extension_action_runner.h" | 
 | #include "chrome/browser/extensions/extension_browsertest.h" | 
 | #include "chrome/common/chrome_paths.h" | 
 | #include "chrome/common/extensions/extension_test_util.h" | 
 | #include "chrome/test/base/in_process_browser_test.h" | 
 | #include "chrome/test/base/ui_test_utils.h" | 
 | #include "components/network_session_configurator/common/network_switches.h" | 
 | #include "content/public/test/browser_test.h" | 
 | #include "content/public/test/browser_test_utils.h" | 
 | #include "content/public/test/test_navigation_observer.h" | 
 | #include "content/public/test/test_utils.h" | 
 | #include "extensions/browser/browsertest_util.h" | 
 | #include "extensions/common/permissions/permissions_data.h" | 
 | #include "extensions/common/value_builder.h" | 
 | #include "extensions/test/extension_test_message_listener.h" | 
 | #include "extensions/test/test_extension_dir.h" | 
 | #include "net/base/features.h" | 
 | #include "net/dns/mock_host_resolver.h" | 
 | #include "net/http/http_request_headers.h" | 
 | #include "net/test/embedded_test_server/controllable_http_response.h" | 
 | #include "net/test/embedded_test_server/default_handlers.h" | 
 | #include "net/test/embedded_test_server/embedded_test_server.h" | 
 | #include "services/network/public/cpp/network_switches.h" | 
 | #include "testing/gmock/include/gmock/gmock-matchers.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest-param-test.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "url/gurl.h" | 
 | #include "url/origin.h" | 
 |  | 
 | namespace extensions { | 
 |  | 
 | namespace { | 
 |  | 
 | const char* kPermittedHost = "a.example"; | 
 | const char* kOtherPermittedHost = "b.example"; | 
 | const char* kNotPermittedHost = "c.example"; | 
 | const char* kPermittedSubdomain = "sub.a.example"; | 
 | const char* kNotPermittedSubdomain = "notallowedsub.a.example"; | 
 | const char* kPermissionPattern1 = "https://a.example/*"; | 
 | const char* kPermissionPattern1Sub = "https://sub.a.example/*"; | 
 | const char* kPermissionPattern2 = "https://b.example/*"; | 
 | const char* kPermissionPattern3 = "https://d.example/*"; | 
 |  | 
 | // Constants for SameParty tests. We reuse some of the above definitions, but | 
 | // give them more meaningful names in the context of SameParty. | 
 | const char* kPermittedOwner = kPermittedHost; | 
 | const char* kPermittedOwnerSubdomain = kPermittedSubdomain; | 
 | const char* kNotPermittedOwnerSubdomain = kNotPermittedSubdomain; | 
 | const char* kPermittedMember = kOtherPermittedHost; | 
 | const char* kNotPermittedMember = kNotPermittedHost; | 
 | const char* kPermittedNonMember = "d.example"; | 
 | const char* kNotPermittedNonMember = "e.example"; | 
 |  | 
 | // Path for URL of custom ControllableHttpResponse | 
 | const char* kFetchCookiesPath = "/respondwithcookies"; | 
 | // CSP header to be applied to the extension and the child frames | 
 | const char* kCspHeader = | 
 |     "script-src 'self' https://a.example:* https://sub.a.example:* " | 
 |     "https://notallowedsub.a.example:* https://b.example:* " | 
 |     "https://c.example:* https://d.example:* https://e.example; object-src " | 
 |     "'self'"; | 
 |  | 
 | const char* kNoneCookie = "none=1"; | 
 | const char* kLaxCookie = "lax=1"; | 
 | const char* kStrictCookie = "strict=1"; | 
 | const char* kUnspecifiedCookie = "unspecified=1"; | 
 | const char* kSameSiteNoneAttribute = "; SameSite=None; Secure"; | 
 | const char* kSameSiteLaxAttribute = "; SameSite=Lax"; | 
 | const char* kSameSiteStrictAttribute = "; SameSite=Strict"; | 
 |  | 
 | const char* kSamePartyCookie = "sameparty=1"; | 
 | const char* kSamePartyAttribute = "; SameParty; Secure; SameSite=None"; | 
 |  | 
 | const char* kSamePartyCookies[] = { | 
 |     kSamePartyCookie, | 
 |     kNoneCookie, | 
 | }; | 
 | const char* kNoSamePartyCookies[] = {kNoneCookie}; | 
 |  | 
 | using testing::UnorderedElementsAreArray; | 
 |  | 
 | std::vector<std::string> AsCookies(const std::string& cookie_line) { | 
 |   return base::SplitString(cookie_line, ";", base::TRIM_WHITESPACE, | 
 |                            base::SPLIT_WANT_NONEMPTY); | 
 | } | 
 |  | 
 | // Base class for special handling of cookies for extensions. | 
 | class ExtensionCookiesTest : public ExtensionBrowserTest { | 
 |  public: | 
 |   ExtensionCookiesTest() : test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} | 
 |   ~ExtensionCookiesTest() override = default; | 
 |   ExtensionCookiesTest(const ExtensionCookiesTest&) = delete; | 
 |   ExtensionCookiesTest& operator=(const ExtensionCookiesTest&) = delete; | 
 |  | 
 |   void SetUpOnMainThread() override { | 
 |     constexpr int kMaxNumberOfCookieRequestsFromSingleTest = 15; | 
 |  | 
 |     ExtensionBrowserTest::SetUpOnMainThread(); | 
 |     extension_dir_ = std::make_unique<TestExtensionDir>(); | 
 |     extension_ = MakeExtension(); | 
 |     for (int i = 0; i < kMaxNumberOfCookieRequestsFromSingleTest; i++) { | 
 |       http_responses_.push_back( | 
 |           std::make_unique<net::test_server::ControllableHttpResponse>( | 
 |               test_server(), kFetchCookiesPath)); | 
 |     } | 
 |     host_resolver()->AddRule("*", "127.0.0.1"); | 
 |     net::test_server::RegisterDefaultHandlers(test_server()); | 
 |     base::FilePath test_data_dir; | 
 |     ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); | 
 |     test_server()->ServeFilesFromDirectory(test_data_dir); | 
 |     ASSERT_TRUE(test_server()->Start()); | 
 |   } | 
 |  | 
 |   // Ignore cert errors for HTTPS test server, in order to use hostnames other | 
 |   // than localhost or 127.0.0.1. | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     ExtensionBrowserTest::SetUpCommandLine(command_line); | 
 |     command_line->AppendSwitch(switches::kIgnoreCertificateErrors); | 
 |   } | 
 |  | 
 |  protected: | 
 |   // Navigates to the extension page in the main frame. Returns a pointer to the | 
 |   // RenderFrameHost of the main frame. | 
 |   content::RenderFrameHost* NavigateMainFrameToExtensionPage() { | 
 |     EXPECT_TRUE(content::NavigateToURL( | 
 |         web_contents(), extension_->GetResourceURL("/empty.html"))); | 
 |     return web_contents()->GetMainFrame(); | 
 |   } | 
 |  | 
 |   // Appends a child iframe via JS and waits for it to load. Returns a pointer | 
 |   // to the RenderFrameHost of the child frame. (Requests a page that responds | 
 |   // with the proper CSP header to allow scripts from the relevant origins.) | 
 |   content::RenderFrameHost* MakeChildFrame(content::RenderFrameHost* frame, | 
 |                                            const std::string& host) { | 
 |     EXPECT_FALSE(content::ChildFrameAt(frame, 0)); | 
 |     GURL url = test_server()->GetURL( | 
 |         host, | 
 |         base::StrCat({"/set-header?Content-Security-Policy: ", kCspHeader})); | 
 |     const char kAppendFrameScriptTemplate[] = R"( | 
 |         var f = document.createElement('iframe'); | 
 |         f.src = $1; | 
 |         f.onload = function(e) { | 
 |             window.domAutomationController.send(true); | 
 |             f.onload = undefined; | 
 |         } | 
 |         document.body.appendChild(f); )"; | 
 |     std::string append_frame_script = | 
 |         content::JsReplace(kAppendFrameScriptTemplate, url.spec()); | 
 |     bool loaded = false; | 
 |     EXPECT_TRUE(content::ExecuteScriptAndExtractBool(frame, append_frame_script, | 
 |                                                      &loaded)); | 
 |     EXPECT_TRUE(loaded); | 
 |     content::RenderFrameHost* child_frame = content::ChildFrameAt(frame, 0); | 
 |     EXPECT_EQ(url, child_frame->GetLastCommittedURL()); | 
 |     return child_frame; | 
 |   } | 
 |  | 
 |   // Sets a vector of cookies directly into the cookie store, simulating being | 
 |   // set from a "strictly same-site" request context. | 
 |   void SetCookies(const std::string& host, | 
 |                   const std::vector<std::string>& cookies) { | 
 |     GURL url = test_server()->GetURL(host, "/"); | 
 |     for (const std::string& cookie : cookies) { | 
 |       content::SetCookie(browser()->profile(), url, cookie); | 
 |     } | 
 |   } | 
 |  | 
 |   // Makes a request to |host| from the context of |frame|, then returns the | 
 |   // cookies that were sent on that request. | 
 |   std::string FetchCookies(content::RenderFrameHost* frame, | 
 |                            const std::string& host) { | 
 |     GURL cookie_url = test_server()->GetURL(host, kFetchCookiesPath); | 
 |     const char kFetchCookiesScriptTemplate[] = R"( | 
 |         fetch($1, {method: 'GET', credentials: 'include'}) | 
 |           .then((resp) => resp.text()) | 
 |           .then((data) => window.domAutomationController.send(data));)"; | 
 |     std::string fetch_cookies_script = | 
 |         content::JsReplace(kFetchCookiesScriptTemplate, cookie_url.spec()); | 
 |     content::DOMMessageQueue messages(frame); | 
 |     ExecuteScriptAsync(frame, fetch_cookies_script); | 
 |  | 
 |     url::Origin initiator = frame->GetLastCommittedOrigin(); | 
 |     WaitForRequestAndRespondWithCookies(initiator); | 
 |  | 
 |     std::string result; | 
 |     if (!messages.PopMessage(&result)) { | 
 |       EXPECT_TRUE(messages.WaitForMessage(&result)); | 
 |     } | 
 |     base::TrimString(result, "\"", &result); | 
 |     return result; | 
 |   } | 
 |  | 
 |   // Triggers a `frame`-initiated navigation of `frame` to `host`, then returns | 
 |   // the cookies that were sent on that navigation request. | 
 |   std::string NavigateChildAndGetCookies(content::RenderFrameHost* frame, | 
 |                                          const std::string& host) { | 
 |     GURL cookie_url = test_server()->GetURL(host, kFetchCookiesPath); | 
 |     url::Origin initiator = frame->GetLastCommittedOrigin(); | 
 |     content::TestNavigationObserver nav_observer(web_contents()); | 
 |     // We cache the parent here, and use it to get the RenderFrameHost again | 
 |     // later, in order to allow cross-site navigations. Cross-site navigations | 
 |     // cause `frame` to be freed (and use a new RFHI for the new document), so | 
 |     // it is not safe to use `frame` after the call to `ExecuteScriptAsync`. | 
 |     content::RenderFrameHost* parent = frame->GetParent(); | 
 |     // We assume there's only one child. | 
 |     DCHECK_EQ(frame, content::ChildFrameAt(parent, 0)); | 
 |     ExecuteScriptAsync(frame, content::JsReplace("location = $1", cookie_url)); | 
 |     WaitForRequestAndRespondWithCookies(initiator); | 
 |     nav_observer.Wait(); | 
 |  | 
 |     return content::EvalJs(content::ChildFrameAt(parent, 0), | 
 |                            "document.body.innerText") | 
 |         .ExtractString(); | 
 |   } | 
 |  | 
 |   // Responds to a request with the cookies that were sent with the request. | 
 |   // We can't simply use the default handler /echoheader?Cookie here, because it | 
 |   // doesn't send the appropriate Access-Control-Allow-Origin and | 
 |   // Access-Control-Allow-Credentials headers (which are required for this to | 
 |   // work since we are making cross-origin requests in these tests). | 
 |   void WaitForRequestAndRespondWithCookies(const url::Origin& initiator) { | 
 |     net::test_server::ControllableHttpResponse& http_response = | 
 |         GetNextCookieResponse(); | 
 |     http_response.WaitForRequest(); | 
 |  | 
 |     // Remove the trailing slash from the URL. | 
 |     std::string origin = initiator.GetURL().spec(); | 
 |     base::TrimString(origin, "/", &origin); | 
 |  | 
 |     // Get the 'Cookie' header that was sent in the request. | 
 |     std::string cookie_header; | 
 |     auto cookie_header_it = http_response.http_request()->headers.find( | 
 |         net::HttpRequestHeaders::kCookie); | 
 |     if (cookie_header_it == http_response.http_request()->headers.end()) { | 
 |       cookie_header = ""; | 
 |     } else { | 
 |       cookie_header = cookie_header_it->second; | 
 |     } | 
 |     std::string content_length = base::NumberToString(cookie_header.length()); | 
 |  | 
 |     // clang-format off | 
 |     http_response.Send( | 
 |         base::StrCat({ | 
 |         "HTTP/1.1 200 OK\r\n", | 
 |         "Content-Type: text/plain; charset=utf-8\r\n", | 
 |         "Content-Length: ", content_length, "\r\n", | 
 |         "Access-Control-Allow-Origin: ", origin, "\r\n", | 
 |         "Access-Control-Allow-Credentials: true\r\n", | 
 |         "\r\n", | 
 |         cookie_header})); | 
 |     // clang-format on | 
 |  | 
 |     http_response.Done(); | 
 |   } | 
 |  | 
 |   virtual const Extension* MakeExtension() = 0; | 
 |  | 
 |   const Extension* MakeExtension( | 
 |       const std::vector<std::string>& host_patterns) { | 
 |     ChromeTestExtensionLoader loader(profile()); | 
 |     DictionaryBuilder manifest; | 
 |     manifest.Set("name", "Cookies test extension") | 
 |         .Set("version", "1") | 
 |         .Set("manifest_version", 2) | 
 |         .Set("web_accessible_resources", ListBuilder().Append("*.html").Build()) | 
 |         .Set("content_security_policy", kCspHeader) | 
 |         .Set("permissions", | 
 |              ListBuilder() | 
 |                  .Append(host_patterns.begin(), host_patterns.end()) | 
 |                  .Build()); | 
 |     extension_dir_->WriteFile(FILE_PATH_LITERAL("empty.html"), ""); | 
 |     extension_dir_->WriteFile(FILE_PATH_LITERAL("script.js"), ""); | 
 |     extension_dir_->WriteManifest(manifest.ToJSON()); | 
 |  | 
 |     const Extension* extension = | 
 |         loader.LoadExtension(extension_dir_->UnpackedPath()).get(); | 
 |  | 
 |     DCHECK(extension); | 
 |     return extension; | 
 |   } | 
 |  | 
 |   // The test server needs to be HTTPS because a SameSite=None cookie must be | 
 |   // Secure. | 
 |   net::EmbeddedTestServer* test_server() { return &test_server_; } | 
 |  | 
 |   content::WebContents* web_contents() { | 
 |     return browser()->tab_strip_model()->GetActiveWebContents(); | 
 |   } | 
 |  | 
 |   net::test_server::ControllableHttpResponse& GetNextCookieResponse() { | 
 |     // If the DCHECK below fails, consider increasing the value of the | 
 |     // kMaxNumberOfCookieRequestsFromSingleTest constant above. | 
 |     DCHECK_LT(index_of_active_http_response_, http_responses_.size()); | 
 |  | 
 |     return *http_responses_[index_of_active_http_response_++]; | 
 |   } | 
 |  | 
 |   std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>> | 
 |       http_responses_; | 
 |   size_t index_of_active_http_response_ = 0; | 
 |  | 
 |   net::EmbeddedTestServer test_server_; | 
 |   base::test::ScopedFeatureList feature_list_; | 
 |   std::unique_ptr<TestExtensionDir> extension_dir_; | 
 |   const Extension* extension_ = nullptr; | 
 | }; | 
 |  | 
 | // Tests for special handling of SameSite cookies for extensions: | 
 | // A request should be treated as same-site for the purposes of SameSite | 
 | // cookies if either | 
 | //  1) the request initiator is an extension with access to the requested URL, | 
 | //  2) the site_for_cookies is an extension with access to the requested URL, | 
 | //     and the request initiator (if it exists) is same-site to the requested | 
 | //     URL and also the extension has access to it. | 
 | // See URLLoader::ShouldForceIgnoreSiteForCookies(). | 
 | // | 
 | // The test fixture param is whether or not the new SameSite features are | 
 | // enabled. | 
 | class ExtensionSameSiteCookiesTest | 
 |     : public ExtensionCookiesTest, | 
 |       public ::testing::WithParamInterface<bool> { | 
 |  public: | 
 |   ExtensionSameSiteCookiesTest() { | 
 |     std::vector<base::Feature> samesite_features = { | 
 |         net::features::kSameSiteByDefaultCookies, | 
 |         net::features::kCookiesWithoutSameSiteMustBeSecure}; | 
 |     if (AreSameSiteFeaturesEnabled()) { | 
 |       feature_list_.InitWithFeatures(samesite_features /* enabled */, {}); | 
 |     } else { | 
 |       feature_list_.InitWithFeatures({}, samesite_features /* disabled */); | 
 |     } | 
 |   } | 
 |   ~ExtensionSameSiteCookiesTest() override = default; | 
 |   ExtensionSameSiteCookiesTest(const ExtensionSameSiteCookiesTest&) = delete; | 
 |   ExtensionSameSiteCookiesTest& operator=(const ExtensionSameSiteCookiesTest&) = | 
 |       delete; | 
 |  | 
 |  protected: | 
 |   // Sets an array of cookies with various SameSite values. | 
 |   void SetCookies(const std::string& host) { | 
 |     ExtensionCookiesTest::SetCookies( | 
 |         host, { | 
 |                   base::StrCat({kNoneCookie, kSameSiteNoneAttribute}), | 
 |                   base::StrCat({kLaxCookie, kSameSiteLaxAttribute}), | 
 |                   base::StrCat({kStrictCookie, kSameSiteStrictAttribute}), | 
 |                   kUnspecifiedCookie, | 
 |               }); | 
 |   } | 
 |  | 
 |   // Expect that all cookies, including SameSite cookies, are present. | 
 |   void ExpectSameSiteCookies(const std::string& cookie_header) { | 
 |     EXPECT_THAT( | 
 |         AsCookies(cookie_header), | 
 |         testing::UnorderedElementsAre(kNoneCookie, kLaxCookie, kStrictCookie, | 
 |                                       kUnspecifiedCookie)); | 
 |   } | 
 |  | 
 |   // Expect that only cookies without SameSite are present. | 
 |   void ExpectNoSameSiteCookies(const std::string& cookie_header) { | 
 |     std::vector<std::string> expected = {kNoneCookie}; | 
 |     if (!AreSameSiteFeaturesEnabled()) { | 
 |       expected.push_back(kUnspecifiedCookie); | 
 |     } | 
 |     EXPECT_THAT(AsCookies(cookie_header), | 
 |                 testing::UnorderedElementsAreArray(expected)); | 
 |   } | 
 |  | 
 |   const Extension* MakeExtension() override { | 
 |     return ExtensionCookiesTest::MakeExtension( | 
 |         {kPermissionPattern1, kPermissionPattern1Sub, kPermissionPattern2}); | 
 |   } | 
 |  | 
 |   bool AreSameSiteFeaturesEnabled() { return GetParam(); } | 
 | }; | 
 |  | 
 | // Tests where the extension page initiates the request. | 
 |  | 
 | // Extension initiates request to permitted host => SameSite cookies are sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        ExtensionInitiatedPermitted) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* frame = NavigateMainFrameToExtensionPage(); | 
 |   std::string cookies = FetchCookies(frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension initiates request to disallowed host => SameSite cookies are not | 
 | // sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        ExtensionInitiatedNotPermitted) { | 
 |   SetCookies(kNotPermittedHost); | 
 |   content::RenderFrameHost* frame = NavigateMainFrameToExtensionPage(); | 
 |   std::string cookies = FetchCookies(frame, kNotPermittedHost); | 
 |   ExpectNoSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Tests with one frame on an extension page which makes the request. | 
 |  | 
 | // Extension is site_for_cookies, initiator and requested URL are permitted, | 
 | // initiator and requested URL are same-site => SameSite cookies are sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        OnePermittedSameSiteFrame) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(child_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator and requested URL are permitted, | 
 | // initiator and requested URL are same-site => SameSite cookies are sent. | 
 | // crbug.com/1153083: flaky on linux, win, and mac | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        OnePermittedSameSiteFrame_Navigation) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedHost); | 
 |   std::string cookies = NavigateChildAndGetCookies(child_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator and requested URL are permitted, | 
 | // initiator and requested URL are same-site (initiator is a subdomain of the | 
 | // requested domain) => SameSite cookies are sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        OnePermittedSubdomainFrame) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedSubdomain); | 
 |   std::string cookies = FetchCookies(child_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator and requested URL are permitted, | 
 | // initiator and requested URL are same-site (initiator is a superdomain of the | 
 | // requested domain) => SameSite cookies are sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        OnePermittedSuperdomainFrame) { | 
 |   SetCookies(kPermittedSubdomain); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(child_frame, kPermittedSubdomain); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator and requested URL are permitted, | 
 | // initiator and requested URL are cross-site => SameSite cookies are not sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        OnePermittedCrossSiteFrame) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kOtherPermittedHost); | 
 |   std::string cookies = FetchCookies(child_frame, kPermittedHost); | 
 |   ExpectNoSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator is permitted but requested URL is | 
 | // not => SameSite cookies are not sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        CrossSiteInitiatorPermittedRequestNotPermitted) { | 
 |   SetCookies(kNotPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(child_frame, kNotPermittedHost); | 
 |   ExpectNoSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator is permitted but requested URL is | 
 | // not, even though they are same-site => SameSite cookies are not sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        SameSiteInitiatorPermittedRequestNotPermitted) { | 
 |   SetCookies(kNotPermittedSubdomain); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(child_frame, kNotPermittedSubdomain); | 
 |   ExpectNoSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator is not permitted but requested URL | 
 | // is permitted, even though they are same-site => SameSite cookies are not | 
 | // sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        SameSiteInitiatorNotPermittedRequestPermitted) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kNotPermittedSubdomain); | 
 |   std::string cookies = FetchCookies(child_frame, kPermittedHost); | 
 |   ExpectNoSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator and requested URL are same-site but | 
 | // not permitted => SameSite cookies are not sent. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        SameSiteInitiatorAndRequestNotPermitted) { | 
 |   SetCookies(kNotPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kNotPermittedHost); | 
 |   std::string cookies = FetchCookies(child_frame, kNotPermittedHost); | 
 |   ExpectNoSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Tests where the initiator is a nested frame. Here it doesn't actually matter | 
 | // what the initiator is nested in, because we don't check. | 
 |  | 
 | // Extension is site_for_cookies, initiator is allowed frame nested inside a | 
 | // same-site allowed frame, request is to the same site => SameSite cookies are | 
 | // attached. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, NestedSameSitePermitted) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kPermittedHost); | 
 |   content::RenderFrameHost* nested_frame = | 
 |       MakeChildFrame(child_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(nested_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator is allowed frame nested inside a | 
 | // cross-site allowed frame, request is to the same site => SameSite cookies are | 
 | // attached. | 
 | // This is kind of an interesting case. Should we attach SameSite cookies here? | 
 | // If we only check first-partyness between each frame ancestor and the main | 
 | // frame, then we consider all of these frames first-party to the extension, so | 
 | // we should attach SameSite cookies here. (This is the current algorithm in the | 
 | // spec, which says to check each ancestor against the top frame: | 
 | // https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-5.2.1) | 
 | // If we also want to ensure first-partyness between each frame and its | 
 | // immediate parent, then we should not send SameSite cookies here. See | 
 | // crbug.com/1027258. | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, NestedCrossSitePermitted) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kOtherPermittedHost); | 
 |   content::RenderFrameHost* nested_frame = | 
 |       MakeChildFrame(child_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(nested_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // The following tests are correct for current behavior, but should probably | 
 | // change in the future. We should be walking up the whole frame tree instead of | 
 | // only checking permissions and same-siteness for the initiator and request. | 
 |  | 
 | // Extension is site_for_cookies, initiator is allowed frame nested inside a | 
 | // cross-site disallowed frame, request is to the same site => SameSite cookies | 
 | // are attached (but ideally shouldn't be). | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        NestedCrossSiteNotPermitted) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kNotPermittedHost); | 
 |   content::RenderFrameHost* nested_frame = | 
 |       MakeChildFrame(child_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(nested_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // Extension is site_for_cookies, initiator is allowed frame nested inside a | 
 | // same-site disallowed frame, request is to the same site => SameSite cookies | 
 | // are attached (but ideally shouldn't be). | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        NestedSameSiteNotPermitted) { | 
 |   SetCookies(kPermittedHost); | 
 |   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |   content::RenderFrameHost* child_frame = | 
 |       MakeChildFrame(main_frame, kNotPermittedSubdomain); | 
 |   content::RenderFrameHost* nested_frame = | 
 |       MakeChildFrame(child_frame, kPermittedHost); | 
 |   std::string cookies = FetchCookies(nested_frame, kPermittedHost); | 
 |   ExpectSameSiteCookies(cookies); | 
 | } | 
 |  | 
 | // SameSite-cookies-flavoured copy of the ExtensionActiveTabTest.ActiveTab test. | 
 | // In this test, the effective extension permissions are changing at runtime | 
 | // - the test verifies that the changing permissions are correctly propagated | 
 | // into the SameSite cookie decisions (e.g. in | 
 | // network::URLLoader::ShouldForceIgnoreSiteForCookies). | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        ActiveTabPermissions_BackgroundPage) { | 
 |   TestExtensionDir extension_dir; | 
 |   constexpr char kManifest[] = R"( | 
 |       { | 
 |         "name": "ActiveTab permissions vs SameSite cookies", | 
 |         "version": "1.0", | 
 |         "manifest_version": 2, | 
 |         "browser_action": { | 
 |           "default_title": "activeTab" | 
 |         }, | 
 |         "permissions": ["activeTab"], | 
 |         "background": { | 
 |           "scripts": ["bg_script.js"] | 
 |         } | 
 |       } )"; | 
 |   extension_dir.WriteManifest(kManifest); | 
 |   extension_dir.WriteFile(FILE_PATH_LITERAL("bg_script.js"), ""); | 
 |   const Extension* extension = LoadExtension(extension_dir.UnpackedPath()); | 
 |   ASSERT_TRUE(extension); | 
 |   content::RenderFrameHost* background_page = | 
 |       ProcessManager::Get(profile()) | 
 |           ->GetBackgroundHostForExtension(extension->id()) | 
 |           ->host_contents() | 
 |           ->GetMainFrame(); | 
 |  | 
 |   // Set up a test scenario: | 
 |   // - top-level frame: kActiveTabHost | 
 |   constexpr char kActiveTabHost[] = "active-tab.example"; | 
 |   GURL original_document_url = | 
 |       test_server()->GetURL(kActiveTabHost, "/title1.html"); | 
 |   ui_test_utils::NavigateToURL(browser(), original_document_url); | 
 |   SetCookies(kActiveTabHost); | 
 |  | 
 |   // Based on activeTab, the extension shouldn't be initially granted access to | 
 |   // `kActiveTabHost`. | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 1: Initial fetch."); | 
 |     std::string cookies = FetchCookies(background_page, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Do one pass of BrowserAction without granting activeTab permission, | 
 |   // extension still shouldn't have access to `kActiveTabHost`. | 
 |   ExtensionActionRunner::GetForWebContents(web_contents()) | 
 |       ->RunAction(extension, false); | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 2: After BrowserAction without granting access."); | 
 |     std::string cookies = FetchCookies(background_page, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Granting activeTab permission to the extension should give it access to | 
 |   // `kActiveTabHost`. | 
 |   ExtensionActionRunner::GetForWebContents(web_contents()) | 
 |       ->RunAction(extension, true); | 
 |   { | 
 |     // ActiveTab access (just like OOR-CORS access) extends to the background | 
 |     // page.  This is desirable, because | 
 |     // 1) there is no security boundary between A) extension background pages | 
 |     //    and B) extension frames in the tab | 
 |     // 2) it seems best to highlight #1 by simplistically granting extra | 
 |     //    capabilities to the whole extension (rather than forcing the extension | 
 |     //    authors to jump through extra hurdles to utilize the new capability). | 
 |     SCOPED_TRACE("TEST STEP 3: After granting ActiveTab access."); | 
 |     std::string cookies = FetchCookies(background_page, kActiveTabHost); | 
 |     ExpectSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Navigating the tab to a different, same-origin document should retain | 
 |   // extension's access to the origin. | 
 |   GURL another_document_url = | 
 |       test_server()->GetURL(kActiveTabHost, "/title2.html"); | 
 |   EXPECT_NE(another_document_url, original_document_url); | 
 |   EXPECT_EQ(url::Origin::Create(another_document_url), | 
 |             url::Origin::Create(original_document_url)); | 
 |   ui_test_utils::NavigateToURL(browser(), another_document_url); | 
 |   { | 
 |     SCOPED_TRACE( | 
 |         "TEST STEP 4: After navigating the tab cross-document, " | 
 |         "but still same-origin."); | 
 |     std::string cookies = FetchCookies(background_page, kActiveTabHost); | 
 |     ExpectSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Navigating the tab to a different origin should revoke extension's access | 
 |   // to the tab. | 
 |   GURL cross_origin_url = test_server()->GetURL("other.com", "/title1.html"); | 
 |   EXPECT_NE(url::Origin::Create(cross_origin_url), | 
 |             url::Origin::Create(original_document_url)); | 
 |   ui_test_utils::NavigateToURL(browser(), cross_origin_url); | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 5: After navigating the tab cross-origin."); | 
 |     std::string cookies = FetchCookies(background_page, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        ActiveTabPermissions_ExtensionSubframeInTab) { | 
 |   TestExtensionDir extension_dir; | 
 |   constexpr char kManifest[] = R"( | 
 |       { | 
 |         "name": "ActiveTab permissions vs SameSite cookies", | 
 |         "version": "1.0", | 
 |         "manifest_version": 2, | 
 |         "browser_action": { | 
 |           "default_title": "activeTab" | 
 |         }, | 
 |         "permissions": ["activeTab"], | 
 |         "web_accessible_resources": ["subframe.html"] | 
 |       } )"; | 
 |   extension_dir.WriteManifest(kManifest); | 
 |   extension_dir.WriteFile(FILE_PATH_LITERAL("subframe.html"), | 
 |                           "<p>Extension frame</p>"); | 
 |   const Extension* extension = LoadExtension(extension_dir.UnpackedPath()); | 
 |   ASSERT_TRUE(extension); | 
 |  | 
 |   // Set up a test scenario: | 
 |   // - top-level frame: kActiveTabHost | 
 |   // - subframe: extension | 
 |   constexpr char kActiveTabHost[] = "active-tab.example"; | 
 |   ui_test_utils::NavigateToURL( | 
 |       browser(), test_server()->GetURL(kActiveTabHost, "/title1.html")); | 
 |   SetCookies(kActiveTabHost); | 
 |   content::RenderFrameHost* extension_subframe = nullptr; | 
 |   { | 
 |     content::TestNavigationObserver subframe_nav_observer(web_contents()); | 
 |     constexpr char kSubframeInjectionScriptTemplate[] = R"( | 
 |         var f = document.createElement('iframe'); | 
 |         f.src = $1; | 
 |         document.body.appendChild(f); | 
 |     )"; | 
 |     ASSERT_TRUE(content::ExecJs( | 
 |         web_contents(), | 
 |         content::JsReplace(kSubframeInjectionScriptTemplate, | 
 |                            extension->GetResourceURL("subframe.html")))); | 
 |     subframe_nav_observer.Wait(); | 
 |     ASSERT_EQ(2u, web_contents()->GetAllFrames().size()); | 
 |     extension_subframe = web_contents()->GetAllFrames()[1]; | 
 |     ASSERT_EQ(extension->origin(), | 
 |               extension_subframe->GetLastCommittedOrigin()); | 
 |   } | 
 |  | 
 |   // Based on activeTab, the extension shouldn't be initially granted access to | 
 |   // `kActiveTabHost`. | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 1: Initial fetch."); | 
 |     std::string cookies = FetchCookies(extension_subframe, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Do one pass of BrowserAction without granting activeTab permission, | 
 |   // extension still shouldn't have access to `kActiveTabHost`. | 
 |   ExtensionActionRunner::GetForWebContents(web_contents()) | 
 |       ->RunAction(extension, false); | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 2: After BrowserAction without granting access."); | 
 |     std::string cookies = FetchCookies(extension_subframe, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Granting activeTab permission to the extension should give it access to | 
 |   // `kActiveTabHost`. | 
 |   ExtensionActionRunner::GetForWebContents(web_contents()) | 
 |       ->RunAction(extension, true); | 
 |   { | 
 |     // ActiveTab should grant access to SameSite cookies to the | 
 |     // `extension_subframe`. | 
 |     SCOPED_TRACE("TEST STEP 3: After granting ActiveTab access."); | 
 |     std::string cookies = FetchCookies(extension_subframe, kActiveTabHost); | 
 |     ExpectSameSiteCookies(cookies); | 
 |   } | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest, | 
 |                        ActiveTabPermissions_ExtensionServiceWorker) { | 
 |   const char kServiceWorker[] = R"( | 
 |       chrome.runtime.onMessage.addListener( | 
 |           function(request, sender, sendResponse) { | 
 |             if (request.url) { | 
 |               fetch(request.url, {method: 'GET', credentials: 'include'}) | 
 |                 .then(response => response.text()) | 
 |                 .then(text => sendResponse(text)) | 
 |                 .catch(err => sendResponse('error: ' + err)); | 
 |               return true; | 
 |             } | 
 |           }); | 
 |       chrome.test.sendMessage('WORKER_RUNNING'); | 
 |   )"; | 
 |   auto fetch_via_extension_service_worker = | 
 |       [this](content::RenderFrameHost* extension_frame, | 
 |              const std::string& host) -> std::string { | 
 |     // Build a script that will send a message to the extension service worker, | 
 |     // asking it to perform a `fetch` and reply with the response. | 
 |     const char kFetchTemplate[] = R"( | 
 |         chrome.runtime.sendMessage({url: $1}, function(response) { | 
 |             domAutomationController.send(response); | 
 |         }); | 
 |     )"; | 
 |     GURL cookie_url = this->test_server()->GetURL(host, kFetchCookiesPath); | 
 |     std::string fetch_script = content::JsReplace(kFetchTemplate, cookie_url); | 
 |  | 
 |     // Use `fetch_script` to ask the service worker to perform a `fetch` and | 
 |     // reply with the response. | 
 |     content::DOMMessageQueue queue; | 
 |     content::ExecuteScriptAsync(extension_frame, fetch_script); | 
 |  | 
 |     // Provide the HTTP response. | 
 |     url::Origin initiator = extension_frame->GetLastCommittedOrigin(); | 
 |     WaitForRequestAndRespondWithCookies(initiator); | 
 |  | 
 |     // Read back the response reported by the extension service worker. | 
 |     std::string json; | 
 |     EXPECT_TRUE(queue.WaitForMessage(&json)); | 
 |     base::Optional<base::Value> value = | 
 |         base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS); | 
 |     std::string result; | 
 |     EXPECT_TRUE(value->GetAsString(&result)); | 
 |     return result; | 
 |   }; | 
 |  | 
 |   TestExtensionDir extension_dir; | 
 |   constexpr char kManifest[] = R"( | 
 |       { | 
 |         "name": "ActiveTab permissions vs SameSite cookies", | 
 |         "version": "1.0", | 
 |         "manifest_version": 2, | 
 |         "browser_action": { | 
 |           "default_title": "activeTab" | 
 |         }, | 
 |         "permissions": ["activeTab"], | 
 |         "background": {"service_worker": "bg_worker.js"} | 
 |       } )"; | 
 |   extension_dir.WriteManifest(kManifest); | 
 |   extension_dir.WriteFile(FILE_PATH_LITERAL("bg_worker.js"), kServiceWorker); | 
 |   extension_dir.WriteFile(FILE_PATH_LITERAL("frame.html"), | 
 |                           "<p>Extension frame</p>"); | 
 |   ExtensionTestMessageListener worker_listener("WORKER_RUNNING", false); | 
 |   const Extension* extension = LoadExtension(extension_dir.UnpackedPath()); | 
 |   ASSERT_TRUE(extension); | 
 |   EXPECT_TRUE(worker_listener.WaitUntilSatisfied()); | 
 |  | 
 |   // Set up a test scenario: | 
 |   // - tab1: top-level frame: kActiveTabHost | 
 |   // - tab2: top-level frame: extension (for triggering fetches in the | 
 |   //                                     extension's service worker) | 
 |   constexpr char kActiveTabHost[] = "active-tab.example"; | 
 |   GURL original_document_url = | 
 |       test_server()->GetURL(kActiveTabHost, "/title1.html"); | 
 |   ui_test_utils::NavigateToURL(browser(), original_document_url); | 
 |   EXPECT_EQ(kActiveTabHost, | 
 |             web_contents()->GetMainFrame()->GetLastCommittedURL().host()); | 
 |   SetCookies(kActiveTabHost); | 
 |   GURL extension_frame_url = extension->GetResourceURL("frame.html"); | 
 |   ui_test_utils::NavigateToURLWithDisposition( | 
 |       browser(), extension_frame_url, WindowOpenDisposition::NEW_BACKGROUND_TAB, | 
 |       ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP | | 
 |           ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB); | 
 |   content::RenderFrameHost* extension_frame = | 
 |       browser()->tab_strip_model()->GetWebContentsAt(1)->GetMainFrame(); | 
 |   EXPECT_EQ(extension_frame_url, extension_frame->GetLastCommittedURL()); | 
 |  | 
 |   // Based on activeTab, the extension shouldn't be initially granted access to | 
 |   // `kActiveTabHost`. | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 1: Initial fetch."); | 
 |     std::string cookies = | 
 |         fetch_via_extension_service_worker(extension_frame, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Do one pass of BrowserAction without granting activeTab permission, | 
 |   // extension still shouldn't have access to `kActiveTabHost`. | 
 |   ExtensionActionRunner::GetForWebContents(web_contents()) | 
 |       ->RunAction(extension, false); | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 2: After BrowserAction without granting access."); | 
 |     std::string cookies = | 
 |         fetch_via_extension_service_worker(extension_frame, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Granting activeTab permission to the extension should give it access to | 
 |   // `kActiveTabHost`. | 
 |   ExtensionActionRunner::GetForWebContents(web_contents()) | 
 |       ->RunAction(extension, true); | 
 |   { | 
 |     // ActiveTab access (just like OOR-CORS access) extends to the service | 
 |     // worker of an extension.  This is desirable, because | 
 |     // 1) there is no security boundary between A) extension service worker | 
 |     //    and B) extension frames in the tab | 
 |     // 2) it seems best to highlight #1 by simplistically granting extra | 
 |     //    capabilities to the whole extension (rather than forcing the extension | 
 |     //    authors to jump through extra hurdles to utilize the new capability). | 
 |     SCOPED_TRACE("TEST STEP 3: After granting ActiveTab access."); | 
 |     std::string cookies = | 
 |         fetch_via_extension_service_worker(extension_frame, kActiveTabHost); | 
 |     ExpectSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Navigating the tab to a different, same-origin document should retain | 
 |   // extension's access to the origin. | 
 |   GURL another_document_url = | 
 |       test_server()->GetURL(kActiveTabHost, "/title2.html"); | 
 |   EXPECT_NE(another_document_url, original_document_url); | 
 |   EXPECT_EQ(url::Origin::Create(another_document_url), | 
 |             url::Origin::Create(original_document_url)); | 
 |   ui_test_utils::NavigateToURL(browser(), another_document_url); | 
 |   { | 
 |     SCOPED_TRACE( | 
 |         "TEST STEP 4: After navigating the tab cross-document, " | 
 |         "but still same-origin."); | 
 |     std::string cookies = | 
 |         fetch_via_extension_service_worker(extension_frame, kActiveTabHost); | 
 |     ExpectSameSiteCookies(cookies); | 
 |   } | 
 |  | 
 |   // Navigating the tab to a different origin should revoke extension's access | 
 |   // to the tab. | 
 |   GURL cross_origin_url = test_server()->GetURL("other.com", "/title1.html"); | 
 |   EXPECT_NE(url::Origin::Create(cross_origin_url), | 
 |             url::Origin::Create(original_document_url)); | 
 |   ui_test_utils::NavigateToURL(browser(), cross_origin_url); | 
 |   { | 
 |     SCOPED_TRACE("TEST STEP 5: After navigating the tab cross-origin."); | 
 |     std::string cookies = | 
 |         fetch_via_extension_service_worker(extension_frame, kActiveTabHost); | 
 |     ExpectNoSameSiteCookies(cookies); | 
 |   } | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, ExtensionSameSiteCookiesTest, ::testing::Bool()); | 
 |  | 
 | // Tests for special handling of SameParty cookies for extensions: A request | 
 | // should be treated as first-party for the purposes of SameParty cookies if the | 
 | // top frame is an extension with access to the requested URL; the extension has | 
 | // access to all the sites in the party context; and all the sites in | 
 | // party_context are same-party to the request URL/site. | 
 | // | 
 | // See URLLoader::ShouldForceIgnoreTopFrameParty(). | 
 | class ExtensionSamePartyCookiesTest : public ExtensionCookiesTest { | 
 |  public: | 
 |   ExtensionSamePartyCookiesTest() | 
 |       : same_party_cookies_(std::begin(kSamePartyCookies), | 
 |                             std::end(kSamePartyCookies)), | 
 |         no_same_party_cookies_(std::begin(kNoSamePartyCookies), | 
 |                                std::end(kNoSamePartyCookies)) { | 
 |     feature_list_.InitAndEnableFeature(net::features::kFirstPartySets); | 
 |   } | 
 |   ~ExtensionSamePartyCookiesTest() override = default; | 
 |   ExtensionSamePartyCookiesTest(const ExtensionSamePartyCookiesTest&) = delete; | 
 |   ExtensionSamePartyCookiesTest& operator=( | 
 |       const ExtensionSamePartyCookiesTest&) = delete; | 
 |  | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     ExtensionCookiesTest::SetUpCommandLine(command_line); | 
 |     command_line->AppendSwitchASCII(network::switches::kUseFirstPartySet, | 
 |                                     base::StrCat({ | 
 |                                         "https://", kPermittedOwner,       // | 
 |                                         ",https://", kPermittedMember,     // | 
 |                                         ",https://", kNotPermittedMember,  // | 
 |                                     })); | 
 |   } | 
 |  | 
 |  protected: | 
 |   // Sets an array of cookies with various SameParty values. | 
 |   void SetCookies(const std::string& host) { | 
 |     ExtensionCookiesTest::SetCookies( | 
 |         host, { | 
 |                   base::StrCat({kSamePartyCookie, kSamePartyAttribute}), | 
 |                   base::StrCat({kNoneCookie, kSameSiteNoneAttribute}), | 
 |               }); | 
 |   } | 
 |  | 
 |   const Extension* MakeExtension() override { | 
 |     return ExtensionCookiesTest::MakeExtension({ | 
 |         kPermissionPattern1, | 
 |         kPermissionPattern1Sub, | 
 |         kPermissionPattern2, | 
 |         kPermissionPattern3, | 
 |     }); | 
 |   } | 
 |  | 
 |   const std::vector<const char*>& same_party_cookies() { | 
 |     return same_party_cookies_; | 
 |   } | 
 |   const std::vector<const char*>& no_same_party_cookies() { | 
 |     return no_same_party_cookies_; | 
 |   } | 
 |  | 
 |  private: | 
 |   const std::vector<const char*> same_party_cookies_; | 
 |   const std::vector<const char*> no_same_party_cookies_; | 
 | }; | 
 |  | 
 | // Tests where the extension page initiates the request. The party_context is | 
 | // empty in these cases, so these tests verify that the extension has | 
 | // permissions for the request URL. | 
 | IN_PROC_BROWSER_TEST_F(ExtensionSamePartyCookiesTest, | 
 |                        ExtensionInitiated_Fetch) { | 
 |   const struct { | 
 |     const char* requested_host; | 
 |     const std::vector<const char*> expected_cookies; | 
 |   } test_cases[] = { | 
 |       {kPermittedOwner, same_party_cookies()}, | 
 |       {kPermittedOwnerSubdomain, same_party_cookies()}, | 
 |       {kNotPermittedOwnerSubdomain, no_same_party_cookies()}, | 
 |       {kPermittedMember, same_party_cookies()}, | 
 |       {kNotPermittedMember, no_same_party_cookies()}, | 
 |       {kPermittedNonMember, same_party_cookies()}, | 
 |   }; | 
 |  | 
 |   for (const auto& test : test_cases) { | 
 |     SetCookies(test.requested_host); | 
 |     content::RenderFrameHost* frame = NavigateMainFrameToExtensionPage(); | 
 |     EXPECT_THAT(AsCookies(FetchCookies(frame, test.requested_host)), | 
 |                 UnorderedElementsAreArray(test.expected_cookies)) | 
 |         << test.requested_host; | 
 |   } | 
 | } | 
 |  | 
 | // Tests with one frame on an extension page which makes the request. | 
 | IN_PROC_BROWSER_TEST_F(ExtensionSamePartyCookiesTest, OneEmbeddedFrame_Fetch) { | 
 |   const struct { | 
 |     const char* child_frame_host; | 
 |     const char* requested_host; | 
 |     const std::vector<const char*> expected_cookies; | 
 |   } test_cases[] = { | 
 |       {kPermittedOwner, kPermittedOwner, same_party_cookies()}, | 
 |       {kPermittedOwner, kPermittedMember, same_party_cookies()}, | 
 |       {kPermittedOwner, kNotPermittedMember, no_same_party_cookies()}, | 
 |       {kPermittedOwner, kPermittedNonMember, same_party_cookies()}, | 
 |       {kPermittedOwner, kNotPermittedOwnerSubdomain, no_same_party_cookies()}, | 
 |       {kPermittedMember, kPermittedOwnerSubdomain, same_party_cookies()}, | 
 |       {kPermittedNonMember, kPermittedOwner, no_same_party_cookies()}, | 
 |       {kNotPermittedMember, kPermittedOwner, no_same_party_cookies()}, | 
 |       {kNotPermittedMember, kPermittedMember, no_same_party_cookies()}, | 
 |       {kNotPermittedMember, kNotPermittedMember, no_same_party_cookies()}, | 
 |       {kPermittedOwnerSubdomain, kPermittedMember, same_party_cookies()}, | 
 |       {kPermittedOwnerSubdomain, kPermittedOwner, same_party_cookies()}, | 
 |       {kNotPermittedOwnerSubdomain, kNotPermittedMember, | 
 |        no_same_party_cookies()}, | 
 |       // We expect the SameParty cookie below because we only look at the | 
 |       // registrable domains of the party context, rather than the whole host. | 
 |       // So kNotPermittedOwnerSubdomain is treated the same as kPermittedOwner | 
 |       // when it's a member of the party context, and therefore the SameParty | 
 |       // cookie is sent. | 
 |       {kNotPermittedOwnerSubdomain, kPermittedMember, same_party_cookies()}, | 
 |   }; | 
 |  | 
 |   for (const auto& test : test_cases) { | 
 |     SetCookies(test.requested_host); | 
 |     content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |     content::RenderFrameHost* child_frame = | 
 |         MakeChildFrame(main_frame, test.child_frame_host); | 
 |     EXPECT_THAT(AsCookies(FetchCookies(child_frame, test.requested_host)), | 
 |                 UnorderedElementsAreArray(test.expected_cookies)) | 
 |         << test.child_frame_host << ", " << test.requested_host; | 
 |   } | 
 | } | 
 |  | 
 | // Tests with one frame on an extension page which navigates to another page. | 
 | // The party context is empty here, so it should not matter what the | 
 | // initiator-site is. | 
 | IN_PROC_BROWSER_TEST_F(ExtensionSamePartyCookiesTest, | 
 |                        OneEmbeddedFrame_Navigation) { | 
 |   const struct { | 
 |     const char* child_frame_host; | 
 |     const char* requested_host; | 
 |     const std::vector<const char*>& expected_cookies; | 
 |   } test_cases[] = { | 
 |       {kPermittedOwner, kPermittedOwner, same_party_cookies()}, | 
 |       {kPermittedOwner, kPermittedMember, same_party_cookies()}, | 
 |       {kPermittedOwner, kNotPermittedMember, no_same_party_cookies()}, | 
 |       // SameParty cookies are sent below because the SameParty attribute is | 
 |       // ignored for sites that are not members of a First-Party Set. | 
 |       {kPermittedOwner, kPermittedNonMember, same_party_cookies()}, | 
 |  | 
 |       {kPermittedOwner, kNotPermittedMember, no_same_party_cookies()}, | 
 |       {kPermittedOwner, kNotPermittedOwnerSubdomain, no_same_party_cookies()}, | 
 |       {kPermittedMember, kPermittedOwnerSubdomain, same_party_cookies()}, | 
 |       {kPermittedNonMember, kPermittedOwner, same_party_cookies()}, | 
 |       {kNotPermittedMember, kPermittedOwner, same_party_cookies()}, | 
 |       {kNotPermittedMember, kPermittedMember, same_party_cookies()}, | 
 |       {kNotPermittedMember, kNotPermittedMember, no_same_party_cookies()}, | 
 |       {kPermittedOwnerSubdomain, kPermittedMember, same_party_cookies()}, | 
 |       {kNotPermittedOwnerSubdomain, kNotPermittedMember, | 
 |        no_same_party_cookies()}, | 
 |   }; | 
 |  | 
 |   for (const auto& test : test_cases) { | 
 |     SetCookies(test.requested_host); | 
 |     content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |     content::RenderFrameHost* child_frame = | 
 |         MakeChildFrame(main_frame, test.child_frame_host); | 
 |     EXPECT_THAT( | 
 |         AsCookies(NavigateChildAndGetCookies(child_frame, test.requested_host)), | 
 |         UnorderedElementsAreArray(test.expected_cookies)) | 
 |         << test.child_frame_host << ", " << test.requested_host; | 
 |   } | 
 | } | 
 |  | 
 | // Tests where the current frame is a nested frame, which fetches from another | 
 | // URL. Here it doesn't actually matter what *host* the current frame is nested | 
 | // in (as long as the site is permitted), because we only check the ETLD+1. | 
 | IN_PROC_BROWSER_TEST_F(ExtensionSamePartyCookiesTest, NestedFrames_Fetch) { | 
 |   const struct { | 
 |     const char* middle_frame_host; | 
 |     const char* leaf_frame_host; | 
 |     const char* requested_host; | 
 |     const std::vector<const char*> expected_cookies; | 
 |   } test_cases[] = { | 
 |       { | 
 |           kPermittedOwner, | 
 |           kPermittedMember, | 
 |           kPermittedMember, | 
 |           same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kNotPermittedMember, | 
 |           kPermittedMember, | 
 |           kPermittedMember, | 
 |           no_same_party_cookies(), | 
 |       }, | 
 |       // In this case, the extension does not have access to the middle frame's | 
 |       // host, but does have access to the middle frame's ETLD+1. We expect | 
 |       // SameParty cookies to be sent only because we check the ETLD+1, rather | 
 |       // than the host. | 
 |       { | 
 |           kNotPermittedOwnerSubdomain, | 
 |           kPermittedMember, | 
 |           kPermittedMember, | 
 |           same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kNotPermittedNonMember, | 
 |           kPermittedOwner, | 
 |           kPermittedMember, | 
 |           no_same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kPermittedNonMember, | 
 |           kPermittedOwner, | 
 |           kPermittedOwner, | 
 |           no_same_party_cookies(), | 
 |       }, | 
 |   }; | 
 |  | 
 |   for (const auto& test : test_cases) { | 
 |     SetCookies(test.requested_host); | 
 |     content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |     content::RenderFrameHost* middle_frame = | 
 |         MakeChildFrame(main_frame, test.middle_frame_host); | 
 |     content::RenderFrameHost* leaf_frame = | 
 |         MakeChildFrame(middle_frame, test.leaf_frame_host); | 
 |  | 
 |     EXPECT_THAT(AsCookies(FetchCookies(leaf_frame, test.requested_host)), | 
 |                 UnorderedElementsAreArray(test.expected_cookies)) | 
 |         << test.middle_frame_host << ", " << test.leaf_frame_host << ", " | 
 |         << test.requested_host; | 
 |   } | 
 | } | 
 |  | 
 | // Tests where the current frame is a nested frame, which navigates to another | 
 | // URL. Here it doesn't actually matter what *host* the current frame is nested | 
 | // in (as long as the domain is permitted), because we only check the domain. | 
 | IN_PROC_BROWSER_TEST_F(ExtensionSamePartyCookiesTest, NestedFrames_Navigation) { | 
 |   const struct { | 
 |     const char* middle_frame_host; | 
 |     const char* leaf_frame_host; | 
 |     const char* requested_host; | 
 |     const std::vector<const char*> expected_cookies; | 
 |   } test_cases[] = { | 
 |       { | 
 |           kPermittedOwner, | 
 |           kPermittedMember, | 
 |           kPermittedMember, | 
 |           same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kNotPermittedMember, | 
 |           kPermittedMember, | 
 |           kPermittedMember, | 
 |           no_same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kNotPermittedOwnerSubdomain, | 
 |           kPermittedMember, | 
 |           kPermittedMember, | 
 |           same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kNotPermittedNonMember, | 
 |           kPermittedOwner, | 
 |           kPermittedMember, | 
 |           no_same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kPermittedNonMember, | 
 |           kPermittedOwner, | 
 |           kPermittedOwner, | 
 |           no_same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kPermittedOwner, | 
 |           kNotPermittedMember, | 
 |           kPermittedMember, | 
 |           same_party_cookies(), | 
 |       }, | 
 |       { | 
 |           kPermittedOwner, | 
 |           kPermittedNonMember, | 
 |           kPermittedMember, | 
 |           same_party_cookies(), | 
 |       }, | 
 |   }; | 
 |  | 
 |   for (const auto& test : test_cases) { | 
 |     SetCookies(test.requested_host); | 
 |     content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage(); | 
 |     content::RenderFrameHost* middle_frame = | 
 |         MakeChildFrame(main_frame, test.middle_frame_host); | 
 |     content::RenderFrameHost* leaf_frame = | 
 |         MakeChildFrame(middle_frame, test.leaf_frame_host); | 
 |  | 
 |     EXPECT_THAT( | 
 |         AsCookies(NavigateChildAndGetCookies(leaf_frame, test.requested_host)), | 
 |         UnorderedElementsAreArray(test.expected_cookies)) | 
 |         << test.middle_frame_host << ", " << test.leaf_frame_host << ", " | 
 |         << test.requested_host; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | }  // namespace extensions |