| // Copyright 2017 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 <cctype> |
| #include <cstddef> |
| #include <memory> |
| |
| #include "base/base_switches.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/fixed_flat_set.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial_param_associator.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/run_loop.h" |
| #include "base/strings/escape.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/synchronization/lock.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/content_settings/cookie_settings_factory.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h" |
| #include "chrome/browser/policy/policy_test_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/client_hints/common/client_hints.h" |
| #include "components/client_hints/common/switches.h" |
| #include "components/content_settings/browser/page_specific_content_settings.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/embedder_support/switches.h" |
| #include "components/embedder_support/user_agent_utils.h" |
| #include "components/metrics/content/subprocess_metrics_provider.h" |
| #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/client_hints.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/public/test/url_loader_interceptor.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/nqe/effective_connection_type.h" |
| #include "net/ssl/ssl_server_config.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/embedded_test_server/request_handler_util.h" |
| #include "net/url_request/url_request.h" |
| #include "services/network/public/cpp/client_hints.h" |
| #include "services/network/public/cpp/cors/cors.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/mojom/web_client_hints_types.mojom-shared.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/client_hints/client_hints.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/web_preferences/web_preferences.h" |
| #include "third_party/re2/src/re2/re2.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| using ::content::URLLoaderInterceptor; |
| using ::net::test_server::EmbeddedTestServer; |
| using ::testing::Contains; |
| using ::testing::Eq; |
| using ::testing::Key; |
| using ::testing::Not; |
| using ::testing::Optional; |
| |
| constexpr unsigned expected_client_hints_number = 18u; |
| constexpr unsigned expected_default_third_party_client_hints_number = 3u; |
| constexpr unsigned expected_requested_third_party_client_hints_number = 21u; |
| constexpr unsigned expected_pre_merge_third_party_client_hints_number = 13u; |
| |
| // All of the status codes from HttpResponseHeaders::IsRedirectResponseCode. |
| const net::HttpStatusCode kRedirectStatusCodes[] = { |
| net::HTTP_MOVED_PERMANENTLY, net::HTTP_FOUND, |
| net::HTTP_SEE_OTHER, net::HTTP_TEMPORARY_REDIRECT, |
| net::HTTP_PERMANENT_REDIRECT, |
| }; |
| |
| // An interceptor that records count of fetches and client hint headers for |
| // requests to https://{foo|bar}.com/non-existing-{image.jpg|iframe.html}. |
| class ThirdPartyURLLoaderInterceptor { |
| public: |
| explicit ThirdPartyURLLoaderInterceptor(const std::set<GURL> intercepted_urls) |
| : intercepted_urls_(intercepted_urls), |
| interceptor_(base::BindRepeating( |
| &ThirdPartyURLLoaderInterceptor::InterceptURLRequest, |
| base::Unretained(this))) {} |
| |
| ThirdPartyURLLoaderInterceptor(const ThirdPartyURLLoaderInterceptor&) = |
| delete; |
| ThirdPartyURLLoaderInterceptor& operator=( |
| const ThirdPartyURLLoaderInterceptor&) = delete; |
| |
| ~ThirdPartyURLLoaderInterceptor() = default; |
| |
| size_t request_count_seen() const { return request_count_seen_; } |
| |
| size_t client_hints_count_seen() const { return client_hints_count_seen_; } |
| |
| size_t unique_request_count_seen() const { |
| return unique_request_count_seen_; |
| } |
| |
| size_t client_hints_count_seen_on_unique_request() const { |
| return client_hints_count_seen_on_unique_request_; |
| } |
| |
| private: |
| bool InterceptURLRequest(URLLoaderInterceptor::RequestParams* params) { |
| if (intercepted_urls_.find(params->url_request.url) == |
| intercepted_urls_.end()) { |
| return false; |
| } |
| |
| bool url_has_not_visited = |
| visited_urls_.insert(params->url_request.url).second; |
| |
| request_count_seen_++; |
| |
| if (url_has_not_visited) { |
| unique_request_count_seen_++; |
| } |
| |
| for (const auto& elem : network::GetClientHintToNameMap()) { |
| const auto& header = elem.second; |
| if (params->url_request.headers.HasHeader(header)) { |
| client_hints_count_seen_++; |
| |
| if (url_has_not_visited) { |
| client_hints_count_seen_on_unique_request_++; |
| } |
| } |
| } |
| return false; |
| } |
| |
| std::set<GURL> intercepted_urls_; |
| |
| size_t request_count_seen_ = 0u; |
| |
| size_t client_hints_count_seen_ = 0u; |
| |
| URLLoaderInterceptor interceptor_; |
| |
| // Count to deduplicate third-party requests since the total number of third |
| // party request can be flaky on JS injected requests. |
| std::set<GURL> visited_urls_; |
| size_t unique_request_count_seen_ = 0u; |
| size_t client_hints_count_seen_on_unique_request_ = 0u; |
| }; |
| |
| // Returns true only if `header_value` satisfies ABNF: 1*DIGIT [ "." 1*DIGIT ] |
| bool IsSimilarToDoubleABNF(const std::string& header_value) { |
| if (header_value.empty()) |
| return false; |
| char first_char = header_value.at(0); |
| if (!isdigit(first_char)) |
| return false; |
| |
| bool period_found = false; |
| bool digit_found_after_period = false; |
| for (char ch : header_value) { |
| if (isdigit(ch)) { |
| if (period_found) { |
| digit_found_after_period = true; |
| } |
| continue; |
| } |
| if (ch == '.') { |
| if (period_found) |
| return false; |
| period_found = true; |
| continue; |
| } |
| return false; |
| } |
| if (period_found) |
| return digit_found_after_period; |
| return true; |
| } |
| |
| // Returns true only if `header_value` satisfies ABNF: 1*DIGIT |
| bool IsSimilarToIntABNF(const std::string& header_value) { |
| if (header_value.empty()) |
| return false; |
| |
| for (char ch : header_value) { |
| if (!isdigit(ch)) |
| return false; |
| } |
| return true; |
| } |
| |
| void OnUnblockOnProfileCreation(base::RunLoop* run_loop, |
| Profile* profile, |
| Profile::CreateStatus status) { |
| if (status == Profile::CREATE_STATUS_INITIALIZED) |
| run_loop->Quit(); |
| } |
| |
| // Return |true| in the following conditions: If we expect reduced user agent, |
| // user agent minor version matches "0.0.0" if reduced UA through UAReduction |
| // origin trial. or user agent minor version matches "0.X.0" if reduced UA |
| // through kReduceUserAgentMinorVersion experiment. Otherwise, return |false|. |
| // We should not always expect reduced UA when kReduceUserAgentMinorVersion |
| // feature turns on, it would give false positive test results when the feature |
| // turns on as default. For example, if we expect full UA in the UADeprecation |
| // origin trial with kReduceUserAgentMinorVersion turned on, the actual value |
| // gives reduced UA, and the validation will succeed in this case which causes |
| // us to ignore actual bugs in code. |
| void CheckUserAgentMinorVersion( |
| const std::string& user_agent_value, |
| const bool expected_user_agent_reduced, |
| const bool expected_reduced_ua_through_experiment) { |
| // A regular expression that matches Chrome/{major_version}.{minor_version} |
| // in the User-Agent string, where the {minor_version} is captured. |
| static constexpr char kChromeVersionRegex[] = |
| "Chrome/[0-9]+\\.([0-9]+\\.[0-9]+\\.[0-9]+)"; |
| // The minor version in the reduced UA string is always "0.0.0". |
| static constexpr char kReducedMinorVersion[] = "0.0.0"; |
| // The minor version in the ReduceUserAgentMinorVersion experiment is always |
| // "0.X.0", where X is the frozen build version. |
| const std::string kReduceUserAgentMinorVersion = |
| "0." + |
| std::string(blink::features::kUserAgentFrozenBuildVersion.Get().data()) + |
| ".0"; |
| |
| std::string minor_version; |
| EXPECT_TRUE(re2::RE2::PartialMatch(user_agent_value, kChromeVersionRegex, |
| &minor_version)); |
| |
| if (expected_user_agent_reduced) { |
| EXPECT_EQ(minor_version, expected_reduced_ua_through_experiment |
| ? kReduceUserAgentMinorVersion |
| : kReducedMinorVersion); |
| } else { |
| EXPECT_NE(minor_version, kReducedMinorVersion); |
| } |
| } |
| |
| // A helper function that returns true when the legacy GREASE implementation is |
| // seen. It relies on the old algorithm having only 3 possible permutations due |
| // to a very limited set of allowed special characters. This may be removed once |
| // the legacy algorithm is no longer supported for emergency situations. |
| bool SawOldGrease(const std::string& ua_ch_result) { |
| bool seen_legacy = false; |
| // The legacy GREASE algorithm had only semicolon and space, and thus had one |
| // of these three permutations. |
| const std::string old_grease_permutations[]{";Not A Brand", " Not;A Brand", |
| " Not A;Brand"}; |
| for (auto i : old_grease_permutations) { |
| seen_legacy = seen_legacy || (ua_ch_result.find(i) != std::string::npos); |
| } |
| return seen_legacy; |
| } |
| |
| // A helper function to determine whether the GREASE algorithm per the spec: |
| // https://wicg.github.io/ua-client-hints/#create-arbitrary-brands-section |
| // was observed in the client hint user agent header. |
| bool SawUpdatedGrease(const std::string& ua_ch_result) { |
| // The updated GREASE algorithm would contain at least two of these |
| // characters. |
| static constexpr char kUpdatedGreaseRegex[] = |
| "Not[ ()\\-.\\/:;=?_]A[ ()\\-.\\/:;=?_]Brand"; |
| return re2::RE2::PartialMatch(ua_ch_result, kUpdatedGreaseRegex); |
| } |
| |
| enum class UserAgentOriginTrialTestType { |
| UAReduction, |
| UADeprecation, |
| UAReductionAndDeprecation |
| }; |
| |
| struct OriginTrialTestOptions { |
| bool has_ot_token = true; |
| bool valid_ot_token = true; |
| bool has_accept_ch_header = true; |
| bool has_critical_ch_header = false; |
| }; |
| |
| class AlternatingCriticalCHRequestHandler { |
| public: |
| AlternatingCriticalCHRequestHandler() = default; |
| net::test_server::EmbeddedTestServer::HandleRequestCallback |
| GetRequestHandler() { |
| return base::BindRepeating( |
| &AlternatingCriticalCHRequestHandler::DifferentCriticalCH, |
| base::Unretained(this)); |
| } |
| |
| int request_count() { return request_count_; } |
| |
| void SetRedirectLocation(const GURL& redirect_location) { |
| redirect_location_ = redirect_location; |
| } |
| |
| void SetStatusCode(net::HttpStatusCode status_code) { |
| status_code_ = status_code; |
| } |
| |
| static constexpr char kCriticalCH[] = "/critical-ch"; |
| |
| private: |
| // A response that flips between two critical-ch headers |
| std::unique_ptr<net::test_server::HttpResponse> DifferentCriticalCH( |
| const net::test_server::HttpRequest& request) { |
| if (!base::StartsWith(request.relative_url, kCriticalCH)) |
| return nullptr; |
| |
| request_count_++; |
| |
| std::unique_ptr<net::test_server::BasicHttpResponse> response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| |
| if (redirect_location_) { |
| response->set_code(status_code_); |
| response->AddCustomHeader("Location", redirect_location_->spec()); |
| } |
| |
| // Always send client hints different from what were received. |
| if (request.headers.find(GetCHToken()) != request.headers.end()) |
| critical_ch_state_ = !critical_ch_state_; |
| |
| response->AddCustomHeader("Accept-CH", GetCHToken()); |
| response->AddCustomHeader("Critical-CH", GetCHToken()); |
| |
| return std::move(response); |
| } |
| |
| std::string GetCHToken() { |
| return critical_ch_state_ ? "sec-ch-ua-arch" : "sec-ch-ua-bitness"; |
| } |
| |
| bool critical_ch_state_ = true; |
| int request_count_ = 0; |
| absl::optional<GURL> redirect_location_; |
| net::HttpStatusCode status_code_ = net::HTTP_TEMPORARY_REDIRECT; |
| }; |
| |
| } // namespace |
| |
| class ClientHintsBrowserTest : public policy::PolicyTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| ClientHintsBrowserTest() |
| : http_server_(net::EmbeddedTestServer::TYPE_HTTP), |
| https_server_(net::EmbeddedTestServer::TYPE_HTTPS), |
| https_cross_origin_server_(net::EmbeddedTestServer::TYPE_HTTPS), |
| http2_server_(net::EmbeddedTestServer::TYPE_HTTPS, |
| net::test_server::HttpConnection::Protocol::kHttp2), |
| expect_client_hints_on_subresources_(false) { |
| http_server_.ServeFilesFromSourceDirectory("chrome/test/data/client_hints"); |
| https_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| https_cross_origin_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| http2_server_.ServeFilesFromSourceDirectory("chrome/test/data"); |
| |
| http_server_.RegisterRequestMonitor( |
| base::BindRepeating(&ClientHintsBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| https_server_.RegisterRequestMonitor( |
| base::BindRepeating(&ClientHintsBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| http2_server_.RegisterRequestMonitor( |
| base::BindRepeating(&ClientHintsBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| https_cross_origin_server_.RegisterRequestMonitor( |
| base::BindRepeating(&ClientHintsBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| https_cross_origin_server_.RegisterRequestHandler( |
| base::BindRepeating(&ClientHintsBrowserTest::RequestHandlerToRedirect, |
| base::Unretained(this))); |
| https_server_.RegisterRequestHandler(base::BindRepeating( |
| &ClientHintsBrowserTest::RequestHandlerToFetchCrossOriginIframe, |
| base::Unretained(this))); |
| |
| http2_server_.AddDefaultHandlers(); |
| |
| std::vector<std::string> accept_ch_tokens; |
| for (const auto& pair : network::GetClientHintToNameMap()) |
| accept_ch_tokens.push_back(pair.second); |
| |
| http2_server_.SetAlpsAcceptCH("", base::JoinString(accept_ch_tokens, ",")); |
| |
| EXPECT_TRUE(http_server_.Start()); |
| EXPECT_TRUE(https_server_.Start()); |
| EXPECT_TRUE(http2_server_.Start()); |
| EXPECT_TRUE(https_cross_origin_server_.Start()); |
| |
| EXPECT_NE(https_server_.base_url(), https_cross_origin_server_.base_url()); |
| |
| accept_ch_url_ = https_server_.GetURL("/accept_ch.html"); |
| http_equiv_accept_ch_url_ = |
| https_server_.GetURL("/http_equiv_accept_ch.html"); |
| meta_name_accept_ch_url_ = |
| https_server_.GetURL("/meta_name_accept_ch.html"); |
| |
| without_accept_ch_url_ = https_server_.GetURL("/without_accept_ch.html"); |
| EXPECT_TRUE(without_accept_ch_url_.SchemeIsHTTPOrHTTPS()); |
| EXPECT_TRUE(without_accept_ch_url_.SchemeIsCryptographic()); |
| |
| without_accept_ch_local_url_ = |
| http_server_.GetURL("/without_accept_ch.html"); |
| EXPECT_TRUE(without_accept_ch_local_url_.SchemeIsHTTPOrHTTPS()); |
| EXPECT_FALSE(without_accept_ch_local_url_.SchemeIsCryptographic()); |
| |
| without_accept_ch_img_localhost_ = |
| https_server_.GetURL("/without_accept_ch_img_localhost.html"); |
| without_accept_ch_img_foo_com_ = |
| https_server_.GetURL("/without_accept_ch_img_foo_com.html"); |
| accept_ch_with_iframe_url_ = |
| https_server_.GetURL("/accept_ch_with_iframe.html"); |
| http_equiv_accept_ch_with_iframe_url_ = |
| https_server_.GetURL("/http_equiv_accept_ch_with_iframe.html"); |
| meta_name_accept_ch_with_iframe_url_ = |
| https_server_.GetURL("/meta_name_accept_ch_with_iframe.html"); |
| accept_ch_with_subresource_url_ = |
| https_server_.GetURL("/accept_ch_with_subresource.html"); |
| http_equiv_accept_ch_with_subresource_url_ = |
| https_server_.GetURL("/http_equiv_accept_ch_with_subresource.html"); |
| meta_name_accept_ch_with_subresource_url_ = |
| https_server_.GetURL("/meta_name_accept_ch_with_subresource.html"); |
| accept_ch_with_subresource_iframe_url_ = |
| https_server_.GetURL("/accept_ch_with_subresource_iframe.html"); |
| http_equiv_accept_ch_with_subresource_iframe_url_ = https_server_.GetURL( |
| "/http_equiv_accept_ch_with_subresource_iframe." |
| "html"); |
| meta_name_accept_ch_with_subresource_iframe_url_ = https_server_.GetURL( |
| "/meta_name_accept_ch_with_subresource_iframe." |
| "html"); |
| accept_ch_img_localhost_ = |
| https_server_.GetURL("/accept_ch_img_localhost.html"); |
| http_equiv_accept_ch_img_localhost_ = |
| https_server_.GetURL("/http_equiv_accept_ch_img_localhost.html"); |
| meta_name_accept_ch_img_localhost_ = |
| https_server_.GetURL("/meta_name_accept_ch_img_localhost.html"); |
| |
| redirect_url_ = https_cross_origin_server_.GetURL("/redirect.html"); |
| |
| accept_ch_empty_ = https_server_.GetURL("/accept_ch_empty.html"); |
| http_equiv_accept_ch_injection_ = |
| https_server_.GetURL("/http_equiv_accept_ch_injection.html"); |
| meta_name_accept_ch_injection_ = |
| https_server_.GetURL("/meta_name_accept_ch_injection.html"); |
| http_equiv_accept_ch_delegation_foo_ = |
| https_server_.GetURL("/http_equiv_accept_ch_delegation_foo.html"); |
| meta_name_accept_ch_delegation_foo_ = |
| https_server_.GetURL("/meta_name_accept_ch_delegation_foo.html"); |
| http_equiv_accept_ch_delegation_bar_ = |
| https_server_.GetURL("/http_equiv_accept_ch_delegation_bar.html"); |
| meta_name_accept_ch_delegation_bar_ = |
| https_server_.GetURL("/meta_name_accept_ch_delegation_bar.html"); |
| http_equiv_accept_ch_delegation_merge_ = |
| https_server_.GetURL("/http_equiv_accept_ch_delegation_merge.html"); |
| meta_name_accept_ch_delegation_merge_ = |
| https_server_.GetURL("/meta_name_accept_ch_delegation_merge.html"); |
| http_equiv_accept_ch_merge_ = |
| https_server_.GetURL("/http_equiv_accept_ch_merge.html"); |
| meta_name_accept_ch_merge_ = |
| https_server_.GetURL("/meta_name_accept_ch_merge.html"); |
| |
| without_accept_ch_cross_origin_ = |
| https_cross_origin_server_.GetURL("/without_accept_ch.html"); |
| } |
| |
| ClientHintsBrowserTest(const ClientHintsBrowserTest&) = delete; |
| ClientHintsBrowserTest& operator=(const ClientHintsBrowserTest&) = delete; |
| |
| ~ClientHintsBrowserTest() override {} |
| |
| virtual std::unique_ptr<base::FeatureList> EnabledFeatures() { |
| std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); |
| feature_list->InitializeFromCommandLine( |
| "UserAgentClientHint,CriticalClientHint," |
| "AcceptCHFrame,PrefersColorSchemeClientHintHeader," |
| "ViewportHeightClientHintHeader", |
| ""); |
| return feature_list; |
| } |
| |
| void SetUp() override { |
| scoped_feature_list_.InitWithFeatureList(EnabledFeatures()); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| request_interceptor_ = std::make_unique<ThirdPartyURLLoaderInterceptor>( |
| (std::set<GURL>){GURL("https://foo.com/non-existing-image.jpg"), |
| GURL("https://foo.com/non-existing-iframe.html"), |
| GURL("https://bar.com/non-existing-image.jpg"), |
| GURL("https://bar.com/non-existing-iframe.html")}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void TearDownOnMainThread() override { request_interceptor_.reset(); } |
| |
| void SetUpCommandLine(base::CommandLine* cmd) override { |
| cmd->AppendSwitchASCII(network::switches::kForceEffectiveConnectionType, |
| net::kEffectiveConnectionType2G); |
| } |
| |
| void SetClientHintExpectationsOnMainFrame(bool expect_client_hints) { |
| expect_client_hints_on_main_frame_ = expect_client_hints; |
| } |
| |
| void SetClientHintExpectationsOnSubresources(bool expect_client_hints) { |
| base::AutoLock lock(expect_client_hints_on_subresources_lock_); |
| expect_client_hints_on_subresources_ = expect_client_hints; |
| } |
| |
| bool expect_client_hints_on_subresources() { |
| base::AutoLock lock(expect_client_hints_on_subresources_lock_); |
| return expect_client_hints_on_subresources_; |
| } |
| |
| // Verify that the user is not notified that cookies or JavaScript were |
| // blocked on the webpage due to the checks done by client hints. |
| void VerifyContentSettingsNotNotified() const { |
| auto* pscs = content_settings::PageSpecificContentSettings::GetForFrame( |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame()); |
| EXPECT_FALSE(pscs->IsContentBlocked(ContentSettingsType::COOKIES)); |
| EXPECT_FALSE(pscs->IsContentBlocked(ContentSettingsType::JAVASCRIPT)); |
| } |
| |
| void SetExpectedEffectiveConnectionType( |
| net::EffectiveConnectionType effective_connection_type) { |
| expected_ect = effective_connection_type; |
| } |
| |
| void SetJsEnabledForActiveView(bool enabled) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| blink::web_pref::WebPreferences prefs = |
| web_contents->GetOrCreateWebPreferences(); |
| prefs.javascript_enabled = enabled; |
| web_contents->SetWebPreferences(prefs); |
| } |
| |
| void TestProfilesIndependent(Browser* browser_a, Browser* browser_b); |
| void TestSwitchWithNewProfile(const std::string& switch_value, |
| size_t origins_stored); |
| |
| // A URL whose response headers include only Accept-CH header. |
| const GURL& accept_ch_url() const { return accept_ch_url_; } |
| const GURL& http_equiv_accept_ch_url() const { |
| return http_equiv_accept_ch_url_; |
| } |
| const GURL& meta_name_accept_ch_url() const { |
| return meta_name_accept_ch_url_; |
| } |
| |
| // A URL whose response headers do not include the Accept-CH header. |
| // Navigating to this URL also fetches an image. |
| const GURL& without_accept_ch_url() const { return without_accept_ch_url_; } |
| |
| // A URL whose response headers do not include the Accept-CH header. |
| // Navigating to this URL also fetches an image. |
| const GURL& without_accept_ch_local_url() const { |
| return without_accept_ch_local_url_; |
| } |
| |
| // A URL whose response headers do not include the Accept-CH header. |
| // Navigating to this URL also fetches an image from localhost. |
| const GURL& without_accept_ch_img_localhost() const { |
| return without_accept_ch_img_localhost_; |
| } |
| |
| // A URL whose response headers do not include the Accept-CH header. |
| // Navigating to this URL also fetches an image from foo.com. |
| const GURL& without_accept_ch_img_foo_com() const { |
| return without_accept_ch_img_foo_com_; |
| } |
| |
| // A URL whose response does not include the Accept-CH header. The response |
| // loads accept_ch_url() in an iframe. |
| const GURL& accept_ch_with_iframe_url() const { |
| return accept_ch_with_iframe_url_; |
| } |
| const GURL& http_equiv_accept_ch_with_iframe_url() const { |
| return http_equiv_accept_ch_with_iframe_url_; |
| } |
| const GURL& meta_name_accept_ch_with_iframe_url() const { |
| return meta_name_accept_ch_with_iframe_url_; |
| } |
| |
| // A URL whose response does not include the Accept-CH header. The response |
| // loads accept_ch_url() as a subresource in the main frame. |
| const GURL& accept_ch_with_subresource_url() const { |
| return accept_ch_with_subresource_url_; |
| } |
| const GURL& http_equiv_accept_ch_with_subresource_url() const { |
| return http_equiv_accept_ch_with_subresource_url_; |
| } |
| const GURL& meta_name_accept_ch_with_subresource_url() const { |
| return meta_name_accept_ch_with_subresource_url_; |
| } |
| |
| // A URL whose response does not include the Accept-CH header. The response |
| // loads accept_ch_url() or {http_equiv|meta_name}_accept_ch_url() as a |
| // subresource in the iframe. |
| const GURL& accept_ch_with_subresource_iframe_url() const { |
| return accept_ch_with_subresource_iframe_url_; |
| } |
| const GURL& http_equiv_accept_ch_with_subresource_iframe_url() const { |
| return http_equiv_accept_ch_with_subresource_iframe_url_; |
| } |
| const GURL& meta_name_accept_ch_with_subresource_iframe_url() const { |
| return meta_name_accept_ch_with_subresource_iframe_url_; |
| } |
| |
| // A URL whose response includes only Accept-CH header. Navigating to |
| // this URL also fetches two images: One from the localhost, and one from |
| // foo.com. |
| const GURL& accept_ch_img_localhost() const { |
| return accept_ch_img_localhost_; |
| } |
| const GURL& http_equiv_accept_ch_img_localhost() const { |
| return http_equiv_accept_ch_img_localhost_; |
| } |
| const GURL& meta_name_accept_ch_img_localhost() const { |
| return meta_name_accept_ch_img_localhost_; |
| } |
| |
| const GURL& redirect_url() const { return redirect_url_; } |
| |
| // A URL to a page with a response containing an empty accept_ch header. |
| const GURL& accept_ch_empty() const { return accept_ch_empty_; } |
| |
| // A page where hints are injected via javascript into an http-equiv meta tag. |
| const GURL& http_equiv_accept_ch_injection() const { |
| return http_equiv_accept_ch_injection_; |
| } |
| const GURL& meta_name_accept_ch_injection() const { |
| return meta_name_accept_ch_injection_; |
| } |
| |
| // A page where hints are delegated to the third-party site `foo.com`. |
| const GURL& http_equiv_accept_ch_delegation_foo() const { |
| return http_equiv_accept_ch_delegation_foo_; |
| } |
| const GURL& meta_name_accept_ch_delegation_foo() const { |
| return meta_name_accept_ch_delegation_foo_; |
| } |
| |
| // A page where hints are delegated to the third-party site `bar.com`. |
| const GURL& http_equiv_accept_ch_delegation_bar() const { |
| return http_equiv_accept_ch_delegation_bar_; |
| } |
| const GURL& meta_name_accept_ch_delegation_bar() const { |
| return meta_name_accept_ch_delegation_bar_; |
| } |
| |
| // A page where hints are delegated to the third-party sites in HTTP and HTML. |
| const GURL& http_equiv_accept_ch_delegation_merge() const { |
| return http_equiv_accept_ch_delegation_merge_; |
| } |
| const GURL& meta_name_accept_ch_delegation_merge() const { |
| return meta_name_accept_ch_delegation_merge_; |
| } |
| |
| // A page where some hints are in accept-ch header, some in http-equiv. |
| const GURL& http_equiv_accept_ch_merge() const { |
| return http_equiv_accept_ch_merge_; |
| } |
| const GURL& meta_name_accept_ch_merge() const { |
| return meta_name_accept_ch_merge_; |
| } |
| |
| const GURL& without_accept_ch_cross_origin() { |
| return without_accept_ch_cross_origin_; |
| } |
| |
| GURL GetHttp2Url(const std::string& relative_url) const { |
| return http2_server_.GetURL(relative_url); |
| } |
| |
| size_t count_user_agent_hint_headers_seen() const { |
| base::AutoLock lock(count_headers_lock_); |
| return count_user_agent_hint_headers_seen_; |
| } |
| |
| size_t count_ua_mobile_client_hints_headers_seen() const { |
| base::AutoLock lock(count_headers_lock_); |
| return count_ua_mobile_client_hints_headers_seen_; |
| } |
| |
| size_t count_ua_platform_client_hints_headers_seen() const { |
| base::AutoLock lock(count_headers_lock_); |
| return count_ua_platform_client_hints_headers_seen_; |
| } |
| |
| size_t count_save_data_client_hints_headers_seen() const { |
| base::AutoLock lock(count_headers_lock_); |
| return count_save_data_client_hints_headers_seen_; |
| } |
| |
| size_t count_client_hints_headers_seen() const { |
| base::AutoLock lock(count_headers_lock_); |
| return count_client_hints_headers_seen_; |
| } |
| |
| size_t third_party_request_count_seen() const { |
| return request_interceptor_->request_count_seen(); |
| } |
| |
| size_t third_party_client_hints_count_seen() const { |
| return request_interceptor_->client_hints_count_seen(); |
| } |
| |
| size_t third_party_unique_request_count_seen() const { |
| return request_interceptor_->unique_request_count_seen(); |
| } |
| |
| size_t third_party_client_hints_count_seen_on_unique_request() const { |
| return request_interceptor_->client_hints_count_seen_on_unique_request(); |
| } |
| |
| const std::string& main_frame_ua_observed() const { |
| return main_frame_ua_observed_; |
| } |
| |
| const std::string& main_frame_ua_full_version_observed() const { |
| return main_frame_ua_full_version_observed_; |
| } |
| |
| const std::string& main_frame_ua_full_version_list_observed() const { |
| return main_frame_ua_full_version_list_observed_; |
| } |
| |
| const std::string& main_frame_ua_mobile_observed() const { |
| return main_frame_ua_mobile_observed_; |
| } |
| |
| const std::string& main_frame_ua_platform_observed() const { |
| return main_frame_ua_platform_observed_; |
| } |
| |
| const std::string& main_frame_save_data_observed() const { |
| return main_frame_save_data_observed_; |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| std::string intercept_iframe_resource_; |
| bool intercept_to_http_equiv_iframe_ = false; |
| bool intercept_to_meta_name_iframe_ = false; |
| mutable base::Lock count_headers_lock_; |
| |
| Profile* GenerateNewProfile() { |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| base::FilePath current_profile_path = browser()->profile()->GetPath(); |
| |
| // Create an additional profile. |
| base::FilePath new_path = |
| profile_manager->GenerateNextProfileDirectoryPath(); |
| base::RunLoop run_loop; |
| profile_manager->CreateProfileAsync( |
| new_path, base::BindRepeating(&OnUnblockOnProfileCreation, &run_loop)); |
| run_loop.Run(); |
| |
| return profile_manager->GetProfile(new_path); |
| } |
| |
| private: |
| // Intercepts only the main frame requests that contain |
| // "redirect" in the resource path. The intercepted requests |
| // are served an HTML file that fetches an iframe from a cross-origin HTTPS |
| // server. |
| std::unique_ptr<net::test_server::HttpResponse> RequestHandlerToRedirect( |
| const net::test_server::HttpRequest& request) { |
| // Check if it's a main frame request. |
| if (request.relative_url.find(".html") == std::string::npos) |
| return nullptr; |
| |
| if (request.GetURL().spec().find("redirect") == std::string::npos) |
| return nullptr; |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_FOUND); |
| response->AddCustomHeader("Location", without_accept_ch_url().spec()); |
| return std::move(response); |
| } |
| |
| // Intercepts only the main frame requests that contain |
| // `intercept_iframe_resource_` in the resource path. The intercepted requests |
| // are served an HTML file that fetches an iframe from a cross-origin HTTPS |
| // server. |
| std::unique_ptr<net::test_server::HttpResponse> |
| RequestHandlerToFetchCrossOriginIframe( |
| const net::test_server::HttpRequest& request) { |
| if (intercept_iframe_resource_.empty()) |
| return nullptr; |
| |
| // Check if it's a main frame request. |
| if (request.relative_url.find(".html") == std::string::npos) |
| return nullptr; |
| |
| if (request.relative_url.find(intercept_iframe_resource_) == |
| std::string::npos) { |
| return nullptr; |
| } |
| |
| const std::string iframe_url = |
| intercept_to_meta_name_iframe_ |
| ? https_cross_origin_server_.GetURL("/meta_name_accept_ch.html") |
| .spec() |
| : intercept_to_http_equiv_iframe_ |
| ? https_cross_origin_server_ |
| .GetURL("/http_equiv_accept_ch.html") |
| .spec() |
| : https_cross_origin_server_.GetURL("/accept_ch.html").spec(); |
| |
| std::unique_ptr<net::test_server::BasicHttpResponse> http_response( |
| new net::test_server::BasicHttpResponse()); |
| http_response->set_code(net::HTTP_OK); |
| http_response->set_content_type("text/html"); |
| http_response->set_content( |
| "<html>" |
| "<link rel='icon' href='data:;base64,='><head></head>" |
| "Empty file which uses link-rel to disable favicon fetches. " |
| "<iframe src='" + |
| iframe_url + "'></iframe></html>"); |
| |
| return std::move(http_response); |
| } |
| |
| static std::string UpdateHeaderObservation( |
| const net::test_server::HttpRequest& request, |
| const std::string& header) { |
| if (request.headers.find(header) != request.headers.end()) |
| return request.headers.find(header)->second; |
| else |
| return ""; |
| } |
| |
| // Called by `https_server_`. |
| void MonitorResourceRequest(const net::test_server::HttpRequest& request) { |
| bool is_main_frame_navigation = |
| (request.GetURL().spec().find(".html") != std::string::npos || |
| request.GetURL().spec().find("echoheader") != std::string::npos); |
| |
| if (is_main_frame_navigation && |
| request.GetURL().spec().find("redirect") != std::string::npos) { |
| return; |
| } |
| |
| if (is_main_frame_navigation) { |
| main_frame_ua_observed_ = UpdateHeaderObservation(request, "sec-ch-ua"); |
| main_frame_ua_full_version_observed_ = |
| UpdateHeaderObservation(request, "sec-ch-ua-full-version"); |
| main_frame_ua_full_version_list_observed_ = |
| UpdateHeaderObservation(request, "sec-ch-ua-full-version-list"); |
| main_frame_ua_mobile_observed_ = |
| UpdateHeaderObservation(request, "sec-ch-ua-mobile"); |
| main_frame_ua_platform_observed_ = |
| UpdateHeaderObservation(request, "sec-ch-ua-platform"); |
| main_frame_save_data_observed_ = |
| UpdateHeaderObservation(request, "save-data"); |
| |
| VerifyClientHintsReceived(expect_client_hints_on_main_frame_, request); |
| if (expect_client_hints_on_main_frame_) { |
| double value = 0.0; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("device-memory")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE(IsSimilarToDoubleABNF( |
| request.headers.find("device-memory")->second)); |
| main_frame_device_memory_observed_deprecated_ = value; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-device-memory")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE(IsSimilarToDoubleABNF( |
| request.headers.find("sec-ch-device-memory")->second)); |
| main_frame_device_memory_observed_ = value; |
| |
| EXPECT_TRUE( |
| base::StringToDouble(request.headers.find("dpr")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE(IsSimilarToDoubleABNF(request.headers.find("dpr")->second)); |
| main_frame_dpr_observed_deprecated_ = value; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-dpr")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE( |
| IsSimilarToDoubleABNF(request.headers.find("sec-ch-dpr")->second)); |
| main_frame_dpr_observed_ = value; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("viewport-width")->second, &value)); |
| EXPECT_TRUE( |
| IsSimilarToIntABNF(request.headers.find("viewport-width")->second)); |
| #if !BUILDFLAG(IS_ANDROID) |
| EXPECT_LT(0.0, value); |
| #else |
| EXPECT_EQ(980, value); |
| #endif |
| main_frame_viewport_width_observed_deprecated_ = value; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-viewport-width")->second, &value)); |
| EXPECT_TRUE(IsSimilarToIntABNF( |
| request.headers.find("sec-ch-viewport-width")->second)); |
| #if !BUILDFLAG(IS_ANDROID) |
| EXPECT_LT(0.0, value); |
| #else |
| EXPECT_EQ(980, value); |
| #endif |
| main_frame_viewport_width_observed_ = value; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-viewport-height")->second, &value)); |
| EXPECT_TRUE(IsSimilarToIntABNF( |
| request.headers.find("sec-ch-viewport-height")->second)); |
| EXPECT_LT(0.0, value); |
| |
| VerifyNetworkQualityClientHints(request); |
| } |
| } |
| |
| if (!is_main_frame_navigation) { |
| VerifyClientHintsReceived(expect_client_hints_on_subresources(), request); |
| |
| if (expect_client_hints_on_subresources()) { |
| double value = 0.0; |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("device-memory")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE(IsSimilarToDoubleABNF( |
| request.headers.find("device-memory")->second)); |
| if (main_frame_device_memory_observed_deprecated_ > 0) { |
| EXPECT_EQ(main_frame_device_memory_observed_deprecated_, value); |
| } |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-device-memory")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE(IsSimilarToDoubleABNF( |
| request.headers.find("sec-ch-device-memory")->second)); |
| if (main_frame_device_memory_observed_ > 0) { |
| EXPECT_EQ(main_frame_device_memory_observed_, value); |
| } |
| |
| EXPECT_TRUE( |
| base::StringToDouble(request.headers.find("dpr")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE(IsSimilarToDoubleABNF(request.headers.find("dpr")->second)); |
| if (main_frame_dpr_observed_deprecated_ > 0) { |
| EXPECT_EQ(main_frame_dpr_observed_deprecated_, value); |
| } |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-dpr")->second, &value)); |
| EXPECT_LT(0.0, value); |
| EXPECT_TRUE( |
| IsSimilarToDoubleABNF(request.headers.find("sec-ch-dpr")->second)); |
| if (main_frame_dpr_observed_ > 0) { |
| EXPECT_EQ(main_frame_dpr_observed_, value); |
| } |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("viewport-width")->second, &value)); |
| EXPECT_TRUE( |
| IsSimilarToIntABNF(request.headers.find("viewport-width")->second)); |
| #if !BUILDFLAG(IS_ANDROID) |
| EXPECT_LT(0.0, value); |
| #else |
| EXPECT_EQ(980, value); |
| #endif |
| #if BUILDFLAG(IS_ANDROID) |
| // TODO(tbansal): https://crbug.com/825892: Viewport width on main |
| // frame requests may be incorrect when the Chrome window is not |
| // maximized. |
| if (main_frame_viewport_width_observed_deprecated_ > 0) { |
| EXPECT_EQ(main_frame_viewport_width_observed_deprecated_, value); |
| } |
| #endif |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-viewport-width")->second, &value)); |
| EXPECT_TRUE(IsSimilarToIntABNF( |
| request.headers.find("sec-ch-viewport-width")->second)); |
| #if !BUILDFLAG(IS_ANDROID) |
| EXPECT_LT(0.0, value); |
| #else |
| EXPECT_EQ(980, value); |
| #endif |
| #if BUILDFLAG(IS_ANDROID) |
| // TODO(tbansal): https://crbug.com/825892: Viewport width on main |
| // frame requests may be incorrect when the Chrome window is not |
| // maximized. |
| if (main_frame_viewport_width_observed_ > 0) { |
| EXPECT_EQ(main_frame_viewport_width_observed_, value); |
| } |
| #endif |
| |
| EXPECT_TRUE(base::StringToDouble( |
| request.headers.find("sec-ch-viewport-height")->second, &value)); |
| EXPECT_TRUE(IsSimilarToIntABNF( |
| request.headers.find("sec-ch-viewport-height")->second)); |
| EXPECT_LT(0.0, value); |
| |
| VerifyNetworkQualityClientHints(request); |
| } |
| } |
| |
| for (const auto& elem : network::GetClientHintToNameMap()) { |
| const auto& header = elem.second; |
| if (base::Contains(request.headers, header)) { |
| base::AutoLock lock(count_headers_lock_); |
| // The user agent hint is special: |
| if (header == "sec-ch-ua") { |
| count_user_agent_hint_headers_seen_++; |
| } else if (header == "sec-ch-ua-mobile") { |
| count_ua_mobile_client_hints_headers_seen_++; |
| } else if (header == "sec-ch-ua-platform") { |
| count_ua_platform_client_hints_headers_seen_++; |
| } else if (header == "save-data") { |
| count_save_data_client_hints_headers_seen_++; |
| } else { |
| count_client_hints_headers_seen_++; |
| } |
| } |
| } |
| } |
| |
| void VerifyClientHintsReceived(bool expect_client_hints, |
| const net::test_server::HttpRequest& request) { |
| for (const auto& elem : network::GetClientHintToNameMap()) { |
| const auto& header = elem.second; |
| SCOPED_TRACE(testing::Message() << header); |
| SCOPED_TRACE(testing::Message() << request.GetURL().spec()); |
| // Resource width client hint is only attached on image subresources. |
| if (header == "width" || header == "sec-ch-width") { |
| continue; |
| } |
| |
| // `Sec-CH-UA`, `Sec-CH-UA-Mobile`, and `Sec-CH-UA-Platform` is attached |
| // on all requests. `Save-Data` is included by default when on. |
| if (header == "sec-ch-ua" || header == "sec-ch-ua-mobile" || |
| header == "sec-ch-ua-platform" || header == "save-data") { |
| continue; |
| } |
| |
| // Skip over the `Sec-CH-UA-Reduced` client hint because it is only added |
| // in the presence of a valid "UserAgentReduction" Origin Trial token. |
| // `Sec-CH-UA-Reduced` is tested via UaReducedOriginTrialBrowserTest |
| // below. |
| if (header == "sec-ch-ua-reduced") { |
| continue; |
| } |
| |
| // TODO(crbug.com/1286857): Skip over the `Sec-CH-UA-Full` client hint |
| // because it is only added in the presence of a valid |
| // "UserAgentDeprecation" Origin Trial token. Need to add `Sec-CH-UA-Full` |
| // corresponding tests. |
| if (header == "sec-ch-ua-full") { |
| continue; |
| } |
| |
| EXPECT_EQ(expect_client_hints, base::Contains(request.headers, header)); |
| } |
| } |
| |
| void VerifyNetworkQualityClientHints( |
| const net::test_server::HttpRequest& request) const { |
| // Effective connection type is forced to 2G using command line in these |
| // tests. |
| int rtt_value = 0.0; |
| EXPECT_TRUE( |
| base::StringToInt(request.headers.find("rtt")->second, &rtt_value)); |
| EXPECT_LE(0, rtt_value); |
| EXPECT_TRUE(IsSimilarToIntABNF(request.headers.find("rtt")->second)); |
| // Verify that RTT value is a multiple of 50 milliseconds. |
| EXPECT_EQ(0, rtt_value % 50); |
| EXPECT_GE(expected_ect == net::EFFECTIVE_CONNECTION_TYPE_2G ? 3000 : 500, |
| rtt_value); |
| |
| double mbps_value = 0.0; |
| EXPECT_TRUE(base::StringToDouble(request.headers.find("downlink")->second, |
| &mbps_value)); |
| EXPECT_LE(0, mbps_value); |
| EXPECT_TRUE( |
| IsSimilarToDoubleABNF(request.headers.find("downlink")->second)); |
| // Verify that the mbps value is a multiple of 0.050 mbps. |
| // Allow for small amount of noise due to double to integer conversions. |
| EXPECT_NEAR(0, (static_cast<int>(mbps_value * 1000)) % 50, 1); |
| EXPECT_GE(10.0, mbps_value); |
| |
| EXPECT_FALSE(request.headers.find("ect")->second.empty()); |
| |
| // TODO(tbansal): https://crbug.com/819244: When network servicification is |
| // enabled, the renderer processes do not receive notifications on |
| // change in the network quality. Hence, the network quality client hints |
| // are not set to the correct value on subresources. |
| bool is_main_frame_navigation = |
| request.GetURL().spec().find(".html") != std::string::npos; |
| if (is_main_frame_navigation) { |
| // Effective connection type is forced to 2G using command line in these |
| // tests. RTT is expected to be 1800 msec but leave some gap to account |
| // for added noise and randomization. |
| if (expected_ect == net::EFFECTIVE_CONNECTION_TYPE_2G) { |
| EXPECT_NEAR(1800, rtt_value, 360); |
| } else if (expected_ect == net::EFFECTIVE_CONNECTION_TYPE_3G) { |
| EXPECT_NEAR(450, rtt_value, 90); |
| } else { |
| NOTREACHED(); |
| } |
| |
| // Effective connection type is forced to 2G using command line in these |
| // tests. downlink is expected to be 0.075 Mbps but leave some gap to |
| // account for added noise and randomization. |
| if (expected_ect == net::EFFECTIVE_CONNECTION_TYPE_2G) { |
| EXPECT_NEAR(0.075, mbps_value, 0.05); |
| } else if (expected_ect == net::EFFECTIVE_CONNECTION_TYPE_3G) { |
| EXPECT_NEAR(0.4, mbps_value, 0.1); |
| } else { |
| NOTREACHED(); |
| } |
| |
| EXPECT_EQ(expected_ect == net::EFFECTIVE_CONNECTION_TYPE_2G ? "2g" : "3g", |
| request.headers.find("ect")->second); |
| } |
| } |
| |
| net::EmbeddedTestServer http_server_; |
| net::EmbeddedTestServer https_server_; |
| net::EmbeddedTestServer https_cross_origin_server_; |
| net::EmbeddedTestServer http2_server_; |
| GURL accept_ch_url_; |
| GURL http_equiv_accept_ch_url_; |
| GURL meta_name_accept_ch_url_; |
| GURL without_accept_ch_url_; |
| GURL without_accept_ch_local_url_; |
| GURL accept_ch_with_iframe_url_; |
| GURL http_equiv_accept_ch_with_iframe_url_; |
| GURL meta_name_accept_ch_with_iframe_url_; |
| GURL accept_ch_with_subresource_url_; |
| GURL http_equiv_accept_ch_with_subresource_url_; |
| GURL meta_name_accept_ch_with_subresource_url_; |
| GURL accept_ch_with_subresource_iframe_url_; |
| GURL http_equiv_accept_ch_with_subresource_iframe_url_; |
| GURL meta_name_accept_ch_with_subresource_iframe_url_; |
| GURL without_accept_ch_img_foo_com_; |
| GURL without_accept_ch_img_localhost_; |
| GURL accept_ch_img_localhost_; |
| GURL http_equiv_accept_ch_img_localhost_; |
| GURL meta_name_accept_ch_img_localhost_; |
| GURL redirect_url_; |
| GURL accept_ch_empty_; |
| GURL http_equiv_accept_ch_injection_; |
| GURL meta_name_accept_ch_injection_; |
| GURL http_equiv_accept_ch_delegation_foo_; |
| GURL meta_name_accept_ch_delegation_foo_; |
| GURL http_equiv_accept_ch_delegation_bar_; |
| GURL meta_name_accept_ch_delegation_bar_; |
| GURL http_equiv_accept_ch_delegation_merge_; |
| GURL meta_name_accept_ch_delegation_merge_; |
| GURL http_equiv_accept_ch_merge_; |
| GURL meta_name_accept_ch_merge_; |
| GURL without_accept_ch_cross_origin_; |
| |
| std::string main_frame_ua_observed_; |
| std::string main_frame_ua_full_version_observed_; |
| std::string main_frame_ua_full_version_list_observed_; |
| std::string main_frame_ua_mobile_observed_; |
| std::string main_frame_ua_platform_observed_; |
| std::string main_frame_save_data_observed_; |
| |
| double main_frame_dpr_observed_deprecated_ = -1; |
| double main_frame_dpr_observed_ = -1; |
| double main_frame_viewport_width_observed_deprecated_ = -1; |
| double main_frame_viewport_width_observed_ = -1; |
| double main_frame_device_memory_observed_deprecated_ = -1; |
| double main_frame_device_memory_observed_ = -1; |
| |
| // Expect client hints on all the main frame request. |
| bool expect_client_hints_on_main_frame_{false}; |
| // Expect client hints on all the subresource requests. |
| bool expect_client_hints_on_subresources_ |
| GUARDED_BY(expect_client_hints_on_subresources_lock_); |
| |
| base::Lock expect_client_hints_on_subresources_lock_; |
| |
| size_t count_user_agent_hint_headers_seen_{0}; |
| size_t count_ua_mobile_client_hints_headers_seen_{0}; |
| size_t count_ua_platform_client_hints_headers_seen_{0}; |
| size_t count_save_data_client_hints_headers_seen_{0}; |
| size_t count_client_hints_headers_seen_{0}; |
| |
| std::unique_ptr<ThirdPartyURLLoaderInterceptor> request_interceptor_{nullptr}; |
| |
| // Set to 2G in SetUpCommandLine(). |
| net::EffectiveConnectionType expected_ect = net::EFFECTIVE_CONNECTION_TYPE_2G; |
| }; |
| |
| // True if testing for http-equiv correctness. When set to true, the tests |
| // use webpages that may contain the http-equiv Accept-CH header. When set to |
| // false, the tests use webpages that set the headers in the HTTP response |
| // headers. |
| INSTANTIATE_TEST_SUITE_P(All, ClientHintsBrowserTest, testing::Bool()); |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, CorsChecks) { |
| for (const auto& elem : network::GetClientHintToNameMap()) { |
| const auto& header = elem.second; |
| // Do not test for headers that have not been enabled on the blink "stable" |
| // yet. |
| if (header == "rtt" || header == "downlink" || header == "ect") { |
| continue; |
| } |
| // Save-Data can only have the 'on' value so it's tested below. |
| if (header == "save-data") |
| continue; |
| EXPECT_TRUE( |
| network::cors::IsCorsSafelistedHeader(header, "42" /* value */)); |
| } |
| EXPECT_FALSE(network::cors::IsCorsSafelistedHeader("not-a-client-hint-header", |
| "" /* value */)); |
| EXPECT_TRUE( |
| network::cors::IsCorsSafelistedHeader("save-data", "on" /* value */)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, HttpEquivWorks) { |
| const GURL gurl = http_equiv_accept_ch_img_localhost(); |
| base::HistogramTester histogram_tester; |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| } |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, MetaNameWorks) { |
| const GURL gurl = meta_name_accept_ch_img_localhost(); |
| base::HistogramTester histogram_tester; |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| } |
| |
| // Loads a webpage that requests persisting of client hints. Verifies that |
| // the browser receives the mojo notification from the renderer and persists the |
| // client hints to the disk --- unless it's using http-equiv which shouldn't |
| // persist. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, ClientHintsHttps_HttpEquiv) { |
| base::HistogramTester histogram_tester; |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_url() : accept_ch_url(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| if (GetParam()) |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| else |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| if (GetParam()) { |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } else { |
| // client_hints_url() sets the expected number of client hints. |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| } |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, ClientHintsHttps_MetaName) { |
| base::HistogramTester histogram_tester; |
| const GURL gurl = GetParam() ? meta_name_accept_ch_url() : accept_ch_url(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| if (GetParam()) |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| else |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| if (GetParam()) { |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } else { |
| // client_hints_url() sets the expected number of client hints. |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, ClientHintsAlps) { |
| base::HistogramTester histogram_tester; |
| SetClientHintExpectationsOnMainFrame(true); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetHttp2Url("/blank.html"))); |
| histogram_tester.ExpectBucketCount( |
| "ClientHints.AcceptCHFrame", |
| content::AcceptCHFrameRestart::kNavigationRestarted, 1); |
| } |
| |
| // Ensure that Critical-CH doesn't restart if headers added via ALPS are already |
| // present. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| NoCriticalRestartIfHeadersPresentViaAlps) { |
| base::HistogramTester histogram_tester; |
| SetClientHintExpectationsOnMainFrame(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| GetHttp2Url("/client_hints/critical_ch_ua_full_version_list.html"))); |
| histogram_tester.ExpectBucketCount( |
| "ClientHints.AcceptCHFrame", |
| content::AcceptCHFrameRestart::kNavigationRestarted, 1); |
| histogram_tester.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, ClientHintsAlpsRestartLimit) { |
| net::test_server::EmbeddedTestServer server_1( |
| net::EmbeddedTestServer::TYPE_HTTPS, |
| net::test_server::HttpConnection::Protocol::kHttp2); |
| net::test_server::EmbeddedTestServer server_2( |
| net::EmbeddedTestServer::TYPE_HTTPS, |
| net::test_server::HttpConnection::Protocol::kHttp2); |
| |
| server_1.RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| auto http_response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); |
| http_response->AddCustomHeader("Location", server_2.GetURL("/").spec()); |
| return http_response; |
| })); |
| server_2.RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| auto http_response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); |
| http_response->AddCustomHeader("Location", server_1.GetURL("/").spec()); |
| return http_response; |
| })); |
| |
| server_1.SetAlpsAcceptCH("", "sec-ch-ua-arch"); |
| server_2.SetAlpsAcceptCH("", "sec-ch-ua-arch"); |
| |
| ASSERT_TRUE(server_1.Start()); |
| ASSERT_TRUE(server_2.Start()); |
| |
| base::HistogramTester histogram_tester; |
| content::TestNavigationObserver nav_observer( |
| browser()->tab_strip_model()->GetActiveWebContents(), 1); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), server_1.GetURL("/"))); |
| histogram_tester.ExpectBucketCount( |
| "ClientHints.AcceptCHFrame", |
| content::AcceptCHFrameRestart::kNavigationRestarted, |
| net::URLRequest::kMaxRedirects); |
| histogram_tester.ExpectBucketCount( |
| "ClientHints.AcceptCHFrame", |
| content::AcceptCHFrameRestart::kRedirectOverflow, 1); |
| EXPECT_EQ(net::ERR_TOO_MANY_ACCEPT_CH_RESTARTS, |
| nav_observer.last_net_error_code()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsAlpsNavigationPreload) { |
| SetClientHintExpectationsOnMainFrame(true); |
| const GURL kCreateServiceWorker = |
| GetHttp2Url("/service_worker/create_service_worker.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kCreateServiceWorker)); |
| EXPECT_EQ( |
| "DONE", |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| "register('/service_worker/navigation_preload_worker.js', '/');")); |
| |
| const GURL kEchoHeader = |
| GetHttp2Url("/echoheader?Service-Worker-Navigation-Preload"); |
| base::HistogramTester histogram_tester; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kEchoHeader)); |
| histogram_tester.ExpectBucketCount( |
| "ClientHints.AcceptCHFrame", |
| content::AcceptCHFrameRestart::kNavigationRestarted, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, PRE_ClientHintsClearSession) { |
| const GURL gurl = accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching `gurl` should persist the request for client hints iff using |
| // headers and not http-equiv. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, ClientHintsClearSession) { |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| |
| EXPECT_EQ( |
| base::FeatureList::IsEnabled(blink::features::kDurableClientHintsCache) |
| ? 1u |
| : 0u, |
| host_settings.size()); |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| } |
| |
| // Test that client hints are attached to subresources only if they belong |
| // to the same host as document host. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsHttpsSubresourceDifferentOrigin) { |
| const GURL gurl = accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Add client hints for the embedded test server. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| // Verify that the client hints settings for localhost have been saved. |
| ContentSettingsForOneType client_hints_settings; |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| ASSERT_EQ(1U, client_hints_settings.size()); |
| |
| // Copy the client hints setting for localhost to foo.com. |
| host_content_settings_map->SetWebsiteSettingDefaultScope( |
| GURL("https://foo.com/"), GURL(), ContentSettingsType::CLIENT_HINTS, |
| client_hints_settings.at(0).setting_value.Clone()); |
| |
| // Verify that client hints for the two hosts has been saved. |
| host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| ASSERT_EQ(2U, client_hints_settings.size()); |
| |
| // Navigating to without_accept_ch_img_localhost() should |
| // attach client hints to the image subresouce contained in that page since |
| // the image is located on the same server as the document origin. |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), |
| without_accept_ch_img_localhost())); |
| base::RunLoop().RunUntilIdle(); |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // The user agent hint is attached to all three requests, as is UA-mobile: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| |
| // Navigating to without_accept_ch_img_foo_com() should not |
| // attach client hints to the image subresouce contained in that page since |
| // the image is located on a different server as the document origin. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), without_accept_ch_img_foo_com())); |
| base::RunLoop().RunUntilIdle(); |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // The device-memory and dprheader is attached to the main frame request. |
| #if BUILDFLAG(IS_ANDROID) |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| #else |
| EXPECT_EQ(expected_client_hints_number * 3, |
| count_client_hints_headers_seen()); |
| #endif |
| |
| // Requests to third party servers should have three (3) client hints attached |
| // (`Sec-CH-UA`, `Sec-CH-UA-Mobile`, `Sec-CH-UA-Platform`). |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| } |
| |
| // Test that client hints are attached to subresources checks the right setting |
| // for OTR profile. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsHttpsSubresourceOffTheRecord) { |
| const GURL gurl = accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Add client hints for the embedded test server. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| // Main profile should get hints for both page and subresources. |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), |
| without_accept_ch_img_localhost())); |
| base::RunLoop().RunUntilIdle(); |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| |
| // OTR profile should get neither. |
| Browser* otr_browser = CreateIncognitoBrowser(browser()->profile()); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(otr_browser, |
| without_accept_ch_img_localhost())); |
| } |
| |
| // Verify that we send only major version information in the `Sec-CH-UA` header |
| // by default, regardless of opt-in. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, UserAgentVersion) { |
| const GURL gurl = accept_ch_url(); |
| |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| |
| // Navigate to a page that opts-into the header: the value should end with |
| // the major version, and not contain the full version. |
| SetClientHintExpectationsOnMainFrame(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string expected_ua = ua.SerializeBrandMajorVersionList(); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_TRUE(main_frame_ua_full_version_observed().empty()); |
| |
| // Navigate again, after the opt-in: the value should stay the major |
| // version. |
| SetClientHintExpectationsOnMainFrame(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string expected_full_version = "\"" + ua.full_version + "\""; |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_EQ(main_frame_ua_full_version_observed(), expected_full_version); |
| |
| std::string expected_full_version_list = ua.SerializeBrandFullVersionList(); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_EQ(main_frame_ua_full_version_list_observed(), |
| expected_full_version_list); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, UAHintsTabletMode) { |
| const GURL gurl = accept_ch_url(); |
| |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| |
| // First request: only minimal hints, no tablet override. |
| SetClientHintExpectationsOnMainFrame(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string expected_ua = ua.SerializeBrandMajorVersionList(); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_EQ(main_frame_ua_full_version_observed(), ""); |
| EXPECT_EQ(main_frame_ua_mobile_observed(), "?0"); |
| EXPECT_EQ(main_frame_ua_platform_observed(), "\"" + ua.platform + "\""); |
| EXPECT_EQ(main_frame_save_data_observed(), ""); |
| |
| // Second request: table override, all hints. |
| chrome::ToggleRequestTabletSite(browser()); |
| SetClientHintExpectationsOnMainFrame(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| std::string expected_full_version = "\"" + ua.full_version + "\""; |
| EXPECT_EQ(main_frame_ua_full_version_observed(), expected_full_version); |
| std::string expected_full_version_list = ua.SerializeBrandFullVersionList(); |
| EXPECT_EQ(main_frame_ua_full_version_list_observed(), |
| expected_full_version_list); |
| EXPECT_EQ(main_frame_ua_mobile_observed(), "?1"); |
| EXPECT_EQ(main_frame_ua_platform_observed(), "\"Android\""); |
| EXPECT_EQ(main_frame_save_data_observed(), ""); |
| } |
| |
| // TODO(morlovich): Move this into WebContentsImplBrowserTest once things are |
| // refactored enough that UA client hints actually work in content/ |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, UserAgentOverrideClientHints) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const std::string kHeaderPath = std::string("/echoheader?") + |
| net::HttpRequestHeaders::kUserAgent + |
| "&sec-ch-ua&sec-ch-ua-mobile"; |
| const GURL kUrl(embedded_test_server()->GetURL(kHeaderPath)); |
| |
| web_contents->SetUserAgentOverride( |
| blink::UserAgentOverride::UserAgentOnly("foo"), false); |
| // Not enabled first. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl)); |
| std::string header_value; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| web_contents, |
| "window.domAutomationController.send(document.body.textContent);", |
| &header_value)); |
| EXPECT_EQ(std::string::npos, header_value.find("foo")) << header_value; |
| |
| // Actually turn it on. |
| web_contents->GetController() |
| .GetLastCommittedEntry() |
| ->SetIsOverridingUserAgent(true); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl)); |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| web_contents, |
| "window.domAutomationController.send(document.body.textContent);", |
| &header_value)); |
| EXPECT_EQ("foo\nNone\nNone", header_value); |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractString(web_contents, |
| "window.domAutomationController.send(JSON." |
| "stringify(navigator.userAgentData));", |
| &header_value)); |
| EXPECT_EQ(R"({"brands":[],"mobile":false,"platform":""})", header_value); |
| |
| // Now actually provide values for the hints. |
| blink::UserAgentOverride ua_override; |
| ua_override.ua_string_override = "foobar"; |
| ua_override.ua_metadata_override.emplace(); |
| ua_override.ua_metadata_override->mobile = true; |
| ua_override.ua_metadata_override->brand_version_list.emplace_back( |
| "Foobarnator", "3.14"); |
| web_contents->SetUserAgentOverride(ua_override, false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl)); |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| web_contents, |
| "window.domAutomationController.send(document.body.textContent);", |
| &header_value)); |
| EXPECT_EQ("foobar\n\"Foobarnator\";v=\"3.14\"\n?1", header_value); |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractString(web_contents, |
| "window.domAutomationController.send(JSON." |
| "stringify(navigator.userAgentData));", |
| &header_value)); |
| const std::string kExpected = |
| "{\"brands\":[{\"brand\":\"Foobarnator\",\"version\":\"3.14\"}]," |
| "\"mobile\":true,\"platform\":\"\"}"; |
| EXPECT_EQ(kExpected, header_value); |
| } |
| |
| class ClientHintsUAOverrideBrowserTest : public ClientHintsBrowserTest { |
| public: |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature( |
| blink::features::kUACHOverrideBlank); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsUAOverrideBrowserTest, |
| UserAgentOverrideClientHints) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const std::string kHeaderPath = std::string("/echoheader?") + |
| net::HttpRequestHeaders::kUserAgent + |
| "&sec-ch-ua&sec-ch-ua-mobile"; |
| const GURL kUrl(embedded_test_server()->GetURL(kHeaderPath)); |
| |
| web_contents->SetUserAgentOverride( |
| blink::UserAgentOverride::UserAgentOnly("foo"), false); |
| web_contents->GetController() |
| .GetLastCommittedEntry() |
| ->SetIsOverridingUserAgent(true); |
| |
| // Since no value was provided for client hints, they are sent with blank or |
| // false values. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl)); |
| |
| std::string header_value; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| web_contents, |
| "window.domAutomationController.send(document.body.textContent);", |
| &header_value)); |
| EXPECT_EQ("foo\n\n?0", header_value); |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractString(web_contents, |
| "window.domAutomationController.send(JSON." |
| "stringify(navigator.userAgentData));", |
| &header_value)); |
| EXPECT_EQ(R"({"brands":[],"mobile":false,"platform":""})", header_value); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, EmptyAcceptCH) { |
| // First navigate to a page that enables hints. No CH for it yet, since |
| // nothing opted in. |
| GURL gurl = accept_ch_url(); |
| SetClientHintExpectationsOnMainFrame(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| // Now go to a page with blank Accept-CH. Should get hints from previous |
| // visit. |
| gurl = accept_ch_empty(); |
| SetClientHintExpectationsOnMainFrame(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| // Visiting again should not expect them since we opted out again. |
| SetClientHintExpectationsOnMainFrame(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, InjectAcceptCH_HttpEquiv) { |
| // Go to page where hints are injected via javascript into an http-equiv meta |
| // tag. It shouldn't get hints itself (due to first visit), |
| // but subresources should get all the client hints. |
| GURL gurl = http_equiv_accept_ch_injection(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, InjectAcceptCH_MetaName) { |
| // Go to page where hints are injected via javascript into an named meta |
| // tag. It shouldn't get hints itself (due to first visit), |
| // but subresources should get all the client hints. |
| GURL gurl = meta_name_accept_ch_injection(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateToFoo_HttpEquiv) { |
| // Go to a page which delegates hints to `foo.com`. |
| GURL gurl = http_equiv_accept_ch_delegation_foo(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| // Four unique requests are request to the following URLs: |
| // "https://foo.com/non-existing-image.jpg", |
| // "https://foo.com/non-existing-iframe.html", |
| // "https://bar.com/non-existing-image.jpg", |
| // "https://bar.com/non-existing-iframe.html" |
| EXPECT_EQ(4u, third_party_unique_request_count_seen()); |
| EXPECT_EQ(expected_default_third_party_client_hints_number * 4, |
| third_party_client_hints_count_seen_on_unique_request()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateToFoo_MetaName) { |
| // Go to a page which delegates hints to `foo.com`. |
| GURL gurl = meta_name_accept_ch_delegation_foo(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| EXPECT_EQ(4u, third_party_unique_request_count_seen()); |
| EXPECT_EQ(expected_requested_third_party_client_hints_number * 2 + |
| expected_default_third_party_client_hints_number * 2, |
| third_party_client_hints_count_seen_on_unique_request()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateToBar_HttpEquiv) { |
| // Go to a page which delegates hints to `bar.com`. |
| GURL gurl = http_equiv_accept_ch_delegation_bar(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| EXPECT_EQ(4u, third_party_unique_request_count_seen()); |
| EXPECT_EQ(expected_default_third_party_client_hints_number * 4, |
| third_party_client_hints_count_seen_on_unique_request()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateToBar_MetaName) { |
| // Go to a page which delegates hints to `bar.com`. |
| GURL gurl = meta_name_accept_ch_delegation_bar(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| EXPECT_EQ(4u, third_party_unique_request_count_seen()); |
| EXPECT_EQ(expected_requested_third_party_client_hints_number * 2 + |
| expected_default_third_party_client_hints_number * 2, |
| third_party_client_hints_count_seen_on_unique_request()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateAndMerge_HttpEquiv) { |
| // Go to a page which delegates hints in HTTP and HTML. |
| GURL gurl = http_equiv_accept_ch_delegation_merge(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| EXPECT_EQ(4u, third_party_unique_request_count_seen()); |
| EXPECT_EQ(expected_pre_merge_third_party_client_hints_number * 2 + |
| expected_requested_third_party_client_hints_number * 2, |
| third_party_client_hints_count_seen_on_unique_request()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateAndMerge_MetaName) { |
| // Go to a page which delegates hints in HTTP and HTML. |
| GURL gurl = meta_name_accept_ch_delegation_merge(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| EXPECT_EQ(4u, third_party_unique_request_count_seen()); |
| EXPECT_EQ(expected_requested_third_party_client_hints_number * 4, |
| third_party_client_hints_count_seen_on_unique_request()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, MergeAcceptCH_HttpEquiv) { |
| // Go to page where some hints are enabled by headers, some by |
| // http-equiv. It shouldn't get hints itself (due to first visit), |
| // but subresources should get all the client hints. |
| GURL gurl = http_equiv_accept_ch_merge(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, MergeAcceptCH_MetaName) { |
| // Go to page where some hints are enabled by headers, some by |
| // http-equiv. It shouldn't get hints itself (due to first visit), |
| // but subresources should get all the client hints. |
| GURL gurl = meta_name_accept_ch_merge(); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| } |
| |
| void ClientHintsBrowserTest::TestProfilesIndependent(Browser* browser_a, |
| Browser* browser_b) { |
| const GURL gurl = accept_ch_url(); |
| |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| |
| // Navigate `browser_a` to a page that opts-into the header: the value should |
| // end with the major version, and not contain the full version. |
| SetClientHintExpectationsOnMainFrame(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser_a, gurl)); |
| std::string expected_ua = ua.SerializeBrandMajorVersionList(); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_TRUE(main_frame_ua_full_version_observed().empty()); |
| |
| // Try again on `browser_a`, the header should have an effect there. |
| SetClientHintExpectationsOnMainFrame(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser_a, gurl)); |
| std::string expected_full_version = "\"" + ua.full_version + "\""; |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_EQ(main_frame_ua_full_version_observed(), expected_full_version); |
| // verify full version list |
| std::string expected_full_version_list = ua.SerializeBrandFullVersionList(); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_EQ(main_frame_ua_full_version_list_observed(), |
| expected_full_version_list); |
| |
| // Navigate on `browser_b`. That should still only have the major |
| // version. |
| SetClientHintExpectationsOnMainFrame(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser_b, gurl)); |
| EXPECT_EQ(main_frame_ua_observed(), expected_ua); |
| EXPECT_TRUE(main_frame_ua_full_version_observed().empty()); |
| } |
| |
| // Check that client hints attached to navigation inside OTR profiles |
| // use the right settings, regular -> OTR direction. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, OffTheRecordIndependent) { |
| TestProfilesIndependent(browser(), |
| CreateIncognitoBrowser(browser()->profile())); |
| } |
| |
| // Check that client hints attached to navigation inside OTR profiles |
| // use the right settings, OTR -> regular direction. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, OffTheRecordIndependent2) { |
| TestProfilesIndependent(CreateIncognitoBrowser(browser()->profile()), |
| browser()); |
| } |
| |
| // Only default client hints should be delegated to third party subresources. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, Default_HttpEquiv) { |
| GURL gurl; |
| unsigned update_event_count = 0; |
| if (GetParam()) { |
| gurl = http_equiv_accept_ch_img_localhost(); |
| } else { |
| gurl = accept_ch_img_localhost(); |
| update_event_count = 1; |
| } |
| |
| base::HistogramTester histogram_tester; |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| |
| // Add client hints for the embedded test server. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", |
| update_event_count); |
| |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| |
| // Requests to third party servers should not have client hints attached. |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| |
| // Client hints should not be sent to the third-party with the exception of |
| // the `Sec-CH-UA/-Platform/-Mobile))` hints sent every request. |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, Default_MetaName) { |
| GURL gurl; |
| unsigned update_event_count = 0; |
| if (GetParam()) { |
| gurl = meta_name_accept_ch_img_localhost(); |
| } else { |
| gurl = accept_ch_img_localhost(); |
| update_event_count = 1; |
| } |
| |
| base::HistogramTester histogram_tester; |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| |
| // Add client hints for the embedded test server. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", |
| update_event_count); |
| |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| |
| // Requests to third party servers should not have client hints attached. |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| |
| // Client hints should not be sent to the third-party with the exception of |
| // the `Sec-CH-UA/-Platform/-Mobile))` hints sent every request. |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| } |
| |
| // Loads a HTTPS webpage that does not request persisting of client hints. |
| // A same-origin iframe loaded by the webpage requests persistence of client |
| // hints. Since that's not a main frame, persistence should not happen. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| PersistenceRequestIframe_SameOrigin) { |
| const GURL gurl = accept_ch_with_iframe_url(); |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // accept_ch_with_iframe_url() loads |
| // accept_ch() in an iframe. The request to persist client |
| // hints from accept_ch() should not be persisted. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| |
| // Loads a HTTPS webpage that does not request persisting of client hints. |
| // An iframe loaded by the webpage from an cross origin server requests |
| // persistence of client hints. |
| // Verify that the request from the cross origin iframe is not honored, and |
| // client hints preference is not persisted. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| DisregardPersistenceRequestIframe_HttpEquivCrossOrigin) { |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_with_iframe_url() |
| : accept_ch_with_iframe_url(); |
| |
| intercept_iframe_resource_ = gurl.path(); |
| intercept_to_http_equiv_iframe_ = GetParam(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // accept_ch_with_iframe_url() loads |
| // accept_ch() in a cross origin iframe. The request to |
| // persist client hints from accept_ch() should be |
| // disregarded. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| DisregardPersistenceRequestIframe_MetaNameCrossOrigin) { |
| const GURL gurl = GetParam() ? meta_name_accept_ch_with_iframe_url() |
| : accept_ch_with_iframe_url(); |
| |
| intercept_iframe_resource_ = gurl.path(); |
| intercept_to_meta_name_iframe_ = GetParam(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // accept_ch_with_iframe_url() loads |
| // accept_ch() in a cross origin iframe. The request to |
| // persist client hints from accept_ch() should be |
| // disregarded. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| |
| // Loads a HTTPS webpage that does not request persisting of client hints. |
| // A subresource loaded by the webpage requests persistence of client hints. |
| // Verify that the request from the subresource is not honored, and client hints |
| // preference is not persisted. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| DisregardPersistenceRequestSubresource_HttpEquiv) { |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_with_subresource_url() |
| : accept_ch_with_subresource_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // accept_ch_with_subresource_url() loads |
| // accept_ch() as a subresource. The request to persist |
| // client hints from accept_ch() should be disregarded. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| DisregardPersistenceRequestSubresource_MetaName) { |
| const GURL gurl = GetParam() ? meta_name_accept_ch_with_subresource_url() |
| : accept_ch_with_subresource_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // accept_ch_with_subresource_url() loads |
| // accept_ch() as a subresource. The request to persist |
| // client hints from accept_ch() should be disregarded. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| |
| // Loads a HTTPS webpage that does not request persisting of client hints. |
| // A subresource loaded by the webpage in an iframe requests persistence of |
| // client hints. Verify that the request from the subresource in the iframe |
| // is not honored, and client hints preference is not persisted. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| DisregardPersistenceRequestSubresourceIframe_HttpEquiv) { |
| const GURL gurl = GetParam() |
| ? http_equiv_accept_ch_with_subresource_iframe_url() |
| : accept_ch_with_subresource_iframe_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // `gurl` loads accept_ch() or |
| // http_equiv_accept_ch_url() as a subresource in an iframe. |
| // The request to persist client hints from accept_ch() or |
| // http_equiv_accept_ch_url() should be disregarded. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| DisregardPersistenceRequestSubresourceIframe_MetaName) { |
| const GURL gurl = GetParam() |
| ? meta_name_accept_ch_with_subresource_iframe_url() |
| : accept_ch_with_subresource_iframe_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // `gurl` loads accept_ch() or |
| // meta_name_accept_ch_url() as a subresource in an iframe. |
| // The request to persist client hints from accept_ch() or |
| // meta_name_accept_ch_url() should be disregarded. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| |
| // Loads a webpage that does not request persisting of client hints. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, NoClientHintsHttps) { |
| base::HistogramTester histogram_tester; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| // no_client_hints_url() does not sets the client hints. |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsFollowedByNoClientHint_HttpEquiv) { |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_url() : accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching `gurl` should persist the request for client hints iff using |
| // headers and not http-equiv. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| if (GetParam()) |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| else |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| |
| if (GetParam()) { |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } else { |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| } |
| |
| SetClientHintExpectationsOnMainFrame(!GetParam()); |
| SetClientHintExpectationsOnSubresources(!GetParam()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(GetParam() ? 0 : expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsFollowedByNoClientHint_MetaName) { |
| const GURL gurl = GetParam() ? meta_name_accept_ch_url() : accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching `gurl` should persist the request for client hints iff using |
| // headers and not http-equiv. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| if (GetParam()) |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0); |
| else |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| |
| if (GetParam()) { |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateSize", 0); |
| } else { |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| } |
| |
| SetClientHintExpectationsOnMainFrame(!GetParam()); |
| SetClientHintExpectationsOnSubresources(!GetParam()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(GetParam() ? 0 : expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| } |
| |
| // The test first fetches a page that sets Accept-CH. Next, it fetches a URL |
| // from a different origin. However, that URL response redirects to the same |
| // origin from where the first page was fetched. The test verifies that on |
| // receiving redirect to an origin for which the browser has persisted client |
| // hints prefs, the browser attaches the client hints headers when fetching the |
| // redirected URL. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsFollowedByRedirectToNoClientHint) { |
| const GURL gurl = accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching `gurl` should persist the request for client hints. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), redirect_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| } |
| |
| // Ensure that even when cookies are blocked, client hint preferences are |
| // persisted. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsPersistedCookiesBlocked) { |
| const GURL gurl_with = accept_ch_url(); |
| |
| scoped_refptr<content_settings::CookieSettings> cookie_settings_ = |
| CookieSettingsFactory::GetForProfile(browser()->profile()); |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| // Block cookies. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(gurl_with, GURL(), |
| ContentSettingsType::COOKIES, |
| CONTENT_SETTING_BLOCK); |
| |
| // Fetching `gurl_with` should persist the request for client hints. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl_with)); |
| histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 1); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| VerifyContentSettingsNotNotified(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsAttachedCookiesBlocked) { |
| const GURL gurl_with = accept_ch_url(); |
| const GURL gurl_without = accept_ch_url(); |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching `gurl_with` should persist the request for client hints. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl_with)); |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| // Block the cookies: Client hints should be attached. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(gurl_without, GURL(), |
| ContentSettingsType::COOKIES, |
| CONTENT_SETTING_BLOCK); |
| |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::COOKIES); |
| } |
| |
| // Ensure that when JavaScript is blocked, client hint preferences are not |
| // persisted. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsNotPersistedJavaScriptBlocked_HttpEquiv) { |
| ContentSettingsForOneType host_settings; |
| |
| // Start a navigation. This navigation makes it possible to block JavaScript |
| // later. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| const GURL gurl = |
| GetParam() ? http_equiv_accept_ch_with_iframe_url() : accept_ch_url(); |
| |
| // Block JavaScript: Client hint preferences should not be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope( |
| gurl, GURL(), ContentSettingsType::JAVASCRIPT, CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_url())); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| VerifyContentSettingsNotNotified(); |
| |
| // Allow JavaScript: Client hint preferences should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope( |
| gurl, GURL(), ContentSettingsType::JAVASCRIPT, CONTENT_SETTING_ALLOW); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_url())); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsNotPersistedJavaScriptBlocked_MetaName) { |
| ContentSettingsForOneType host_settings; |
| |
| // Start a navigation. This navigation makes it possible to block JavaScript |
| // later. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| const GURL gurl = |
| GetParam() ? meta_name_accept_ch_with_iframe_url() : accept_ch_url(); |
| |
| // Block JavaScript: Client hint preferences should not be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope( |
| gurl, GURL(), ContentSettingsType::JAVASCRIPT, CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_url())); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| VerifyContentSettingsNotNotified(); |
| |
| // Allow JavaScript: Client hint preferences should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope( |
| gurl, GURL(), ContentSettingsType::JAVASCRIPT, CONTENT_SETTING_ALLOW); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_url())); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| } |
| |
| // Ensure that when JavaScript is blocked, persisted client hints are not |
| // attached to the request headers. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsNotAttachedJavaScriptBlocked) { |
| const GURL gurl = accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching accept_ch_url() should persist the request for |
| // client hints. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| EXPECT_EQ(1u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(1u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(1u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| // Block JavaScript via WebPreferences: Client hints should not be attached. |
| SetJsEnabledForActiveView(false); |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| VerifyContentSettingsNotNotified(); |
| EXPECT_EQ(1u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(1u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(1u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| SetJsEnabledForActiveView(true); |
| |
| // Block JavaScript via ContentSetting: Client hints should not be attached. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(without_accept_ch_url(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| VerifyContentSettingsNotNotified(); |
| EXPECT_EQ(1u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(1u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(1u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Allow JavaScript: Client hints should now be attached. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(without_accept_ch_url(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_ALLOW); |
| |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| } |
| |
| // Test that if the content settings are malformed, then the browser does not |
| // crash. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsMalformedContentSettings) { |
| ContentSettingsForOneType client_hints_settings; |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| |
| // Add setting for the host. |
| std::unique_ptr<base::ListValue> client_hints_list = |
| std::make_unique<base::ListValue>(); |
| client_hints_list->Append(42 /* client hint value */); |
| auto client_hints_dictionary = std::make_unique<base::DictionaryValue>(); |
| client_hints_dictionary->SetList(client_hints::kClientHintsSettingKey, |
| std::move(client_hints_list)); |
| host_content_settings_map->SetWebsiteSettingDefaultScope( |
| without_accept_ch_url(), GURL(), ContentSettingsType::CLIENT_HINTS, |
| client_hints_dictionary->Clone()); |
| |
| // Reading the settings should now return one setting. |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| EXPECT_EQ(1U, client_hints_settings.size()); |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| } |
| |
| // Ensure that when JavaScript is blocked, client hints requested using |
| // Accept-CH are not attached to the request headers for subresources. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsScriptNotAllowed_HttpEquiv) { |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_url() : accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Block Javascript: Client hints should not be attached. |
| SetClientHintExpectationsOnSubresources(false); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(accept_ch_img_localhost(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), accept_ch_img_localhost())); |
| EXPECT_EQ(0u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(0u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| EXPECT_EQ(0u, third_party_client_hints_count_seen()); |
| |
| // Allow Javascript: Client hints should now be attached. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(accept_ch_img_localhost(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_ALLOW); |
| |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), accept_ch_img_localhost())); |
| |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| EXPECT_EQ(2u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| VerifyContentSettingsNotNotified(); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| |
| // Block Javascript again: Client hints should not be attached. |
| SetClientHintExpectationsOnSubresources(false); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(accept_ch_img_localhost(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), accept_ch_img_localhost())); |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| EXPECT_EQ(3u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsScriptNotAllowed_MetaName) { |
| const GURL gurl = GetParam() ? meta_name_accept_ch_url() : accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Block Javascript: Client hints should not be attached. |
| SetClientHintExpectationsOnSubresources(false); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(accept_ch_img_localhost(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), accept_ch_img_localhost())); |
| EXPECT_EQ(0u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(0u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| EXPECT_EQ(0u, third_party_client_hints_count_seen()); |
| |
| // Allow Javascript: Client hints should now be attached. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(accept_ch_img_localhost(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_ALLOW); |
| |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), accept_ch_img_localhost())); |
| |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| EXPECT_EQ(2u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| VerifyContentSettingsNotNotified(); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| |
| // Block Javascript again: Client hints should not be attached. |
| SetClientHintExpectationsOnSubresources(false); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(accept_ch_img_localhost(), GURL(), |
| ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_BLOCK); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), accept_ch_img_localhost())); |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| EXPECT_EQ(3u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::JAVASCRIPT); |
| } |
| |
| // Ensure that when the cookies is blocked, client hints are attached to the |
| // request headers. |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsCookiesNotAllowed_HttpEquiv) { |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_img_localhost() |
| : accept_ch_img_localhost(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| scoped_refptr<content_settings::CookieSettings> cookie_settings_ = |
| CookieSettingsFactory::GetForProfile(browser()->profile()); |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Block cookies. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope( |
| gurl, GURL(), ContentSettingsType::COOKIES, CONTENT_SETTING_BLOCK); |
| base::RunLoop().RunUntilIdle(); |
| |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::COOKIES); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, |
| ClientHintsCookiesNotAllowed_MetaName) { |
| const GURL gurl = GetParam() ? meta_name_accept_ch_img_localhost() |
| : accept_ch_img_localhost(); |
| |
| base::HistogramTester histogram_tester; |
| ContentSettingsForOneType host_settings; |
| scoped_refptr<content_settings::CookieSettings> cookie_settings_ = |
| CookieSettingsFactory::GetForProfile(browser()->profile()); |
| |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Block cookies. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope( |
| gurl, GURL(), ContentSettingsType::COOKIES, CONTENT_SETTING_BLOCK); |
| base::RunLoop().RunUntilIdle(); |
| |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| EXPECT_EQ(2u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(2u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(2u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number, count_client_hints_headers_seen()); |
| EXPECT_EQ(1u, third_party_request_count_seen()); |
| EXPECT_EQ(3u, third_party_client_hints_count_seen()); |
| |
| // Clear settings. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->ClearSettingsForOneType(ContentSettingsType::COOKIES); |
| } |
| |
| // Verify that client hints are sent in the incognito profiles, and server |
| // client hint opt-ins are honored within the incognito profile. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, |
| ClientHintsFollowedByNoClientHintIncognito) { |
| const GURL gurl = accept_ch_url(); |
| |
| base::HistogramTester histogram_tester; |
| Browser* incognito = CreateIncognitoBrowser(); |
| ContentSettingsForOneType host_settings; |
| |
| HostContentSettingsMapFactory::GetForProfile(incognito->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(0u, host_settings.size()); |
| |
| // Fetching `gurl` should persist the request for client hints. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito, gurl)); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", |
| expected_client_hints_number, 1); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(incognito->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::CLIENT_HINTS, |
| &host_settings); |
| EXPECT_EQ(1u, host_settings.size()); |
| |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito, without_accept_ch_url())); |
| |
| // The user agent hint is attached to all three requests: |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // Expected number of hints attached to the image request, and the same number |
| // to the main frame request. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| |
| // Navigate using regular profile. Client hints should not be send. |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // The user agent hint is attached to the two new requests. |
| EXPECT_EQ(5u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(5u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(5u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| |
| // No additional hints are sent. |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| } |
| |
| class ClientHintsWebHoldbackBrowserTest : public ClientHintsBrowserTest { |
| public: |
| ClientHintsWebHoldbackBrowserTest() : ClientHintsBrowserTest() { |
| ConfigureHoldbackExperiment(); |
| } |
| |
| net::EffectiveConnectionType web_effective_connection_type_override() const { |
| return web_effective_connection_type_override_; |
| } |
| |
| std::unique_ptr<base::FeatureList> EnabledFeatures() override { |
| base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting(); |
| const std::string kTrialName = "TrialFoo"; |
| const std::string kGroupName = "GroupFoo"; // Value not used |
| |
| scoped_refptr<base::FieldTrial> trial = |
| base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName); |
| |
| std::map<std::string, std::string> params; |
| |
| params["web_effective_connection_type_override"] = |
| net::GetNameForEffectiveConnectionType( |
| web_effective_connection_type_override_); |
| EXPECT_TRUE( |
| base::FieldTrialParamAssociator::GetInstance() |
| ->AssociateFieldTrialParams(kTrialName, kGroupName, params)); |
| |
| std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); |
| feature_list->InitializeFromCommandLine( |
| "UserAgentClientHint,PrefersColorSchemeClientHintHeader," |
| "ViewportHeightClientHintHeader", |
| ""); |
| feature_list->RegisterFieldTrialOverride( |
| features::kNetworkQualityEstimatorWebHoldback.name, |
| base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get()); |
| return feature_list; |
| } |
| |
| private: |
| void ConfigureHoldbackExperiment() {} |
| |
| const net::EffectiveConnectionType web_effective_connection_type_override_ = |
| net::EFFECTIVE_CONNECTION_TYPE_3G; |
| }; |
| |
| // Make sure that when NetInfo holdback experiment is enabled, the NetInfo APIs |
| // and client hints return the overridden values. Verify that the client hints |
| // are overridden on both main frame and subresource requests. |
| IN_PROC_BROWSER_TEST_F(ClientHintsWebHoldbackBrowserTest, |
| EffectiveConnectionTypeChangeNotified) { |
| SetExpectedEffectiveConnectionType(web_effective_connection_type_override()); |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(true); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_url())); |
| EXPECT_EQ(0u, count_client_hints_headers_seen()); |
| EXPECT_EQ(0u, third_party_request_count_seen()); |
| EXPECT_EQ(0u, third_party_client_hints_count_seen()); |
| |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), |
| accept_ch_with_subresource_url())); |
| base::RunLoop().RunUntilIdle(); |
| content::FetchHistogramsFromChildProcesses(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| EXPECT_EQ(3u, count_user_agent_hint_headers_seen()); |
| EXPECT_EQ(3u, count_ua_mobile_client_hints_headers_seen()); |
| EXPECT_EQ(3u, count_ua_platform_client_hints_headers_seen()); |
| EXPECT_EQ(0u, count_save_data_client_hints_headers_seen()); |
| EXPECT_EQ(expected_client_hints_number * 2, |
| count_client_hints_headers_seen()); |
| EXPECT_EQ(0u, third_party_request_count_seen()); |
| EXPECT_EQ(0u, third_party_client_hints_count_seen()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, UseCounter_HttpEquiv) { |
| auto web_feature_waiter = |
| std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>( |
| chrome_test_utils::GetActiveWebContents(this)); |
| |
| web_feature_waiter->AddWebFeatureExpectation( |
| blink::mojom::WebFeature::kClientHintsUAFullVersion); |
| const GURL gurl = GetParam() ? http_equiv_accept_ch_url() : accept_ch_url(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| web_feature_waiter->Wait(); |
| } |
| IN_PROC_BROWSER_TEST_P(ClientHintsBrowserTest, UseCounter_MetaName) { |
| auto web_feature_waiter = |
| std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>( |
| chrome_test_utils::GetActiveWebContents(this)); |
| |
| web_feature_waiter->AddWebFeatureExpectation( |
| blink::mojom::WebFeature::kClientHintsUAFullVersion); |
| const GURL gurl = GetParam() ? meta_name_accept_ch_url() : accept_ch_url(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| |
| web_feature_waiter->Wait(); |
| } |
| |
| class CriticalClientHintsBrowserTest : public InProcessBrowserTest { |
| public: |
| CriticalClientHintsBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| https_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| https_server_.RegisterRequestMonitor(base::BindRepeating( |
| &CriticalClientHintsBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| https_server_.RegisterRequestHandler( |
| base::BindRepeating(&CriticalClientHintsBrowserTest::CriticalCHRedirect, |
| base::Unretained(this))); |
| |
| EXPECT_TRUE(https_server_.Start()); |
| } |
| |
| void SetUp() override { |
| std::unique_ptr<base::FeatureList> feature_list = |
| std::make_unique<base::FeatureList>(); |
| // Don't include PrefersColorSchemeClientHintHeader in the enabled |
| // features; we will verify that PrefersColorScheme is not included. |
| feature_list->InitializeFromCommandLine( |
| "UserAgentClientHint,CriticalClientHint,AcceptCHFrame", ""); |
| scoped_feature_list_.InitWithFeatureList(std::move(feature_list)); |
| |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| InProcessBrowserTest::SetUpOnMainThread(); |
| } |
| |
| GURL critical_ch_ua_full_version_url() const { |
| return https_server_.GetURL("/critical_ch_ua_full_version.html"); |
| } |
| |
| GURL critical_ch_prefers_color_scheme_url() const { |
| return https_server_.GetURL("/critical_ch_prefers-color-scheme.html"); |
| } |
| |
| GURL critical_ch_ua_full_version_list_url() const { |
| return https_server_.GetURL("/critical_ch_ua_full_version_list.html"); |
| } |
| |
| GURL critical_ch_redirect(GURL target, |
| int status = net::HTTP_TEMPORARY_REDIRECT) const { |
| return https_server_.GetURL( |
| "/redirect-criticl-ch" |
| "?url=" + |
| target.spec() + "&status=" + base::NumberToString(status)); |
| } |
| |
| GURL blank_url() { return https_server_.GetURL("/blank.html"); } |
| |
| GURL accept_ch_empty() { |
| return https_server_.GetURL("/accept_ch_empty.html"); |
| } |
| |
| const absl::optional<std::string>& observed_ch_ua_full_version() { |
| base::AutoLock lock(ch_ua_full_version_lock_); |
| return ch_ua_full_version_; |
| } |
| |
| const absl::optional<std::string>& observed_ch_ua_full_version_list() { |
| base::AutoLock lock(ch_ua_full_version_list_lock_); |
| return ch_ua_full_version_list_; |
| } |
| |
| const absl::optional<std::string>& observed_ch_prefers_color_scheme() { |
| base::AutoLock lock(ch_prefers_color_scheme_lock_); |
| return ch_prefers_color_scheme_; |
| } |
| |
| void MonitorResourceRequest(const net::test_server::HttpRequest& request) { |
| if (request.headers.find("sec-ch-ua-full-version") != |
| request.headers.end()) { |
| SetChUaFullVersion(request.headers.at("sec-ch-ua-full-version")); |
| } |
| if (request.headers.find("sec-ch-ua-full-version-list") != |
| request.headers.end()) { |
| SetChUaFullVersionList(request.headers.at("sec-ch-ua-full-version-list")); |
| } |
| if (request.headers.find("prefers-color-scheme") != request.headers.end()) { |
| SetChPrefersColorScheme(request.headers.at("prefers-color-scheme")); |
| } |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> CriticalCHRedirect( |
| const net::test_server::HttpRequest& request) { |
| auto http_response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| |
| if (!base::StartsWith(request.relative_url, "/redirect-criticl-ch")) |
| return nullptr; |
| |
| net::test_server::RequestQuery query = |
| net::test_server::ParseQuery(request.GetURL()); |
| |
| std::string location = base::UnescapeBinaryURLComponent(query["url"][0]); |
| |
| net::HttpStatusCode status_code = net::HTTP_TEMPORARY_REDIRECT; |
| auto query_code = query.find("status"); |
| int query_code_int; |
| if (query_code != query.end() && |
| base::StringToInt(query_code->second[0], &query_code_int)) |
| status_code = static_cast<net::HttpStatusCode>(query_code_int); |
| |
| http_response->set_code(status_code); |
| http_response->AddCustomHeader("Location", location); |
| http_response->AddCustomHeader("Accept-CH", "sec-ch-ua-full-version"); |
| http_response->AddCustomHeader("Critical-CH", "sec-ch-ua-full-version"); |
| |
| return http_response; |
| } |
| |
| private: |
| void SetChUaFullVersion(const std::string& ch_ua_full_version) { |
| base::AutoLock lock(ch_ua_full_version_lock_); |
| ch_ua_full_version_ = ch_ua_full_version; |
| } |
| |
| void SetChUaFullVersionList(const std::string& ch_ua_full_version_list) { |
| base::AutoLock lock(ch_ua_full_version_list_lock_); |
| ch_ua_full_version_list_ = ch_ua_full_version_list; |
| } |
| |
| void SetChPrefersColorScheme(const std::string& ch_prefers_color_scheme) { |
| base::AutoLock lock(ch_prefers_color_scheme_lock_); |
| ch_prefers_color_scheme_ = ch_prefers_color_scheme; |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| net::EmbeddedTestServer https_server_; |
| base::Lock ch_ua_full_version_lock_; |
| absl::optional<std::string> ch_ua_full_version_ |
| GUARDED_BY(ch_ua_full_version_lock_); |
| base::Lock ch_ua_full_version_list_lock_; |
| absl::optional<std::string> ch_ua_full_version_list_ |
| GUARDED_BY(ch_ua_full_version_list_lock_); |
| base::Lock ch_prefers_color_scheme_lock_; |
| absl::optional<std::string> ch_prefers_color_scheme_ |
| GUARDED_BY(ch_prefers_color_scheme_lock_); |
| }; |
| |
| // Verify that setting Critical-CH in the response header causes the request to |
| // be resent with the client hint included. |
| IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest, |
| CriticalClientHintInRequestHeader) { |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| // On the first navigation request, the client hints in the Critical-CH |
| // should be set on the request header. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), |
| critical_ch_ua_full_version_url())); |
| const std::string expected_ch_ua_full_version = "\"" + ua.full_version + "\""; |
| EXPECT_THAT(observed_ch_ua_full_version(), |
| Optional(Eq(expected_ch_ua_full_version))); |
| EXPECT_EQ(observed_ch_prefers_color_scheme(), absl::nullopt); |
| EXPECT_EQ(observed_ch_ua_full_version_list(), absl::nullopt); |
| } |
| |
| // Verify that setting Critical-CH in the response header causes the request to |
| // be resent with the client hint included. Adding a separate test case for |
| // Sec-CH-UA-Full-Version-List since Sec-CH-UA-Full-Version will be deprecated. |
| IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest, |
| CriticalClientHintFullVersionListInRequestHeader) { |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| // On the first navigation request, the client hints in the Critical-CH |
| // should be set on the request header. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), critical_ch_ua_full_version_list_url())); |
| const std::string expected_ch_ua_full_version_list = |
| ua.SerializeBrandFullVersionList(); |
| EXPECT_THAT(observed_ch_ua_full_version_list(), |
| Optional(Eq(expected_ch_ua_full_version_list))); |
| // The request should not have been resent, so ch-ua-full-version and |
| // prefers-color-schemeshould also not be present. |
| EXPECT_EQ(observed_ch_ua_full_version(), absl::nullopt); |
| EXPECT_EQ(observed_ch_prefers_color_scheme(), absl::nullopt); |
| } |
| |
| // Verify that setting Critical-CH in the response header with a client hint |
| // that is filtered out of Accept-CH causes the request to *not* be resent and |
| // the critical client hint is not included. |
| IN_PROC_BROWSER_TEST_F( |
| CriticalClientHintsBrowserTest, |
| CriticalClientHintFilteredOutOfAcceptChNotInRequestHeader) { |
| // On the first navigation request, the client hints in the Critical-CH |
| // should be set on the request header, but in this case, the |
| // kPrefersColorSchemeClientHintHeader is not enabled, so the critical client |
| // hint won't be set in the request header. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), critical_ch_prefers_color_scheme_url())); |
| EXPECT_EQ(observed_ch_prefers_color_scheme(), absl::nullopt); |
| // The request should not have been resent, so ch-ua-full-version should also |
| // not be present. |
| EXPECT_EQ(observed_ch_ua_full_version(), absl::nullopt); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest, OneRestartSingleOrigin) { |
| AlternatingCriticalCHRequestHandler handler; |
| net::test_server::EmbeddedTestServer https_server = |
| net::test_server::EmbeddedTestServer( |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS); |
| |
| https_server.RegisterRequestHandler(handler.GetRequestHandler()); |
| |
| ASSERT_TRUE(https_server.Start()); |
| |
| base::HistogramTester histogram; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| https_server.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| EXPECT_EQ(2, handler.request_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest, |
| OneRestartPerNavigation) { |
| AlternatingCriticalCHRequestHandler handler; |
| net::test_server::EmbeddedTestServer https_server = |
| net::test_server::EmbeddedTestServer( |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS); |
| |
| https_server.RegisterRequestHandler(handler.GetRequestHandler()); |
| |
| ASSERT_TRUE(https_server.Start()); |
| |
| // Two navigations, two separate restarts |
| base::HistogramTester histogram; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| https_server.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH))); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| https_server.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 2); |
| EXPECT_EQ(4, handler.request_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest, |
| NoRestartIfHintsAlreadyPresent) { |
| base::HistogramTester histogram; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), critical_ch_ua_full_version_list_url())); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| |
| // Ensure that hints are now in storage. |
| ContentSettingsForOneType client_hints_settings; |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| ASSERT_EQ(1U, client_hints_settings.size()); |
| |
| // Because hints are already in storage, there should be no restart. |
| base::HistogramTester histogram_after; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), critical_ch_ua_full_version_list_url())); |
| histogram_after.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 1 /*=kHeaderPresent*/, 1); |
| histogram_after.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest, |
| HintsPersistAfterRestart) { |
| base::HistogramTester histogram; |
| // Critical-CH on a redirect to a page with no headers. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), critical_ch_ua_full_version_list_url())); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| |
| // Ensure that hints are now in storage. |
| ContentSettingsForOneType client_hints_settings; |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| ASSERT_EQ(1U, client_hints_settings.size()); |
| } |
| |
| class CriticalClientHintsRedirectBrowserTest |
| : public CriticalClientHintsBrowserTest, |
| public testing::WithParamInterface<net::HttpStatusCode> {}; |
| |
| INSTANTIATE_TEST_CASE_P(AllRedirectCodes, |
| CriticalClientHintsRedirectBrowserTest, |
| testing::ValuesIn(kRedirectStatusCodes)); |
| |
| IN_PROC_BROWSER_TEST_P(CriticalClientHintsRedirectBrowserTest, |
| RestartDuringRedirect) { |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| base::HistogramTester histogram; |
| |
| // Critical-CH on a redirect to a page with no headers. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), critical_ch_redirect(blank_url(), GetParam()))); |
| const std::string expected_ch_ua_full_version = "\"" + ua.full_version + "\""; |
| |
| EXPECT_THAT(observed_ch_ua_full_version(), |
| Optional(Eq(expected_ch_ua_full_version))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CriticalClientHintsRedirectBrowserTest, |
| InsecureRedirectToSecureRedirect) { |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| base::HistogramTester histogram; |
| |
| net::test_server::EmbeddedTestServer http_server; |
| http_server.AddDefaultHandlers(); |
| ASSERT_TRUE(http_server.Start()); |
| |
| // http -> https + Critical-CH -> https blank |
| GURL url = |
| http_server.GetURL("/server-redirect?" + |
| critical_ch_redirect(blank_url(), GetParam()).spec()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| const std::string expected_ch_ua_full_version = "\"" + ua.full_version + "\""; |
| |
| EXPECT_THAT(observed_ch_ua_full_version(), |
| Optional(Eq(expected_ch_ua_full_version))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CriticalClientHintsRedirectBrowserTest, |
| SecureRedirectToInsecureRedirect) { |
| blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata(); |
| base::HistogramTester histogram; |
| |
| net::test_server::EmbeddedTestServer http_server; |
| http_server.AddDefaultHandlers(); |
| ASSERT_TRUE(http_server.Start()); |
| |
| // https + Critical-CH -> http -> https blank |
| GURL redirect_url = |
| http_server.GetURL("/server-redirect?" + blank_url().spec()); |
| GURL url = critical_ch_redirect(redirect_url, GetParam()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| const std::string expected_ch_ua_full_version = "\"" + ua.full_version + "\""; |
| |
| EXPECT_THAT(observed_ch_ua_full_version(), |
| Optional(Eq(expected_ch_ua_full_version))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CriticalClientHintsRedirectBrowserTest, |
| OneRestartSingleOriginRedirect) { |
| // "Permanent" redirects are cached and don't actually send a second request |
| // before redirecting |
| if (GetParam() == net::HTTP_PERMANENT_REDIRECT || |
| GetParam() == net::HTTP_MOVED_PERMANENTLY) { |
| return; |
| } |
| |
| AlternatingCriticalCHRequestHandler handler; |
| net::test_server::EmbeddedTestServer https_server = |
| net::test_server::EmbeddedTestServer( |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS); |
| |
| https_server.RegisterRequestHandler(handler.GetRequestHandler()); |
| |
| ASSERT_TRUE(https_server.Start()); |
| |
| handler.SetRedirectLocation(https_server.GetURL("/")); |
| handler.SetStatusCode(GetParam()); |
| |
| base::HistogramTester histogram; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| https_server.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 1); |
| EXPECT_EQ(2, handler.request_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CriticalClientHintsRedirectBrowserTest, |
| OneRestartMultipleOriginRedirect) { |
| // "Permanent" redirects are cached and don't actually send a second request |
| // before redirecting |
| if (GetParam() == net::HTTP_PERMANENT_REDIRECT || |
| GetParam() == net::HTTP_MOVED_PERMANENTLY) { |
| return; |
| } |
| |
| AlternatingCriticalCHRequestHandler handler_1, handler_2; |
| |
| net::test_server::EmbeddedTestServer https_server_1 = |
| net::test_server::EmbeddedTestServer( |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS); |
| net::test_server::EmbeddedTestServer https_server_2 = |
| net::test_server::EmbeddedTestServer( |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS); |
| |
| https_server_1.RegisterRequestHandler(handler_1.GetRequestHandler()); |
| https_server_2.RegisterRequestHandler(handler_2.GetRequestHandler()); |
| |
| ASSERT_TRUE(https_server_1.Start()); |
| ASSERT_TRUE(https_server_2.Start()); |
| |
| // This will send the two servers redirecting to each other in a loop until |
| // the navigation redirect break is tripped. |
| handler_1.SetRedirectLocation( |
| https_server_2.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH)); |
| handler_2.SetRedirectLocation( |
| https_server_1.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH)); |
| |
| handler_1.SetStatusCode(GetParam()); |
| handler_2.SetStatusCode(GetParam()); |
| |
| base::HistogramTester histogram; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| https_server_1.GetURL(AlternatingCriticalCHRequestHandler::kCriticalCH))); |
| histogram.ExpectBucketCount("ClientHints.CriticalCHRestart", |
| 2 /*=kNavigationRestarted*/, 2); |
| EXPECT_EQ(net::URLRequest::kMaxRedirects, |
| handler_1.request_count() + handler_2.request_count()); |
| } |
| |
| class ClientHintsBrowserTestWithEmulatedMedia |
| : public DevToolsProtocolTestBase { |
| public: |
| ClientHintsBrowserTestWithEmulatedMedia() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| scoped_feature_list_.InitFromCommandLine( |
| "UserAgentClientHint,AcceptCHFrame,PrefersColorSchemeClientHintHeader", |
| ""); |
| |
| https_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| https_server_.RegisterRequestMonitor(base::BindRepeating( |
| &ClientHintsBrowserTestWithEmulatedMedia::MonitorResourceRequest, |
| base::Unretained(this))); |
| EXPECT_TRUE(https_server_.Start()); |
| |
| test_url_ = https_server_.GetURL("/accept_ch.html"); |
| } |
| |
| ClientHintsBrowserTestWithEmulatedMedia( |
| const ClientHintsBrowserTestWithEmulatedMedia&) = delete; |
| ClientHintsBrowserTestWithEmulatedMedia& operator=( |
| const ClientHintsBrowserTestWithEmulatedMedia&) = delete; |
| |
| ~ClientHintsBrowserTestWithEmulatedMedia() override = default; |
| |
| void MonitorResourceRequest(const net::test_server::HttpRequest& request) { |
| if (request.headers.find("sec-ch-prefers-color-scheme") != |
| request.headers.end()) { |
| prefers_color_scheme_observed_ = |
| request.headers.at("sec-ch-prefers-color-scheme"); |
| } |
| } |
| |
| const GURL& test_url() const { return test_url_; } |
| |
| const std::string& prefers_color_scheme_observed() const { |
| return prefers_color_scheme_observed_; |
| } |
| |
| void EmulatePrefersColorScheme(std::string value) { |
| base::Value feature(base::Value::Type::DICTIONARY); |
| feature.SetKey("name", base::Value("prefers-color-scheme")); |
| feature.SetKey("value", base::Value(value)); |
| base::Value features(base::Value::Type::LIST); |
| features.Append(std::move(feature)); |
| base::Value params(base::Value::Type::DICTIONARY); |
| params.SetKey("features", std::move(features)); |
| SendCommandSync("Emulation.setEmulatedMedia", std::move(params)); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| net::EmbeddedTestServer https_server_; |
| GURL test_url_; |
| std::string prefers_color_scheme_observed_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTestWithEmulatedMedia, |
| PrefersColorScheme) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url())); |
| EXPECT_EQ(prefers_color_scheme_observed(), ""); |
| Attach(); |
| |
| EmulatePrefersColorScheme("light"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url())); |
| EXPECT_EQ(prefers_color_scheme_observed(), "light"); |
| |
| EmulatePrefersColorScheme("dark"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url())); |
| EXPECT_EQ(prefers_color_scheme_observed(), "dark"); |
| } |
| |
| // Base class for the User-Agent reduction or deprecation Origin Trial browser |
| // tests. Common functionality shared between the various UA browser |
| // tests should go in this class. |
| class UaOriginTrialBrowserTest : public InProcessBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // The public key for the default privatey key used by the |
| // tools/origin_trials/generate_token.py tool. |
| static constexpr char kOriginTrialTestPublicKey[] = |
| "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA="; |
| command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey, |
| kOriginTrialTestPublicKey); |
| } |
| |
| void SetUp() override { |
| std::unique_ptr<base::FeatureList> feature_list = |
| std::make_unique<base::FeatureList>(); |
| feature_list->InitializeFromCommandLine( |
| "CriticalClientHint,UACHOverrideBlank", ""); |
| scoped_feature_list_.InitWithFeatureList(std::move(feature_list)); |
| |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetUserAgentOverride(const std::string& ua_override) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| web_contents->SetUserAgentOverride( |
| blink::UserAgentOverride::UserAgentOnly(ua_override), false); |
| web_contents->GetController() |
| .GetLastCommittedEntry() |
| ->SetIsOverridingUserAgent(true); |
| } |
| |
| void CheckUaOriginTrialClientHint(const bool ch_ua_expected) { |
| const absl::optional<std::string>& ua_client_hint = |
| GetLastUaOriginTrialClientHintValue(); |
| |
| if (ch_ua_expected) { |
| EXPECT_THAT(ua_client_hint, Optional(Eq("?1"))); |
| } else { |
| EXPECT_THAT(ua_client_hint, Eq(absl::nullopt)); |
| } |
| } |
| |
| void CheckUserAgentString(const std::string& expected_ua_header_value) { |
| EXPECT_THAT(GetLastUserAgentHeaderValue(), |
| Optional(expected_ua_header_value)); |
| } |
| |
| void CheckUserAgentReduced( |
| const bool expected_user_agent_reduced, |
| const bool expected_reduced_ua_through_experiment) { |
| const absl::optional<std::string>& user_agent_header_value = |
| GetLastUserAgentHeaderValue(); |
| EXPECT_TRUE(user_agent_header_value.has_value()); |
| CheckUserAgentMinorVersion(*user_agent_header_value, |
| expected_user_agent_reduced, |
| expected_reduced_ua_through_experiment); |
| } |
| |
| // |ch_ua_reduced_expected| indicates whether expects a reduce UA string. |
| // |ch_ua_exist_expected| indicates whether the corresponding |
| // Sec-CH-UA-Reduced or Sec-CH-UA-Full exist in header. |
| void NavigateAndCheckHeaders(const GURL& url, |
| const bool ch_ua_reduced_expected, |
| const bool ch_ua_exist_expected) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| CheckUaOriginTrialClientHint(ch_ua_exist_expected); |
| |
| // If we expect reduced user agent, but there is no valid origin trial |
| // header, it means reduced UA depends on feature |
| // kReduceUserAgentMinorVersion experiment. |
| const bool expected_reduced_ua_through_experiment = |
| ch_ua_reduced_expected && !ch_ua_exist_expected; |
| CheckUserAgentReduced(ch_ua_reduced_expected, |
| expected_reduced_ua_through_experiment); |
| } |
| |
| bool UAReductionEnabled() { |
| return base::FeatureList::IsEnabled( |
| blink::features::kReduceUserAgentMinorVersion); |
| } |
| |
| protected: |
| // Returns the value of the User-Agent request header from the last sent |
| // request, or nullopt if the header could not be read. |
| virtual const absl::optional<std::string>& GetLastUserAgentHeaderValue() = 0; |
| // Returns the value of the Sec-CH-UA-Reduced or Sec-CH-UA-Full request header |
| // from the last sent request, or nullopt if the header could not be read. |
| virtual const absl::optional<std::string>& |
| GetLastUaOriginTrialClientHintValue() = 0; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Common tests that verify |
| // 1. Sec-CH-UA-Reduced client hint is sent if and only if the |
| // UserAgentReduction Origin Trial token is present and valid in the response |
| // headers. |
| // 2. Sec-CH-UA-Full client hint is sent if and only if the |
| // SendFullUserAgentAfterReduction Origin Trial token is present and valid in |
| // the response headers. |
| // |
| // The test Origin Trial token found in the test files was generated by running |
| // (in tools/origin_trials): |
| // generate_token.py https://127.0.0.1:44444 UserAgentReduction |
| // --expire-timestamp=2000000000 |
| // |
| // generate_token.py https://127.0.0.1:44444 SendFullUserAgentAfterReduction |
| // --expire-timestamp=2000000000 |
| // |
| // The Origin Trial token expires in 2033. Generate a new token by then, or |
| // find a better way to re-generate a test trial token. |
| class SameOriginUaOriginTrialBrowserTest |
| : public UaOriginTrialBrowserTest, |
| public testing::WithParamInterface<UserAgentOriginTrialTestType> { |
| public: |
| SameOriginUaOriginTrialBrowserTest() = default; |
| |
| // The URL that was used to register the Origin Trial token. |
| static constexpr const char kOriginUrl[] = "https://127.0.0.1:44444"; |
| |
| // According to the low entropy hint table: |
| // https://wicg.github.io/client-hints-infrastructure/#low-entropy-hint-table, |
| // only 3 hints are low entropy hints |
| static constexpr const int kSecChUaLowEntropyCount = 3; |
| |
| void SetUpOnMainThread() override { |
| // We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, since |
| // the origin trial token in the response is associated with a fixed |
| // origin, whereas EmbeddedTestServer serves content on a random port. |
| url_loader_interceptor_ = |
| std::make_unique<URLLoaderInterceptor>(base::BindRepeating( |
| &SameOriginUaOriginTrialBrowserTest::InterceptRequest, |
| base::Unretained(this))); |
| |
| InProcessBrowserTest::SetUpOnMainThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| url_loader_interceptor_.reset(); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void SetTestOptions(const OriginTrialTestOptions& test_setting, |
| const std::set<GURL>& expected_request_urls) { |
| test_options_ = test_setting; |
| expected_request_urls_ = expected_request_urls; |
| } |
| |
| const absl::optional<std::string>& GetLastUserAgentHeaderValue() override { |
| std::string user_agent; |
| CHECK(url_loader_interceptor_->GetLastRequestHeaders().GetHeader( |
| "user-agent", &user_agent)); |
| last_user_agent_ = user_agent; |
| return last_user_agent_; |
| } |
| |
| const absl::optional<std::string>& GetLastUaOriginTrialClientHintValue() |
| override { |
| std::string ch_ua_header_value; |
| if (url_loader_interceptor_->GetLastRequestHeaders().GetHeader( |
| base::StrCat( |
| {GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? "sec-ch-ua-reduced" |
| : "sec-ch-ua-full"}), |
| &ch_ua_header_value)) { |
| last_ua_ch_val_ = ch_ua_header_value; |
| } else { |
| last_ua_ch_val_ = absl::nullopt; |
| } |
| return last_ua_ch_val_; |
| } |
| |
| void CheckSecClientHintUaCount() { |
| net::HttpRequestHeaders::Iterator header_iterator( |
| url_loader_interceptor_->GetLastRequestHeaders()); |
| int sec_ch_ua_count = 0; |
| while (header_iterator.GetNext()) { |
| if (base::StartsWith(header_iterator.name(), "sec-ch-ua", |
| base::CompareCase::SENSITIVE)) { |
| ++sec_ch_ua_count; |
| } |
| } |
| |
| if (GetParam() == UserAgentOriginTrialTestType::UAReductionAndDeprecation) { |
| // Two Accept-CH client hints in header: sec-ch-ua-reduced and |
| // sec-ch-ua-full. |
| EXPECT_EQ(sec_ch_ua_count, kSecChUaLowEntropyCount + 2); |
| } else { |
| // One Accept-CH client hint in header: sec-ch-ua-reduced or |
| // sec-ch-ua-full. |
| EXPECT_EQ(sec_ch_ua_count, kSecChUaLowEntropyCount + 1); |
| } |
| } |
| |
| void VerifyNonAcceptCHNotAddedToHeader(const std::string& client_hint) { |
| ASSERT_FALSE(url_loader_interceptor_->GetLastRequestHeaders().HasHeader( |
| client_hint)); |
| } |
| |
| GURL ua_with_valid_origin_trial_token_url() const { |
| return GURL(base::StrCat( |
| {kOriginUrl, "/accept_ch_ua_with_valid_origin_trial.html"})); |
| } |
| |
| GURL ua_with_invalid_origin_trial_token_url() const { |
| return GURL(base::StrCat( |
| {kOriginUrl, "/accept_ch_ua_with_invalid_origin_trial.html"})); |
| } |
| |
| GURL ua_with_no_origin_trial_token_url() const { |
| return GURL( |
| base::StrCat({kOriginUrl, "/accept_ch_ua_with_no_origin_trial.html"})); |
| } |
| |
| GURL ua_missing_with_valid_origin_trial_token_url() const { |
| return GURL(base::StrCat( |
| {kOriginUrl, "/accept_ch_ua_missing_valid_origin_trial.html"})); |
| } |
| |
| GURL critical_ch_ua_with_valid_origin_trial_token_url() const { |
| return GURL(base::StrCat( |
| {kOriginUrl, "/critical_ch_ua_with_valid_origin_trial.html"})); |
| } |
| |
| GURL critical_ch_ua_with_invalid_origin_trial_token_url() const { |
| return GURL(base::StrCat( |
| {kOriginUrl, "/critical_ch_ua_with_invalid_origin_trial.html"})); |
| } |
| |
| GURL accept_ch_ua_subresource_request_url() const { |
| return GURL( |
| base::StrCat({kOriginUrl, "/accept_ch_ua_subresource_request.html"})); |
| } |
| |
| GURL accept_ch_ua_iframe_request_url() const { |
| return GURL( |
| base::StrCat({kOriginUrl, "/accept_ch_ua_iframe_request.html"})); |
| } |
| |
| GURL accept_ch_ua_iframe_sandbox_request_url() const { |
| return GURL(base::StrCat( |
| {kOriginUrl, "/accept_ch_ua_iframe_sandbox_request.html"})); |
| } |
| |
| GURL critical_ch_ua_subresource_request_url() const { |
| return GURL( |
| base::StrCat({kOriginUrl, "/critical_ch_ua_subresource_request.html"})); |
| } |
| |
| GURL critical_ch_ua_iframe_request_url() const { |
| return GURL( |
| base::StrCat({kOriginUrl, "/critical_ch_ua_iframe_request.html"})); |
| } |
| |
| GURL simple_request_url() const { |
| return GURL(base::StrCat({kOriginUrl, "/simple.html"})); |
| } |
| |
| GURL style_css_request_url() const { |
| return GURL(base::StrCat({kOriginUrl, "/style.css"})); |
| } |
| |
| GURL last_request_url() const { |
| return url_loader_interceptor_->GetLastRequestURL(); |
| } |
| |
| void NavigateTwiceAndCheckHeaderReduced( |
| const GURL& url, |
| const bool ch_ua_reduced_expected, |
| const bool critical_ch_ua_reduced_expected) { |
| base::HistogramTester histograms; |
| int reduced_count = 0; |
| int full_count = 0; |
| |
| // If Critical-CH is set, we expect Sec-CH-UA-Reduced in the first |
| // navigation request header. If Critical-CH is not set, we don't expect |
| // Sec-CH-UA-Reduced in the first navigation request. |
| // If Sec-CH-UA-Reduced in the first request, UA string should reduced, |
| // otherwise UA string depends on whether kReduceUserAgentMinorVersion has |
| // turns up. |
| const bool first_navigation_has_sec_reduced_ua = |
| critical_ch_ua_reduced_expected && ch_ua_reduced_expected; |
| bool first_navigation_expected_reduced_ua = true; |
| if (first_navigation_has_sec_reduced_ua) { |
| first_navigation_expected_reduced_ua = true; |
| } else { |
| first_navigation_expected_reduced_ua = UAReductionEnabled(); |
| } |
| NavigateAndCheckHeaders(url, first_navigation_expected_reduced_ua, |
| first_navigation_has_sec_reduced_ua); |
| if (first_navigation_has_sec_reduced_ua) { |
| ++reduced_count; |
| if (critical_ch_ua_reduced_expected) { |
| // If Critical-CH was set, there will also be the initial navigation |
| // that does not send the reduced UA string. |
| ++full_count; |
| } |
| } else { |
| ++full_count; |
| } |
| // The UserAgentStringType enum is not accessible in //chrome/browser, so |
| // we just use the enum's integer value. |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kFullVersion*/ 0, |
| full_count); |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kReducedVersion*/ 1, |
| reduced_count); |
| |
| // Regardless of the Critical-CH setting, we expect the Sec-CH-UA-Reduced |
| // client hint sent on the second request, if Sec-CH-UA-Reduced is set and |
| // the Origin Trial token is valid. |
| // If Sec-CH-UA-Reduced in the second request, UA string should reduced, |
| // otherwise UA string depends on whether kReduceUserAgentMinorVersion has |
| // turns up. |
| bool second_navigation_has_sec_reduced_ua = ch_ua_reduced_expected; |
| bool second_navigation_expected_reduced_ua = true; |
| if (second_navigation_has_sec_reduced_ua) { |
| second_navigation_expected_reduced_ua = true; |
| } else { |
| second_navigation_expected_reduced_ua = UAReductionEnabled(); |
| } |
| NavigateAndCheckHeaders(url, second_navigation_expected_reduced_ua, |
| second_navigation_has_sec_reduced_ua); |
| // Make sure non-default client hints are not added to the request headers |
| // of subresource requests. Here, we just use Sec-CH-UA-Bitness as a high |
| // entropy hint to check against. |
| VerifyNonAcceptCHNotAddedToHeader("sec-ch-ua-bitness"); |
| if (ch_ua_reduced_expected) { |
| ++reduced_count; |
| } else { |
| ++full_count; |
| } |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kFullVersion*/ 0, |
| full_count); |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kReducedVersion*/ 1, |
| reduced_count); |
| } |
| |
| void NavigateTwiceAndCheckHeaderFull( |
| const GURL& url, |
| const bool ch_ua_full_expected, |
| const bool critical_ch_ua_full_expected) { |
| base::HistogramTester histograms; |
| int full_count = 0; |
| |
| // If Critical-CH is set, we expect Sec-CH-UA-Full in the first |
| // navigation request header. If Critical-CH is not set, we don't expect |
| // Sec-CH-UA-Full in the first navigation request. |
| const bool first_navigation_has_sec_full_ua = |
| critical_ch_ua_full_expected && ch_ua_full_expected; |
| // If Sec-CH-UA-Full in the first request, UA string should not reduced, |
| // otherwise UA string depends on whether kReduceUserAgentMinorVersion has |
| // turns up. |
| bool first_navigation_expected_reduced_ua = false; |
| if (first_navigation_has_sec_full_ua) { |
| first_navigation_expected_reduced_ua = false; |
| } else { |
| first_navigation_expected_reduced_ua = UAReductionEnabled(); |
| } |
| NavigateAndCheckHeaders(url, first_navigation_expected_reduced_ua, |
| first_navigation_has_sec_full_ua); |
| |
| // TODO: Currently no matter whether it's a first navigation request or not, |
| // we always sent the full user agent string. We need to update the count |
| // logic once we fully migrate to the reduced user agent string. |
| |
| // If Critical-CH was set, there will also be the initial navigation |
| // that send full UA string. |
| if (critical_ch_ua_full_expected) { |
| ++full_count; |
| } |
| ++full_count; |
| |
| // The UserAgentStringType enum is not accessible in //chrome/browser, so |
| // we just use the enum's integer value. |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kFullVersion*/ 0, |
| full_count); |
| |
| // Regardless of the Critical-CH setting, we expect the Sec-CH-UA-Full |
| // client hint sent on the second request, if Sec-CH-UA-Full is set and |
| // the Origin Trial token is valid. |
| // If Sec-CH-UA-Full in the second request, UA string should not reduced, |
| // otherwise UA string depends on whether kReduceUserAgentMinorVersion has |
| // turns up. |
| bool second_navigation_has_sec_full_ua = ch_ua_full_expected; |
| bool second_navigation_expected_reduced_ua = false; |
| if (second_navigation_has_sec_full_ua) { |
| second_navigation_expected_reduced_ua = false; |
| } else { |
| second_navigation_expected_reduced_ua = UAReductionEnabled(); |
| } |
| NavigateAndCheckHeaders(url, second_navigation_expected_reduced_ua, |
| ch_ua_full_expected); |
| // Make sure non-default client hints are not added to the request headers |
| // of subresource requests. Here, we just use Sec-CH-UA-Bitness as a high |
| // entropy hint to check against. |
| VerifyNonAcceptCHNotAddedToHeader("sec-ch-ua-bitness"); |
| |
| ++full_count; |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kFullVersion*/ 0, |
| full_count); |
| } |
| |
| void NavigateTwiceAndCheckHeader(const GURL& url, |
| const bool ch_ua_exist_expected, |
| const bool critical_ch_ua_exist_expected) { |
| if (GetParam() == UserAgentOriginTrialTestType::UAReduction) { |
| NavigateTwiceAndCheckHeaderReduced(url, ch_ua_exist_expected, |
| critical_ch_ua_exist_expected); |
| } else { |
| NavigateTwiceAndCheckHeaderFull(url, ch_ua_exist_expected, |
| critical_ch_ua_exist_expected); |
| } |
| } |
| |
| private: |
| // URLLoaderInterceptor callback |
| bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) { |
| if (expected_request_urls_.find(params->url_request.url) == |
| expected_request_urls_.end()) |
| return false; |
| |
| std::string path = "chrome/test/data/client_hints"; |
| path.append(static_cast<std::string>(params->url_request.url.path_piece())); |
| |
| if (params->url_request.url.path() == "/style.css" || |
| params->url_request.url.path() == "/simple.html") { |
| URLLoaderInterceptor::WriteResponse(path, params->client.get()); |
| return true; |
| } |
| std::string headers = "HTTP/1.1 200 OK\nContent-Type: text/html\n"; |
| base::StrAppend(&headers, {BuildOriginTrialHeader()}); |
| URLLoaderInterceptor::WriteResponse(path, params->client.get(), &headers, |
| absl::nullopt, |
| /*url=*/params->url_request.url); |
| return true; |
| } |
| |
| std::string BuildOriginTrialHeader() { |
| std::string headers; |
| |
| // Generated by running (in tools/origin_trials): |
| // generate_token.py https://127.0.0.1:44444 UserAgentReduction |
| // --expire-timestamp=2000000000 |
| static constexpr char kUAReducedOriginTrialToken[] = |
| "A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+" |
| "YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcm" |
| "lnaW4iOiAiaH" |
| "R0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbn" |
| "RSZWR1Y3Rpb2" |
| "4iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="; |
| |
| // Generated by running (in tools/origin_trials): |
| // generate_token.py https://127.0.0.1:44444 |
| // SendFullUserAgentAfterReduction |
| // --expire-timestamp=2000000000 |
| static constexpr char kUAFullOriginTrialToken[] = |
| "A6+Ti/9KuXTgmFzOQwkTuO8k0QFH8vUaxmv0CllAET1/" |
| "307KShF6fhskMuBqFUvqO7ViAkZ+" |
| "NSeJhQI0n5aLggsAAABpeyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6" |
| "NDQ0NDQiLCAiZmVhdHVyZSI6ICJTZW5kRnVsbFVzZXJBZ2VudEFmdGVyUmVk" |
| "dWN0aW9uIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9"; |
| |
| switch (GetParam()) { |
| case UserAgentOriginTrialTestType::UAReduction: |
| if (test_options_.has_accept_ch_header) { |
| base::StrAppend(&headers, {"Accept-CH: ", "sec-ch-ua-reduced", "\n"}); |
| } |
| if (test_options_.has_critical_ch_header) { |
| base::StrAppend(&headers, |
| {"Critical-CH: ", "sec-ch-ua-reduced", "\n"}); |
| } |
| if (test_options_.has_ot_token) { |
| base::StrAppend(&headers, {"Origin-Trial: ", |
| test_options_.valid_ot_token |
| ? kUAReducedOriginTrialToken |
| : "invalid", |
| "\n"}); |
| } |
| break; |
| case UserAgentOriginTrialTestType::UADeprecation: |
| if (test_options_.has_accept_ch_header) { |
| base::StrAppend(&headers, {"Accept-CH: ", "sec-ch-ua-full", "\n"}); |
| } |
| if (test_options_.has_critical_ch_header) { |
| base::StrAppend(&headers, {"Critical-CH: ", "sec-ch-ua-full", "\n"}); |
| } |
| if (test_options_.has_ot_token) { |
| base::StrAppend( |
| &headers, {"Origin-Trial: ", |
| test_options_.valid_ot_token ? kUAFullOriginTrialToken |
| : "invalid", |
| "\n"}); |
| } |
| break; |
| case UserAgentOriginTrialTestType::UAReductionAndDeprecation: |
| if (test_options_.has_accept_ch_header) { |
| base::StrAppend( |
| &headers, |
| {"Accept-CH: ", "sec-ch-ua-reduced, sec-ch-ua-full", "\n"}); |
| } |
| if (test_options_.has_critical_ch_header) { |
| base::StrAppend( |
| &headers, |
| {"Critical-CH: ", "sec-ch-ua-reduced, sec-ch-ua-full", "\n"}); |
| } |
| |
| if (test_options_.has_ot_token) { |
| base::StrAppend( |
| &headers, |
| {"Origin-Trial: ", |
| (test_options_.valid_ot_token ? kUAReducedOriginTrialToken |
| : "invalid"), |
| ",", |
| (test_options_.valid_ot_token ? kUAFullOriginTrialToken |
| : "invalid"), |
| "\n"}); |
| } |
| break; |
| default: |
| break; |
| } |
| return headers; |
| } |
| |
| std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_; |
| absl::optional<std::string> last_user_agent_; |
| absl::optional<std::string> last_ua_ch_val_; |
| std::set<GURL> expected_request_urls_; |
| OriginTrialTestOptions test_options_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SameOriginUaOriginTrialBrowserTest, |
| testing::Values(UserAgentOriginTrialTestType::UAReduction, |
| UserAgentOriginTrialTestType::UADeprecation, |
| UserAgentOriginTrialTestType::UAReductionAndDeprecation)); |
| |
| constexpr const char SameOriginUaOriginTrialBrowserTest::kOriginUrl[]; |
| constexpr const int SameOriginUaOriginTrialBrowserTest::kSecChUaLowEntropyCount; |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| AcceptChUaWithValidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {ua_with_valid_origin_trial_token_url()}); |
| |
| NavigateTwiceAndCheckHeader(ua_with_valid_origin_trial_token_url(), |
| /*ch_ua_exist_expected=*/true, |
| /*critical_ch_ua_exist_expected=*/false); |
| |
| // The Origin Trial token is valid, so we expect the reduced/full UA values in |
| // the Javascript getters as well. |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| CheckUserAgentMinorVersion( |
| content::EvalJs(web_contents, "navigator.userAgent").ExtractString(), |
| /*expected_user_agent_reduced=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| false); |
| CheckUserAgentMinorVersion( |
| content::EvalJs(web_contents, "navigator.appVersion").ExtractString(), |
| /*expected_user_agent_reduced=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| false); |
| // Instead of checking all platform types, just check one that has a |
| // difference between the full and reduced versions. |
| #if BUILDFLAG(IS_ANDROID) |
| EXPECT_EQ("Linux x86_64", |
| content::EvalJs(web_contents, "navigator.platform")); |
| #endif |
| |
| CheckSecClientHintUaCount(); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| AcceptChUaWithInvalidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/false, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {ua_with_invalid_origin_trial_token_url()}); |
| |
| // The response contained Sec-CH-UA-Reduced or Sec-CH-UA-Full in the |
| // Accept-CH header, but the origin trial token is invalid. |
| NavigateTwiceAndCheckHeader(ua_with_invalid_origin_trial_token_url(), |
| /*ch_ua_exist_expected=*/false, |
| /*critical_ch_ua_exist_expected=*/false); |
| |
| // The Origin Trial token is invalid, so we expect the UA values depends on |
| // the feature kReduceUserAgentMinorVersion in the Javascript getters. |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| CheckUserAgentMinorVersion( |
| content::EvalJs(web_contents, "navigator.userAgent").ExtractString(), |
| /*expected_user_agent_reduced=*/UAReductionEnabled(), true); |
| CheckUserAgentMinorVersion( |
| content::EvalJs(web_contents, "navigator.appVersion").ExtractString(), |
| /*expected_user_agent_reduced=*/UAReductionEnabled(), true); |
| // Instead of checking all platform types, just check one that has a |
| // difference between the full and reduced versions. |
| #if BUILDFLAG(IS_ANDROID) |
| EXPECT_NE("Linux x86_64", |
| content::EvalJs(web_contents, "navigator.platform")); |
| #endif |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| AcceptChUaWithNoOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/false, /*valid_ot_token=*/false, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {ua_with_no_origin_trial_token_url()}); |
| |
| // The response contained Sec-CH-UA-Reduced or Sec-CH-UA-Full in the |
| // Accept-CH header, but the origin trial token is not present. |
| NavigateTwiceAndCheckHeader(ua_with_no_origin_trial_token_url(), |
| /*ch_ua_exist_expected=*/false, |
| /*critical_ch_ua_exist_expected=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| NoAcceptChUaWithValidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/false, /*has_critical_ch_header=*/false}, |
| {ua_missing_with_valid_origin_trial_token_url()}); |
| |
| // The response contained a valid Origin Trial token, but no corresponding |
| // Sec-CH-UA-Reduced or Sec-CH-UA-Full in the Accept-CH header. |
| NavigateTwiceAndCheckHeader(ua_missing_with_valid_origin_trial_token_url(), |
| /*ch_ua_exist_expected=*/false, |
| /*critical_ch_ua_exist_expected=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| CriticalChUaWithValidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/true}, |
| {critical_ch_ua_with_valid_origin_trial_token_url()}); |
| |
| // The initial navigation also contains the Critical-CH header, so the |
| // corresponding Sec-CH-UA-Reduced or Sec-CH-UA-Full header should be set |
| // after the first navigation. |
| NavigateTwiceAndCheckHeader( |
| critical_ch_ua_with_valid_origin_trial_token_url(), |
| /*ch_ua_exist_expected=*/true, |
| /*critical_ch_ua_exist_expected=*/true); |
| |
| CheckSecClientHintUaCount(); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| CriticalChUaWithInvalidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/false, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/true}, |
| {critical_ch_ua_with_invalid_origin_trial_token_url()}); |
| |
| // The Origin Trial token is invalid, so the Critical-CH should not have |
| // resulted in the corresponding Sec-CH-UA-Reduced or Sec-CH-UA-Full header |
| // being sent. |
| NavigateTwiceAndCheckHeader( |
| critical_ch_ua_with_invalid_origin_trial_token_url(), |
| /*ch_ua_exist_expected=*/false, |
| /*critical_ch_ua_exist_expected=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| IframeRequestUaWithValidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {accept_ch_ua_iframe_request_url(), simple_request_url()}); |
| |
| // The last resource request processed for this navigation will be an embedded |
| // iframe request. Since Accept-CH has either Sec-CH-UA-Reduced or |
| // Sec-CH-UA-Full set on the top-level level frame's response header, along |
| // with a valid origin trial token, the iframe request should send the reduced |
| // UA string if Sec-CH-UA-Reduced set, or the full UA string if Sec-CH-UA-Full |
| // set in the request header. |
| NavigateAndCheckHeaders(accept_ch_ua_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| CheckSecClientHintUaCount(); |
| |
| // Make sure the last intercepted URL was the request for the embedded iframe. |
| EXPECT_EQ(last_request_url().path(), "/simple.html"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| IframeRequestUaWithValidOriginTrialTokenIgnoreSandbox) { |
| SetTestOptions( |
| { |
| /*has_ot_token=*/true, |
| /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, |
| /*has_critical_ch_header=*/false, |
| }, |
| {accept_ch_ua_iframe_sandbox_request_url(), simple_request_url()}); |
| |
| // Ensure that frames with sandbox flags don't interfere with the origin trial |
| NavigateAndCheckHeaders(accept_ch_ua_iframe_sandbox_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| CheckSecClientHintUaCount(); |
| |
| // Make sure the last intercepted URL was the request for the embedded iframe. |
| EXPECT_EQ(last_request_url().path(), "/simple.html"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| IframeRequestUaWithValidOriginTrialTokenAndCriticalCH) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/true}, |
| {critical_ch_ua_iframe_request_url(), simple_request_url()}); |
| |
| // The last resource request processed for this navigation will be an embedded |
| // iframe request. Since Accept-CH has either Sec-CH-UA-Reduced or |
| // Sec-CH-UA-Full set on the top-level level frame's response header, |
| // along with a valid origin trial token, the iframe request should send the |
| // reduced UA string if Sec-CH-UA-Reduced set, or the full UA string if |
| // Sec-CH-UA-Full set in the request header. |
| NavigateAndCheckHeaders(critical_ch_ua_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| CheckSecClientHintUaCount(); |
| |
| // Make sure the last intercepted URL was the request for the embedded iframe. |
| EXPECT_EQ(last_request_url().path(), "/simple.html"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| SubresourceRequestUaWithValidOriginTrialToken) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {accept_ch_ua_subresource_request_url(), style_css_request_url()}); |
| |
| // The last resource request processed for this navigation will be a |
| // subresource request for the stylesheet. Since Accept-CH has |
| // either Sec-CH-UA-Reduced or Sec-CH-UA-Full set on the top-level |
| // level frame's response header, along with a valid origin trial token, the |
| // subresource request should send the reduced UA string if Sec-CH-UA-Reduced |
| // set, or the full UA string if Sec-CH-UA-Full set in the request header. |
| NavigateAndCheckHeaders(accept_ch_ua_subresource_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| // Make sure the last intercepted URL was the subresource request for the |
| // embedded stylesheet. |
| EXPECT_EQ(last_request_url().path(), "/style.css"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SameOriginUaOriginTrialBrowserTest, |
| SubresourceRequestUaWithValidOriginTrialTokenAndCriticalCH) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/true}, |
| {critical_ch_ua_subresource_request_url(), style_css_request_url()}); |
| |
| // The last resource request processed for this navigation will be a |
| // subresource request for the stylesheet. Since Accept-CH has |
| // either Sec-CH-UA-Reduced or Sec-CH-UA-Full set on the top-level level |
| // frame's response header, along with a valid origin trial token, the |
| // subresource request should send the reduced UA string Sec-CH-UA-Reduced |
| // set, or the full UA string if Sec-CH-UA-Full set in the request header. |
| NavigateAndCheckHeaders(critical_ch_ua_subresource_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| // Make sure the last intercepted URL was the subresource request for the |
| // embedded stylesheet. |
| EXPECT_EQ(last_request_url().path(), "/style.css"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| UserAgentOverrideAcceptChUa) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {ua_with_valid_origin_trial_token_url()}); |
| |
| base::HistogramTester histograms; |
| const std::string user_agent_override = "foo"; |
| SetUserAgentOverride(user_agent_override); |
| |
| const GURL url = ua_with_valid_origin_trial_token_url(); |
| // First navigation to set the client hints in the response. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| // Second navigation has either Sec-CH-UA-Reduced or Sec-CH-UA-Full client |
| // hint stored from the first navigation's response. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Since the UA override was set, the UA client hints are *not* added to the |
| // request. |
| CheckUaOriginTrialClientHint(/*ch_ua_expected=*/true); |
| // Make sure the overridden UA string is the one sent. |
| CheckUserAgentString(user_agent_override); |
| |
| // The UserAgentStringType enum is not accessible in //chrome/browser, so |
| // we just use the enum's integer value. |
| histograms.ExpectBucketCount("Navigation.UserAgentStringType", |
| /*NavigationRequest::kOverridden*/ 2, 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| UserAgentOverrideSubresourceRequest) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {accept_ch_ua_subresource_request_url()}); |
| |
| const std::string user_agent_override = "foo"; |
| SetUserAgentOverride(user_agent_override); |
| |
| const GURL url = accept_ch_ua_subresource_request_url(); |
| // First navigation to set the client hints in the response. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| // Second navigation has either Sec-CH-UA-Reduced or Sec-CH-UA-Full client |
| // hint stored from the first navigation's response. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Since the UA override was set, the UA client hints are *not* added to the |
| // request. |
| CheckUaOriginTrialClientHint(/*ch_ua_expected=*/true); |
| // Make sure the overridden UA string is the one sent. |
| CheckUserAgentString(user_agent_override); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| UserAgentOverrideIframeRequest) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {accept_ch_ua_iframe_request_url()}); |
| |
| const std::string user_agent_override = "foo"; |
| SetUserAgentOverride(user_agent_override); |
| |
| const GURL url = accept_ch_ua_iframe_request_url(); |
| // First navigation to set the client hints in the response. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| // Second navigation has either Sec-CH-UA-Reduced or Sec-CH-UA-Full client |
| // hint stored from the first navigation's response. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Since the UA override was set, the UA client hints are *not* added to the |
| // request. |
| CheckUaOriginTrialClientHint(/*ch_ua_expected=*/true); |
| // Make sure the overridden UA string is the one sent. |
| CheckUserAgentString(user_agent_override); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameOriginUaOriginTrialBrowserTest, |
| NoAcceptCHRemovesSecChUaFromStorage) { |
| SetTestOptions( |
| {/*has_ot_token=*/true, /*valid_ot_token=*/true, |
| /*has_accept_ch_header=*/true, /*has_critical_ch_header=*/false}, |
| {ua_with_valid_origin_trial_token_url(), simple_request_url()}); |
| |
| // The first navigation sets Sec-CH-UA-Reduced/Sec-CH-UA-Full in the client |
| // hints storage for the origin. |
| NavigateAndCheckHeaders(ua_with_valid_origin_trial_token_url(), |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| // The second navigation doesn't contain an Accept-CH header in the |
| // response, so Sec-CH-UA-Reduced/Sec-CH-UA-Full is removed from the storage. |
| NavigateAndCheckHeaders(simple_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| // The third navigation doesn't contain a Sec-CH-UA-Reduced/Sec-CH-UA-Full |
| // in the request header because the second navigation caused it to get |
| // removed. |
| NavigateAndCheckHeaders(ua_with_valid_origin_trial_token_url(), |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| } |
| |
| // Tests that the Sec-CH-UA-Reduced or Sec-CH-UA-Full client hint and the |
| // reduced User-Agent string are sent on request headers for third-party |
| // embedded resources if the Origin Trial token from the top-level frame is |
| // valid and the permissions policy allows it. |
| class ThirdPartyUaOriginTrialBrowserTest |
| : public UaOriginTrialBrowserTest, |
| public testing::WithParamInterface<UserAgentOriginTrialTestType> { |
| public: |
| ThirdPartyUaOriginTrialBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| https_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| https_server_.RegisterRequestMonitor(base::BindRepeating( |
| &ThirdPartyUaOriginTrialBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| EXPECT_TRUE(https_server_.Start()); |
| } |
| |
| // The URL that was used to register the Origin Trial token. |
| static constexpr char kFirstPartyOriginUrl[] = "https://my-site.com:44444"; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| // We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, |
| // since the origin trial token in the response is associated with a fixed |
| // origin, whereas EmbeddedTestServer serves content on a random port. |
| url_loader_interceptor_ = |
| std::make_unique<URLLoaderInterceptor>(base::BindRepeating( |
| &ThirdPartyUaOriginTrialBrowserTest::InterceptRequest, |
| base::Unretained(this))); |
| } |
| |
| void TearDownOnMainThread() override { |
| url_loader_interceptor_.reset(); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| GURL accept_ch_ua_cross_origin_iframe_request_url() const { |
| return GURL( |
| base::StrCat({kFirstPartyOriginUrl, |
| "/accept_ch_ua_cross_origin_iframe_request.html"})); |
| } |
| |
| GURL accept_ch_ua_cross_origin_subresource_request_url() const { |
| return GURL( |
| base::StrCat({kFirstPartyOriginUrl, |
| "/accept_ch_ua_cross_origin_subresource_request.html"})); |
| } |
| |
| protected: |
| const absl::optional<std::string>& GetLastUserAgentHeaderValue() override { |
| base::AutoLock lock(last_request_lock_); |
| return last_user_agent_; |
| } |
| |
| const absl::optional<std::string>& GetLastUaOriginTrialClientHintValue() |
| override { |
| base::AutoLock lock(last_request_lock_); |
| return last_sec_ch_ua_value_; |
| } |
| |
| const absl::optional<GURL>& GetLastRequestedURL() { |
| base::AutoLock lock(last_request_lock_); |
| return last_requested_url_; |
| } |
| |
| void SetUaPermissionsPolicy(const std::string& value) { |
| ua_permissions_policy_header_value_ = value; |
| } |
| |
| void SetValidOTToken(const bool valid_ot_token) { |
| valid_ot_token_ = valid_ot_token; |
| } |
| |
| GURL GetServerOrigin() const { return https_server_.GetOrigin().GetURL(); } |
| |
| private: |
| // URLLoaderInterceptor callback |
| bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) { |
| if (params->url_request.url.DeprecatedGetOriginAsURL() != |
| GURL(kFirstPartyOriginUrl)) { |
| return false; |
| } |
| if (params->url_request.url.path() != |
| base::StrCat({"/accept_ch_ua_cross_origin_iframe_request.html"}) && |
| params->url_request.url.path() != |
| base::StrCat( |
| {"/accept_ch_ua_cross_origin_subresource_request.html"})) { |
| return false; |
| } |
| |
| // Generated by running (in tools/origin_trials): |
| // generate_token.py https://my-site.com:44444 UserAgentReduction |
| // --expire-timestamp=2000000000 |
| // |
| // The Origin Trial token expires in 2033. Generate a new token by then, or |
| // find a better way to re-generate a test trial token. |
| static constexpr const char kOriginTrialTokenReduced[] = |
| "AziP2Iyo74PHkJAVVXJ1NBAyZd+" |
| "GZFmTqpFtug4Wazsj5rQPFeCFjjZpiEYb086vZzi48lF1ydynMj/" |
| "oLqqLXgEAAABeeyJvcmlnaW4iOiAiaHR0cHM6Ly9teS1zaXRlLmNvbTo0NDQ0NCIsICJmZ" |
| "WF0dXJlIjogIlVzZXJBZ2VudFJlZHVjdGlvbiIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ=" |
| "="; |
| |
| // Generated by running (in tools/origin_trials): |
| // generate_token.py https://my-site.com:44444 |
| // SendFullUserAgentAfterReduction --expire-timestamp=2000000000 |
| // |
| // The Origin Trial token expires in 2033. Generate a new token by then, |
| // or find a better way to re-generate a test trial token. |
| static constexpr const char kOriginTrialTokenFull[] = |
| "A/qRZSBJ/" |
| "wuh1vOPO1X3x79VvjXlKiWldDIX0ra1iQf2FBB7yHPCQ3rEEHOc8S0cnWUG8as1k98sUyV" |
| "xGawmmggAAABreyJvcmlnaW4iOiAiaHR0cHM6Ly9teS1zaXRlLmNvbTo0NDQ0NCIsICJmZ" |
| "WF0dXJlIjogIlNlbmRGdWxsVXNlckFnZW50QWZ0ZXJSZWR1Y3Rpb24iLCAiZXhwaXJ5Ijo" |
| "gMjAwMDAwMDAwMH0="; |
| |
| // Construct and send the response. |
| std::string headers = |
| "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n"; |
| base::StrAppend(&headers, |
| {"Accept-CH: ", |
| GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? "Sec-CH-UA-Reduced" |
| : "Sec-CH-UA-Full", |
| "\n"}); |
| base::StrAppend(&headers, |
| {"Permissions-Policy: ", |
| GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? "ch-ua-reduced=" |
| : "ch-ua-full=", |
| ua_permissions_policy_header_value_, "\n"}); |
| |
| base::StrAppend( |
| &headers, |
| {"Origin-Trial: ", |
| valid_ot_token_ |
| ? (GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? kOriginTrialTokenReduced |
| : kOriginTrialTokenFull) |
| : "invalid-origin-trial-token", |
| "\n\n"}); |
| std::string body = "<html><head>"; |
| if (params->url_request.url.path() == |
| base::StrCat({"/accept_ch_ua_cross_origin_subresource_request.html"})) { |
| base::StrAppend(&body, {BuildSubresourceHTML()}); |
| } |
| base::StrAppend(&body, {"</head><body>"}); |
| if (params->url_request.url.path() == |
| base::StrCat({"/accept_ch_ua_cross_origin_iframe_request.html"})) { |
| base::StrAppend(&body, {BuildIframeHTML()}); |
| } |
| base::StrAppend(&body, {"</body></html>"}); |
| URLLoaderInterceptor::WriteResponse(headers, body, params->client.get()); |
| return true; |
| } |
| |
| void SetLastUserAgent(const std::string* value) { |
| base::AutoLock lock(last_request_lock_); |
| if (value != nullptr) { |
| last_user_agent_ = *value; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void SetLastSecChUaValue(const std::string* value) { |
| base::AutoLock lock(last_request_lock_); |
| if (value != nullptr) { |
| last_sec_ch_ua_value_ = *value; |
| } else { |
| last_sec_ch_ua_value_ = absl::nullopt; |
| } |
| } |
| |
| void SetLastRequestedURL(const GURL& url) { |
| base::AutoLock lock(last_request_lock_); |
| last_requested_url_ = url; |
| } |
| |
| // Called by `https_server_`. |
| void MonitorResourceRequest(const net::test_server::HttpRequest& request) { |
| SetLastRequestedURL(request.GetURL()); |
| auto it = request.headers.find("user-agent"); |
| SetLastUserAgent(it != request.headers.end() ? &it->second : nullptr); |
| it = request.headers.find( |
| base::StrCat({GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? "sec-ch-ua-reduced" |
| : "sec-ch-ua-full"})); |
| SetLastSecChUaValue(it != request.headers.end() ? &it->second : nullptr); |
| } |
| |
| std::string BuildIframeHTML() { |
| std::string html = "<iframe src=\""; |
| base::StrAppend( |
| &html, {https_server_.GetURL("/simple.html").spec(), "\"></iframe>"}); |
| return html; |
| } |
| |
| std::string BuildSubresourceHTML() { |
| std::string html = "<link rel=\"stylesheet\" href=\""; |
| base::StrAppend(&html, |
| {https_server_.GetURL("/style.css").spec(), "\"></link>"}); |
| return html; |
| } |
| |
| std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_; |
| net::EmbeddedTestServer https_server_; |
| std::string ua_permissions_policy_header_value_; |
| bool valid_ot_token_ = true; |
| base::Lock last_request_lock_; |
| absl::optional<std::string> last_user_agent_ GUARDED_BY(last_request_lock_); |
| absl::optional<std::string> last_sec_ch_ua_value_ |
| GUARDED_BY(last_request_lock_); |
| absl::optional<GURL> last_requested_url_ GUARDED_BY(last_request_lock_); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ThirdPartyUaOriginTrialBrowserTest, |
| testing::Values(UserAgentOriginTrialTestType::UAReduction, |
| UserAgentOriginTrialTestType::UADeprecation, |
| UserAgentOriginTrialTestType::UAReductionAndDeprecation)); |
| |
| constexpr const char ThirdPartyUaOriginTrialBrowserTest::kFirstPartyOriginUrl[]; |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaWildcardPolicy) { |
| SetUaPermissionsPolicy("*"); // Allow all third-party sites. |
| |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/simple.html"); |
| } |
| |
| // Tests that headers are not sent to a third-party iframe after script is |
| // disabled with content settings. |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, ScriptDisabled) { |
| SetUaPermissionsPolicy("*"); |
| const GURL url = accept_ch_ua_cross_origin_iframe_request_url(); |
| NavigateAndCheckHeaders(url, |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| // Disable script for first party origin. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromURL(GURL(kFirstPartyOriginUrl)), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::JAVASCRIPT, |
| CONTENT_SETTING_BLOCK); |
| // Headers should not be sent in third party iframe. |
| NavigateAndCheckHeaders(url, |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/simple.html"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, |
| ThirdPartySubresourceUaWithWildcardPolicy) { |
| SetUaPermissionsPolicy("*"); // Allow all third-party sites. |
| |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_subresource_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/style.css"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaSpecificPolicy) { |
| std::string policy = "(self \""; |
| base::StrAppend(&policy, {GetServerOrigin().spec(), "\")"}); |
| SetUaPermissionsPolicy(policy); // Allow our third-party site only. |
| |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/simple.html"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, |
| ThirdPartySubresourceUaSpecificPolicy) { |
| std::string policy = "(self \""; |
| base::StrAppend(&policy, {GetServerOrigin().spec(), "\")"}); |
| SetUaPermissionsPolicy(policy); // Allow our third-party site only. |
| |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_subresource_request_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/style.css"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaInvalidOriginTrialToken) { |
| SetUaPermissionsPolicy("*"); // Allow all third-party sites. |
| SetValidOTToken(false); // Origin Trial Token is invalid. |
| |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/simple.html"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyUaOriginTrialBrowserTest, |
| ThirdPartySubresourceUaInvalidOriginTrialToken) { |
| SetUaPermissionsPolicy("*"); // Allow all third-party sites. |
| SetValidOTToken(false); // Origin Trial Token is invalid. |
| |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_subresource_request_url(), |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), "/style.css"); |
| } |
| |
| // A test fixture for setting Accept-CH and Origin-Trial response headers for |
| // third-party embeds. This suite of tests ensures that third-party embeds |
| // with Sec-CH-UA-Reduced or Sec-CH-UA-Full and a valid Origin Trial token send |
| // the reduced UA string if Sec-CH-UA-Reduced set, or the full UA string if |
| // Sec-CH-UA-Full set in the request header, even if the top-level page is |
| // not enrolled in the UA reduction origin trial. |
| // |
| // The Origin Trial token for UserAgentReduction in the header files were |
| // generated by running (in tools/origin_trials): generate_token.py |
| // https://my-site.com:44444 UserAgentReduction |
| // --is-third-party --expire-timestamp=2000000000 |
| // |
| // The Origin Trial token for SendFullUserAgentAfterReduction in the header |
| // files were generated by running (in tools/origin_trials): generate_token.py |
| // https://my-site.com:44444 SendFullUserAgentAfterReduction |
| // --is-third-party --expire-timestamp=2000000000 |
| class ThirdPartyAcceptChUaOriginTrialBrowserTest |
| : public UaOriginTrialBrowserTest, |
| public testing::WithParamInterface<UserAgentOriginTrialTestType> { |
| public: |
| ThirdPartyAcceptChUaOriginTrialBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| https_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| https_server_.RegisterRequestMonitor(base::BindRepeating( |
| &ThirdPartyAcceptChUaOriginTrialBrowserTest::MonitorRequest, |
| base::Unretained(this))); |
| EXPECT_TRUE(https_server_.Start()); |
| } |
| |
| // The URL that was used to register the Origin Trial token. |
| static constexpr char kThirdPartyOriginUrl[] = "https://my-site.com:44444"; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| // We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, for |
| // the third-party requests, since the origin trial token in the response |
| // is associated with a fixed origin, whereas EmbeddedTestServer serves |
| // content on a random port. |
| url_loader_interceptor_ = |
| std::make_unique<URLLoaderInterceptor>(base::BindRepeating( |
| &ThirdPartyAcceptChUaOriginTrialBrowserTest::InterceptRequest, |
| base::Unretained(this))); |
| } |
| |
| void TearDownOnMainThread() override { |
| url_loader_interceptor_.reset(); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| GURL accept_ch_ua_cross_origin_iframe_request_url() const { |
| return https_server_.GetURL(base::StrCat( |
| {"/accept_ch_ua_cross_origin_iframe_with_ot_request.html"})); |
| } |
| |
| GURL accept_ch_ua_cross_origin_iframe_with_subrequests_url() const { |
| return https_server_.GetURL(base::StrCat( |
| {"/accept_ch_ua_cross_origin_iframe_with_subrequests.html"})); |
| } |
| |
| GURL top_level_with_iframe_redirect_url() const { |
| return https_server_.GetURL( |
| base::StrCat({"/top_level_with_iframe_redirect.html"})); |
| } |
| |
| protected: |
| const absl::optional<std::string>& GetLastUserAgentHeaderValue() override { |
| base::AutoLock lock(last_request_lock_); |
| return last_user_agent_; |
| } |
| |
| const absl::optional<std::string>& GetLastUaOriginTrialClientHintValue() |
| override { |
| base::AutoLock lock(last_request_lock_); |
| return last_sec_ch_ua_vaule_; |
| } |
| |
| const absl::optional<GURL>& GetLastRequestedURL() { |
| base::AutoLock lock(last_request_lock_); |
| return last_requested_url_; |
| } |
| |
| private: |
| // URLLoaderInterceptor callback. Handles the third-party embeds and |
| // subresource requests. |
| bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) { |
| const GURL origin = params->url_request.url.DeprecatedGetOriginAsURL(); |
| // The interceptor does not handle requests to the EmbeddedTestServer. |
| // Requests also get sent to https://accounts.google.com/ (not sure from |
| // where), so we ignore them as well. |
| if (origin == https_server_.base_url() || |
| origin == GURL("https://accounts.google.com/")) { |
| return false; |
| } |
| |
| // Filter out unknown paths to avoid flaky tests. |
| static constexpr auto kExpectedPaths = |
| base::MakeFixedFlatSet<base::StringPiece>({ |
| "/basic.html", |
| "/frame_3p_ot.html", |
| "/nested_style.css", |
| "/redirect_style.css", |
| "/simple_3p_ot.html", |
| "/style.css", |
| "/subresource_redirect.html", |
| }); |
| const std::string path = params->url_request.url.path(); |
| if (!base::Contains(kExpectedPaths, path)) { |
| return false; |
| } |
| |
| SetLastRequestedURL(params->url_request.url); |
| std::string user_agent; |
| params->url_request.headers.GetHeader("user-agent", &user_agent); |
| SetLastUserAgent(&user_agent); |
| std::string sec_ch_ua_value; |
| params->url_request.headers.GetHeader( |
| base::StrCat({GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? "sec-ch-ua-reduced" |
| : "sec-ch-ua-full"}), |
| &sec_ch_ua_value); |
| SetLastSecChUaValue(&sec_ch_ua_value); |
| |
| if (path == "/style.css" || path == "/basic.html" || |
| path == "/nested_style.css") { |
| // These paths are known to be the last request (with no subrequest |
| // after them), so verify that the UA string is set appropriately. |
| const bool ch_ua_reduced_expected = |
| GetParam() == UserAgentOriginTrialTestType::UAReduction; |
| const bool ch_ua_exist_expected = true; |
| CheckUaOriginTrialClientHint(ch_ua_exist_expected); |
| CheckUserAgentReduced(ch_ua_reduced_expected, false); |
| } |
| |
| std::string resource_path = "chrome/test/data/client_hints"; |
| resource_path.append( |
| static_cast<std::string>(params->url_request.url.path_piece())); |
| |
| // Only build mock header with origin trial tokens for the third party |
| // requests. |
| if (origin != GURL(kThirdPartyOriginUrl)) { |
| URLLoaderInterceptor::WriteResponse(resource_path, params->client.get()); |
| return true; |
| } |
| |
| // Generated by running (in tools/origin_trials): |
| // generate_token.py https://my-site.com:44444 UserAgentReduction |
| // --is-third-party --expire-timestamp=2000000000 |
| // |
| // The Origin Trial token expires in 2033. Generate a new token by then, or |
| // find a better way to re-generate a test trial token. |
| static constexpr const char kOriginTrialTokenReduced[] = |
| "Awgc/" |
| "axBbE+4mDB+z2AKFEl26TUKBzCM2GBkDQmt4IephJgHpel1kcsIdCCBYKUgJ4s4+" |
| "JQLXFKkOCs7lFIISAMAAAB0eyJvcmlnaW4iOiAiaHR0cHM6Ly9teS1zaXRlLmNvbTo0NDQ" |
| "0NCIsICJpc1RoaXJkUGFydHkiOiB0cnVlLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y" |
| "3Rpb24iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="; |
| |
| // Generated by running (in tools/origin_trials): |
| // generate_token.py https://my-site.com:44444 |
| // SendFullUserAgentAfterReduction --is-third-party |
| // --expire-timestamp=2000000000 |
| // |
| // The Origin Trial token expires in 2033. Generate a new token by then, |
| // or find a better way to re-generate a test trial token. |
| static constexpr const char kOriginTrialTokenFull[] = |
| "AznwSelRzlbEO7T3NXT68fp+" |
| "k7amzJdYhxfcUEH3M7WTMES73QlwoqK8zBNVd1rGDvFuDxDbDILL4pr7Og6wJw0AAACBey" |
| "JvcmlnaW4iOiAiaHR0cHM6Ly9teS1zaXRlLmNvbTo0NDQ0NCIsICJpc1RoaXJkUGFydHki" |
| "OiB0cnVlLCAiZmVhdHVyZSI6ICJTZW5kRnVsbFVzZXJBZ2VudEFmdGVyUmVkdWN0aW9uIi" |
| "wgImV4cGlyeSI6IDIwMDAwMDAwMDB9"; |
| |
| std::string headers = "HTTP/1.1 200 OK\nContent-Type: text/html\n"; |
| switch (GetParam()) { |
| case UserAgentOriginTrialTestType::UAReduction: |
| base::StrAppend(&headers, {"Accept-CH: ", "Sec-CH-UA-Reduced", "\n"}); |
| base::StrAppend(&headers, {"Origin-Trial: ", kOriginTrialTokenReduced}); |
| break; |
| case UserAgentOriginTrialTestType::UADeprecation: |
| base::StrAppend(&headers, {"Accept-CH: ", "Sec-CH-UA-Full", "\n"}); |
| base::StrAppend(&headers, {"Origin-Trial: ", kOriginTrialTokenFull}); |
| break; |
| case UserAgentOriginTrialTestType::UAReductionAndDeprecation: |
| base::StrAppend( |
| &headers, |
| {"Accept-CH: ", "Sec-CH-UA-Reduced, Sec-CH-UA-Full", "\n"}); |
| base::StrAppend(&headers, {"Origin-Trial: ", kOriginTrialTokenReduced, |
| ",", kOriginTrialTokenFull}); |
| break; |
| default: |
| break; |
| } |
| URLLoaderInterceptor::WriteResponse(resource_path, params->client.get(), |
| &headers); |
| return true; |
| } |
| |
| // Called by `https_server_`. |
| void MonitorRequest(const net::test_server::HttpRequest& request) { |
| // All first party requests don't respond with a valid Origin Trial token, |
| // Reduced UA string or not is controlled by kReduceUserAgentMinorVersion. |
| CheckUserAgentMinorVersion( |
| request.headers.at("user-agent"), |
| /*expected_user_agent_reduced=*/UAReductionEnabled(), true); |
| std::string sec_ua_ch_name = |
| base::StrCat({GetParam() == UserAgentOriginTrialTestType::UAReduction |
| ? "sec-ch-ua-reduced" |
| : "sec-ch-ua-full"}); |
| request.headers.find(sec_ua_ch_name); |
| EXPECT_THAT(request.headers, Not(Contains(Key(sec_ua_ch_name)))); |
| } |
| |
| void SetLastUserAgent(const std::string* value) { |
| base::AutoLock lock(last_request_lock_); |
| if (value != nullptr) { |
| last_user_agent_ = *value; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void SetLastSecChUaValue(const std::string* value) { |
| base::AutoLock lock(last_request_lock_); |
| if (value != nullptr && !value->empty()) { |
| last_sec_ch_ua_vaule_ = *value; |
| } else { |
| last_sec_ch_ua_vaule_ = absl::nullopt; |
| } |
| } |
| |
| void SetLastRequestedURL(const GURL& url) { |
| base::AutoLock lock(last_request_lock_); |
| last_requested_url_ = url; |
| } |
| |
| std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_; |
| net::EmbeddedTestServer https_server_; |
| base::Lock last_request_lock_; |
| absl::optional<std::string> last_user_agent_ GUARDED_BY(last_request_lock_); |
| absl::optional<std::string> last_sec_ch_ua_vaule_ |
| GUARDED_BY(last_request_lock_); |
| absl::optional<GURL> last_requested_url_ GUARDED_BY(last_request_lock_); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ThirdPartyAcceptChUaOriginTrialBrowserTest, |
| testing::Values(UserAgentOriginTrialTestType::UAReduction, |
| UserAgentOriginTrialTestType::UADeprecation, |
| UserAgentOriginTrialTestType::UAReductionAndDeprecation)); |
| |
| constexpr char |
| ThirdPartyAcceptChUaOriginTrialBrowserTest::kThirdPartyOriginUrl[]; |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyAcceptChUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaWithOriginTrialToken) { |
| const GURL top_level_frame_url = |
| accept_ch_ua_cross_origin_iframe_request_url(); |
| // The first navigation is to opt-into the OT. |
| NavigateAndCheckHeaders(top_level_frame_url, |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| NavigateAndCheckHeaders(top_level_frame_url, |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), |
| base::StrCat({"/simple_3p_ot.html"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyAcceptChUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaWithAllCookiesBlocked) { |
| const GURL top_level_frame_url = |
| accept_ch_ua_cross_origin_iframe_request_url(); |
| const GURL third_party_iframe_url = GURL(kThirdPartyOriginUrl); |
| |
| // Block all cookies for the third-party origin. |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(third_party_iframe_url, GURL(), |
| ContentSettingsType::COOKIES, |
| CONTENT_SETTING_BLOCK); |
| |
| // The first navigation is to attempt to opt-into the OT for the third-party |
| // embed, which shouldn't happen for this test because third-party cookies |
| // are blocked. |
| NavigateAndCheckHeaders(top_level_frame_url, |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| NavigateAndCheckHeaders(top_level_frame_url, |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), |
| base::StrCat({"/simple_3p_ot.html"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyAcceptChUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaWithThirdPartyCookiesBlocked) { |
| // Block third-party cookies. |
| browser()->profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kBlockThirdParty)); |
| |
| // The first navigation is to attempt to opt-into the OT for the third-party |
| // embed, which shouldn't happen for this test because cookies are blocked. |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| NavigateAndCheckHeaders(accept_ch_ua_cross_origin_iframe_request_url(), |
| /*ch_ua_reduced_expected=*/UAReductionEnabled(), |
| /*ch_ua_exist_expected=*/false); |
| // Make sure the last intercepted URL was the request for the embedded |
| // iframe. |
| EXPECT_EQ(GetLastRequestedURL()->path(), |
| base::StrCat({"/simple_3p_ot.html"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyAcceptChUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaWithSubresourceRequests) { |
| // The first navigation is to opt-into the OT. Since there are subresource |
| // requests, the last processed requests from the first navigation will have |
| // the corresponding reduced or full UA string. |
| NavigateAndCheckHeaders( |
| accept_ch_ua_cross_origin_iframe_with_subrequests_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| NavigateAndCheckHeaders( |
| accept_ch_ua_cross_origin_iframe_with_subrequests_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ThirdPartyAcceptChUaOriginTrialBrowserTest, |
| ThirdPartyIframeUaWithSubresourceRedirectRequests) { |
| // The first navigation is to opt-into the OT. Since there are subresource |
| // requests, the last processed requests from the first navigation will have |
| // the corresponding reduced or full UA string. |
| NavigateAndCheckHeaders(top_level_with_iframe_redirect_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| NavigateAndCheckHeaders(top_level_with_iframe_redirect_url(), |
| /*ch_ua_reduced_expected=*/GetParam() == |
| UserAgentOriginTrialTestType::UAReduction, |
| /*ch_ua_exist_expected=*/true); |
| } |
| |
| // CrOS multi-profiles implementation is too different for these tests. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| void ClientHintsBrowserTest::TestSwitchWithNewProfile( |
| const std::string& switch_value, |
| size_t origins_stored) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| client_hints::switches::kInitializeClientHintsStorage, switch_value); |
| |
| Profile* profile = GenerateNewProfile(); |
| Browser* browser = CreateBrowser(profile); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser, without_accept_ch_url())); |
| |
| ContentSettingsForOneType host_settings; |
| |
| // Clients hints preferences for one origin should be persisted. |
| HostContentSettingsMapFactory::GetForProfile(profile)->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &host_settings); |
| EXPECT_EQ(origins_stored, host_settings.size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, SwitchAppliesStorage) { |
| TestSwitchWithNewProfile( |
| "{\"https://a.test\":\"Sec-CH-UA-Full-Version\", " |
| "\"https://b.test\":\"Sec-CH-UA-Full-Version\"}", |
| 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, SwitchNotJson) { |
| TestSwitchWithNewProfile("foo", 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, SwitchOriginNotSecure) { |
| TestSwitchWithNewProfile("{\"http://a.test\":\"Sec-CH-UA-Full-Version\"}", 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, SwitchAcceptCHInvalid) { |
| TestSwitchWithNewProfile("{\"https://a.test\":\"Not Valid\"}", 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, SwitchAppliesStorageOneOrigin) { |
| TestSwitchWithNewProfile( |
| "{\"https://a.test\":\"Sec-CH-UA-Full-Version\", " |
| "\"https://b.test\":\"Not Valid\"}", |
| 1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| class ClientHintsCommandLineSwitchBrowserTest : public ClientHintsBrowserTest { |
| public: |
| ClientHintsCommandLineSwitchBrowserTest() = default; |
| |
| void SetUpCommandLine(base::CommandLine* cmd) override { |
| ClientHintsBrowserTest::SetUpCommandLine(cmd); |
| std::string server_origin = |
| url::Origin::Create(accept_ch_url()).Serialize(); |
| |
| std::vector<std::string> accept_ch_tokens; |
| for (const auto& pair : network::GetClientHintToNameMap()) |
| accept_ch_tokens.push_back(pair.second); |
| |
| cmd->AppendSwitchASCII( |
| client_hints::switches::kInitializeClientHintsStorage, |
| base::StringPrintf("{\"%s\":\"%s\"}", server_origin.c_str(), |
| base::JoinString(accept_ch_tokens, ",").c_str())); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsCommandLineSwitchBrowserTest, |
| NavigationToDifferentOrigins) { |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), |
| without_accept_ch_cross_origin())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsCommandLineSwitchBrowserTest, |
| ClearHintsWithAcceptCH) { |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_empty())); |
| |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClientHintsCommandLineSwitchBrowserTest, |
| StorageNotPresentInOffTheRecordProfile) { |
| SetClientHintExpectationsOnMainFrame(true); |
| SetClientHintExpectationsOnSubresources(true); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), without_accept_ch_url())); |
| |
| // OTR profile should get neither. |
| Browser* otr_browser = CreateIncognitoBrowser(browser()->profile()); |
| SetClientHintExpectationsOnMainFrame(false); |
| SetClientHintExpectationsOnSubresources(false); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(otr_browser, without_accept_ch_url())); |
| } |
| |
| // Validate that the updated GREASE algorithm is used by default. The continued |
| // support of the old algorithm (which used only semicolon and space) is tested |
| // separately below. That functionality will be maintained for a period of time |
| // until we are confident in more permutations generated by the updated |
| // algorithm. |
| IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, UpdatedGREASEByDefault) { |
| const GURL gurl = accept_ch_url(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string ua_ch_result = main_frame_ua_observed(); |
| |
| ASSERT_TRUE(SawUpdatedGrease(ua_ch_result) && !SawOldGrease(ua_ch_result)); |
| } |
| |
| class GreaseFeatureParamOptOutTest : public ClientHintsBrowserTest { |
| // Test that feature param opt outs are able to trigger the old algorithm, |
| // which we will maintain until additional confidence in the permutations of |
| // the new algorithm is attained. |
| std::unique_ptr<base::FeatureList> EnabledFeatures() override { |
| std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); |
| feature_list->InitializeFromCommandLine( |
| "GreaseUACH:updated_algorithm/false", ""); |
| return feature_list; |
| } |
| }; |
| |
| // Checks that the updated GREASE algorithm is not used when explicitly |
| // disabled. |
| IN_PROC_BROWSER_TEST_F(GreaseFeatureParamOptOutTest, |
| UpdatedGreaseFeatureParamOptOutTest) { |
| const GURL gurl = accept_ch_url(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string ua_ch_result = main_frame_ua_observed(); |
| |
| ASSERT_TRUE(SawOldGrease(ua_ch_result)); |
| } |
| |
| class GreaseEnterprisePolicyTest : public ClientHintsBrowserTest { |
| void SetUpInProcessBrowserTestFixture() override { |
| policy::PolicyTest::SetUpInProcessBrowserTestFixture(); |
| policy::PolicyMap policies; |
| SetPolicy(&policies, policy::key::kUserAgentClientHintsGREASEUpdateEnabled, |
| absl::optional<base::Value>(false)); |
| provider_.UpdateChromePolicy(policies); |
| } |
| }; |
| |
| // Makes sure that the enterprise policy is able to prevent updated GREASE. |
| IN_PROC_BROWSER_TEST_F(GreaseEnterprisePolicyTest, GreaseEnterprisePolicyTest) { |
| const GURL gurl = accept_ch_url(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string ua_ch_result = main_frame_ua_observed(); |
| |
| ASSERT_TRUE(SawOldGrease(ua_ch_result)); |
| } |
| IN_PROC_BROWSER_TEST_F(GreaseEnterprisePolicyTest, |
| GreaseEnterprisePolicyDynamicRefreshTest) { |
| const GURL gurl = accept_ch_url(); |
| // Reset the policy that was already set to false in the setup, then see if |
| // the change is reflected in the sec-ch-ua header without requiring a |
| // browser restart. |
| policy::PolicyMap policies; |
| SetPolicy(&policies, policy::key::kUserAgentClientHintsGREASEUpdateEnabled, |
| absl::optional<base::Value>(true)); |
| provider_.UpdateChromePolicy(policies); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); |
| std::string ua_ch_result = main_frame_ua_observed(); |
| |
| ASSERT_TRUE(SawUpdatedGrease(ua_ch_result) && !SawOldGrease(ua_ch_result)); |
| } |
| |
| // Tests that the Sec-CH-UA-Reduced client hint gets cleared on a redirect if |
| // the response doesn't contain the hint in the Accept-CH header. |
| class RedirectUaReducedOriginTrialBrowserTest : public InProcessBrowserTest { |
| public: |
| RedirectUaReducedOriginTrialBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| https_server_.ServeFilesFromSourceDirectory( |
| "chrome/test/data/client_hints"); |
| https_server_.RegisterRequestMonitor(base::BindRepeating( |
| &RedirectUaReducedOriginTrialBrowserTest::MonitorResourceRequest, |
| base::Unretained(this))); |
| EXPECT_TRUE(https_server_.Start()); |
| } |
| |
| static constexpr char kRedirectUrl[] = "https://my-site:44444"; |
| |
| void SetUpCommandLine(base::CommandLine* cmd) override { |
| InProcessBrowserTest::SetUpCommandLine(cmd); |
| // Store Sec-CH-UA-Reduced in the Accept-CH cache for the server origin on |
| // browser startup. |
| cmd->AppendSwitchASCII( |
| client_hints::switches::kInitializeClientHintsStorage, |
| base::StringPrintf("{\"%s\":\"%s\"}", |
| https_server_.GetOrigin().Serialize().c_str(), |
| "Sec-CH-UA-Reduced")); |
| } |
| |
| void SetUpOnMainThread() override { |
| // We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, |
| // since the origin trial token in the response is associated with a fixed |
| // origin, whereas EmbeddedTestServer serves content on a random port. |
| url_loader_interceptor_ = |
| std::make_unique<URLLoaderInterceptor>(base::BindRepeating( |
| &RedirectUaReducedOriginTrialBrowserTest::InterceptURLRequest, |
| base::Unretained(this))); |
| |
| InProcessBrowserTest::SetUpOnMainThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| url_loader_interceptor_.reset(); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| GURL accept_ch_url() const { return https_server_.GetURL("/accept_ch.html"); } |
| |
| GURL redirect_url() const { return https_server_.GetURL("/redirect.html"); } |
| |
| std::string last_url() const { return last_url_; } |
| |
| std::string last_user_agent() const { return last_user_agent_; } |
| |
| absl::optional<std::string> last_ua_reduced_ch() const { |
| return last_ua_reduced_ch_; |
| } |
| |
| private: |
| bool InterceptURLRequest(URLLoaderInterceptor::RequestParams* params) { |
| if (url::Origin::Create(params->url_request.url) != |
| url::Origin::Create(GURL(kRedirectUrl))) { |
| return false; |
| } |
| |
| std::string resource_path = "chrome/test/data/client_hints"; |
| resource_path.append( |
| static_cast<std::string>(params->url_request.url.path_piece())); |
| URLLoaderInterceptor::WriteResponse(resource_path, params->client.get()); |
| return true; |
| } |
| |
| void MonitorResourceRequest(const net::test_server::HttpRequest& request) { |
| last_url_.clear(); |
| last_user_agent_.clear(); |
| last_ua_reduced_ch_.reset(); |
| |
| last_url_ = request.GetURL().spec(); |
| last_user_agent_ = request.headers.at("user-agent"); |
| std::string ch_ua_reduced; |
| if (request.headers.find("sec-ch-ua-reduced") != request.headers.end()) { |
| last_ua_reduced_ch_ = request.headers.at("sec-ch-ua-reduced"); |
| } |
| } |
| |
| net::EmbeddedTestServer https_server_; |
| std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_; |
| std::string last_url_; |
| std::string last_user_agent_; |
| absl::optional<std::string> last_ua_reduced_ch_; |
| }; |
| |
| constexpr char RedirectUaReducedOriginTrialBrowserTest::kRedirectUrl[]; |
| |
| IN_PROC_BROWSER_TEST_F(RedirectUaReducedOriginTrialBrowserTest, |
| AcceptChUaReducedWithValidOriginTrialToken) { |
| // The first request sends SEc-CH-UA-Reduced and the reduced UA string, but |
| // redirects to a different origin. Since the response did not contain a |
| // valid origin trial token, Sec-CH-UA-Reduced should be removed from the |
| // Accept-CH cache. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), redirect_url())); |
| EXPECT_EQ(last_url(), redirect_url()); |
| CheckUserAgentMinorVersion(last_user_agent(), |
| /*expected_user_agent_reduced=*/true, false); |
| EXPECT_THAT(last_ua_reduced_ch(), Optional(Eq("?1"))); |
| |
| // The next request to the origin should not send Sec-CH-UA-Reduced. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), accept_ch_url())); |
| EXPECT_EQ(last_url(), accept_ch_url()); |
| CheckUserAgentMinorVersion(last_user_agent(), |
| /*expected_user_agent_reduced=*/ |
| base::FeatureList::IsEnabled( |
| blink::features::kReduceUserAgentMinorVersion), |
| true); |
| EXPECT_THAT(last_ua_reduced_ch(), Eq(absl::nullopt)); |
| } |