client_hints_browsertest.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "base/callback_forward.h"
  5. #include "base/strings/string_number_conversions.h"
  6. #include "build/build_config.h"
  7. #include "components/version_info/version_info.h"
  8. #include "content/public/browser/client_hints_controller_delegate.h"
  9. #include "content/public/browser/web_contents.h"
  10. #include "content/public/test/browser_test.h"
  11. #include "content/public/test/browser_test_utils.h"
  12. #include "fuchsia_web/common/test/frame_test_util.h"
  13. #include "fuchsia_web/common/test/test_navigation_listener.h"
  14. #include "fuchsia_web/webengine/browser/context_impl.h"
  15. #include "fuchsia_web/webengine/browser/frame_impl.h"
  16. #include "fuchsia_web/webengine/browser/frame_impl_browser_test_base.h"
  17. #include "fuchsia_web/webengine/test/frame_for_test.h"
  18. #include "services/network/public/mojom/web_client_hints_types.mojom-shared.h"
  19. namespace {
  20. // Value returned by echoheader if header is not present in the request.
  21. constexpr const char kHeaderNotPresent[] = "None";
  22. // Client Hint header names defined by the spec.
  23. constexpr const char kRoundTripTimeCH[] = "rtt";
  24. constexpr const char kDeviceMemoryCH[] = "sec-ch-device-memory";
  25. constexpr const char kUserAgentCH[] = "sec-ch-ua";
  26. constexpr const char kFullVersionListCH[] = "sec-ch-ua-full-version-list";
  27. constexpr const char kArchCH[] = "sec-ch-ua-arch";
  28. constexpr const char kBitnessCH[] = "sec-ch-ua-bitness";
  29. constexpr const char kPlatformCH[] = "sec-ch-ua-platform";
  30. // |str| is interpreted as a decimal number or integer.
  31. void ExpectStringIsNonNegativeNumber(std::string& str) {
  32. double str_double;
  33. EXPECT_TRUE(base::StringToDouble(str, &str_double));
  34. EXPECT_GE(str_double, 0);
  35. }
  36. } // namespace
  37. class ClientHintsTest : public FrameImplTestBaseWithServer {
  38. public:
  39. ClientHintsTest() = default;
  40. ~ClientHintsTest() override = default;
  41. ClientHintsTest(const ClientHintsTest&) = delete;
  42. ClientHintsTest& operator=(const ClientHintsTest&) = delete;
  43. void SetUpOnMainThread() override {
  44. FrameImplTestBaseWithServer::SetUpOnMainThread();
  45. frame_for_test_ =
  46. FrameForTest::Create(context(), fuchsia::web::CreateFrameParams());
  47. }
  48. protected:
  49. // Sets Client Hints for embedded test server to request from the content
  50. // embedder. Sends "Accept-CH" response header with |hint_types|, a
  51. // comma-separated list of Client Hint types.
  52. void SetClientHintsForTestServerToRequest(const std::string& hint_types) {
  53. GURL url = embedded_test_server()->GetURL(
  54. std::string("/set-header?Accept-CH: ") + hint_types);
  55. LoadUrlAndExpectResponse(frame_for_test_.GetNavigationController(), {},
  56. url.spec());
  57. frame_for_test_.navigation_listener().RunUntilUrlEquals(url);
  58. }
  59. // Gets the value of |header| returned by WebEngine on a navigation.
  60. // Loads "/echoheader" which echoes the given |header|. The server responds to
  61. // this navigation request with the header value. Returns the header value,
  62. // which is read by JavaScript. Returns kHeaderNotPresent if header was not
  63. // sent during the request.
  64. std::string GetNavRequestHeaderValue(const std::string& header) {
  65. GURL url =
  66. embedded_test_server()->GetURL(std::string("/echoheader?") + header);
  67. LoadUrlAndExpectResponse(frame_for_test_.GetNavigationController(), {},
  68. url.spec());
  69. frame_for_test_.navigation_listener().RunUntilUrlEquals(url);
  70. absl::optional<base::Value> value =
  71. ExecuteJavaScript(frame_for_test_.get(), "document.body.innerText;");
  72. return value->GetString();
  73. }
  74. // Gets the value of |header| returned by WebEngine on a XMLHttpRequest.
  75. // Loads "/echoheader" which echoes the given |header|. The server responds to
  76. // the XMLHttpRequest with the header value, which is saved in a JavaScript
  77. // Promise. Returns the value of Promise, and returns kHeaderNotPresent if
  78. // header is not sent during the request. Requires a loaded page first. Call
  79. // TestServerRequestsClientHints or GetNavRequestHeaderValue first to have a
  80. // loaded page.
  81. std::string GetXHRRequestHeaderValue(const std::string& header) {
  82. constexpr char kScript[] = R"(
  83. new Promise(function (resolve, reject) {
  84. const xhr = new XMLHttpRequest();
  85. xhr.open("GET", "/echoheader?" + $1);
  86. xhr.onload = () => {
  87. resolve(xhr.response);
  88. };
  89. xhr.send();
  90. })
  91. )";
  92. FrameImpl* frame_impl =
  93. context_impl()->GetFrameImplForTest(&frame_for_test_.ptr());
  94. content::WebContents* web_contents = frame_impl->web_contents_for_test();
  95. return content::EvalJs(web_contents, content::JsReplace(kScript, header))
  96. .ExtractString();
  97. }
  98. // Fetches value of Client Hint |hint_type| for both navigation and
  99. // subresource requests, and calls |verify_callback| with the value.
  100. void GetAndVerifyClientHint(
  101. const std::string& hint_type,
  102. base::RepeatingCallback<void(std::string&)> verify_callback) {
  103. std::string result = GetNavRequestHeaderValue(hint_type);
  104. verify_callback.Run(result);
  105. result = GetXHRRequestHeaderValue(hint_type);
  106. verify_callback.Run(result);
  107. }
  108. FrameForTest frame_for_test_;
  109. };
  110. IN_PROC_BROWSER_TEST_F(ClientHintsTest, NumericalClientHints) {
  111. SetClientHintsForTestServerToRequest(std::string(kRoundTripTimeCH) + "," +
  112. std::string(kDeviceMemoryCH));
  113. GetAndVerifyClientHint(kRoundTripTimeCH,
  114. base::BindRepeating(&ExpectStringIsNonNegativeNumber));
  115. GetAndVerifyClientHint(kDeviceMemoryCH,
  116. base::BindRepeating(&ExpectStringIsNonNegativeNumber));
  117. }
  118. IN_PROC_BROWSER_TEST_F(ClientHintsTest, InvalidClientHint) {
  119. // Check browser handles requests for an invalid Client Hint.
  120. SetClientHintsForTestServerToRequest("not-a-client-hint");
  121. GetAndVerifyClientHint("not-a-client-hint",
  122. base::BindRepeating([](std::string& str) {
  123. EXPECT_EQ(str, kHeaderNotPresent);
  124. }));
  125. }
  126. // Low-entropy User Agent Client Hints are sent by default without the origin
  127. // needing to request them. For a list of low-entropy Client Hints, see
  128. // https://wicg.github.io/client-hints-infrastructure/#low-entropy-hint-table/
  129. IN_PROC_BROWSER_TEST_F(ClientHintsTest, LowEntropyClientHintsAreSentByDefault) {
  130. GetAndVerifyClientHint(
  131. kUserAgentCH, base::BindRepeating([](std::string& str) {
  132. EXPECT_TRUE(str.find("Chromium") != std::string::npos);
  133. EXPECT_TRUE(str.find(version_info::GetMajorVersionNumber()) !=
  134. std::string::npos);
  135. }));
  136. }
  137. IN_PROC_BROWSER_TEST_F(ClientHintsTest,
  138. LowEntropyClientHintsAreSentWhenRequested) {
  139. SetClientHintsForTestServerToRequest(kUserAgentCH);
  140. GetAndVerifyClientHint(
  141. kUserAgentCH, base::BindRepeating([](std::string& str) {
  142. EXPECT_TRUE(str.find("Chromium") != std::string::npos);
  143. EXPECT_TRUE(str.find(version_info::GetMajorVersionNumber()) !=
  144. std::string::npos);
  145. }));
  146. }
  147. IN_PROC_BROWSER_TEST_F(ClientHintsTest,
  148. HighEntropyClientHintsAreNotSentByDefault) {
  149. GetAndVerifyClientHint(kFullVersionListCH,
  150. base::BindRepeating([](std::string& str) {
  151. EXPECT_EQ(str, kHeaderNotPresent);
  152. }));
  153. }
  154. IN_PROC_BROWSER_TEST_F(ClientHintsTest,
  155. HighEntropyClientHintsAreSentWhenRequested) {
  156. SetClientHintsForTestServerToRequest(kFullVersionListCH);
  157. GetAndVerifyClientHint(
  158. kFullVersionListCH, base::BindRepeating([](std::string& str) {
  159. EXPECT_TRUE(str.find("Chromium") != std::string::npos);
  160. EXPECT_TRUE(str.find(version_info::GetVersionNumber()) !=
  161. std::string::npos);
  162. }));
  163. }
  164. IN_PROC_BROWSER_TEST_F(ClientHintsTest, ArchitectureIsArmOrX86) {
  165. SetClientHintsForTestServerToRequest(kArchCH);
  166. GetAndVerifyClientHint(kArchCH, base::BindRepeating([](std::string& str) {
  167. #if defined(ARCH_CPU_X86_64)
  168. EXPECT_EQ(str, "\"x86\"");
  169. #elif defined(ARCH_CPU_ARM64)
  170. EXPECT_EQ(str, "\"arm\"");
  171. #else
  172. #error Unsupported CPU architecture
  173. #endif
  174. }));
  175. }
  176. IN_PROC_BROWSER_TEST_F(ClientHintsTest, BitnessIs64) {
  177. SetClientHintsForTestServerToRequest(kBitnessCH);
  178. GetAndVerifyClientHint(kBitnessCH, base::BindRepeating([](std::string& str) {
  179. EXPECT_EQ(str, "\"64\"");
  180. }));
  181. }
  182. IN_PROC_BROWSER_TEST_F(ClientHintsTest, PlatformIsFuchsia) {
  183. // Platform is a low-entropy Client Hint, so no need for test server to
  184. // request it.
  185. GetAndVerifyClientHint(kPlatformCH, base::BindRepeating([](std::string& str) {
  186. EXPECT_EQ(str, "\"Fuchsia\"");
  187. }));
  188. }
  189. IN_PROC_BROWSER_TEST_F(ClientHintsTest, RemoveClientHint) {
  190. SetClientHintsForTestServerToRequest(std::string(kRoundTripTimeCH) + "," +
  191. std::string(kDeviceMemoryCH));
  192. GetAndVerifyClientHint(kDeviceMemoryCH,
  193. base::BindRepeating(&ExpectStringIsNonNegativeNumber));
  194. // Remove device memory from list of requested Client Hints. Removed hints
  195. // should no longer be sent.
  196. SetClientHintsForTestServerToRequest(kRoundTripTimeCH);
  197. GetAndVerifyClientHint(kDeviceMemoryCH,
  198. base::BindRepeating([](std::string& str) {
  199. EXPECT_EQ(str, kHeaderNotPresent);
  200. }));
  201. }
  202. IN_PROC_BROWSER_TEST_F(ClientHintsTest, AdditionalClientHintsAreAlwaysSent) {
  203. SetClientHintsForTestServerToRequest(kRoundTripTimeCH);
  204. // Enable device memory as an additional Client Hint.
  205. auto* client_hints_delegate =
  206. context_impl()->browser_context()->GetClientHintsControllerDelegate();
  207. client_hints_delegate->SetAdditionalClientHints(
  208. {network::mojom::WebClientHintsType::kDeviceMemory});
  209. GetAndVerifyClientHint(kRoundTripTimeCH,
  210. base::BindRepeating(&ExpectStringIsNonNegativeNumber));
  211. // The additional Client Hint is sent without needing to be requested.
  212. GetAndVerifyClientHint(kDeviceMemoryCH,
  213. base::BindRepeating(&ExpectStringIsNonNegativeNumber));
  214. // Remove all additional Client Hints.
  215. client_hints_delegate->ClearAdditionalClientHints();
  216. // Request a different URL because the frame would not reload the page with
  217. // the same URL.
  218. GetAndVerifyClientHint(kRoundTripTimeCH,
  219. base::BindRepeating(&ExpectStringIsNonNegativeNumber));
  220. // Removed additional Client Hint is no longer sent.
  221. GetAndVerifyClientHint(kDeviceMemoryCH,
  222. base::BindRepeating([](std::string& str) {
  223. EXPECT_EQ(str, kHeaderNotPresent);
  224. }));
  225. }