blob: 9b5e32d9641066b71b372e880f5c46e012a3e70a [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/functional/callback_forward.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/client_hints_controller_delegate.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "fuchsia_web/common/test/frame_test_util.h"
#include "fuchsia_web/common/test/test_navigation_listener.h"
#include "fuchsia_web/webengine/browser/context_impl.h"
#include "fuchsia_web/webengine/browser/frame_impl.h"
#include "fuchsia_web/webengine/browser/frame_impl_browser_test_base.h"
#include "fuchsia_web/webengine/test/frame_for_test.h"
#include "services/network/public/mojom/web_client_hints_types.mojom-shared.h"
namespace {
// Value returned by echoheader if header is not present in the request.
constexpr const char kHeaderNotPresent[] = "None";
// Client Hint header names defined by the spec.
constexpr const char kRoundTripTimeCH[] = "rtt";
constexpr const char kDeviceMemoryCH[] = "sec-ch-device-memory";
constexpr const char kUserAgentCH[] = "sec-ch-ua";
constexpr const char kFullVersionListCH[] = "sec-ch-ua-full-version-list";
constexpr const char kArchCH[] = "sec-ch-ua-arch";
constexpr const char kBitnessCH[] = "sec-ch-ua-bitness";
constexpr const char kPlatformCH[] = "sec-ch-ua-platform";
// |str| is interpreted as a decimal number or integer.
void ExpectStringIsNonNegativeNumber(std::string& str) {
double str_double;
EXPECT_TRUE(base::StringToDouble(str, &str_double));
EXPECT_GE(str_double, 0);
}
} // namespace
// TODO(crbug.com/1356277): Client Hints temporarily disabled as it is causing
// several apps to fail. Re-enable Client Hints tests after breakage is fixed.
class DISABLED_ClientHintsTest : public FrameImplTestBaseWithServer {
public:
DISABLED_ClientHintsTest() = default;
~DISABLED_ClientHintsTest() override = default;
DISABLED_ClientHintsTest(const DISABLED_ClientHintsTest&) = delete;
DISABLED_ClientHintsTest& operator=(const DISABLED_ClientHintsTest&) = delete;
void SetUpOnMainThread() override {
FrameImplTestBaseWithServer::SetUpOnMainThread();
frame_for_test_ =
FrameForTest::Create(context(), fuchsia::web::CreateFrameParams());
}
protected:
// Sets Client Hints for embedded test server to request from the content
// embedder. Sends "Accept-CH" response header with |hint_types|, a
// comma-separated list of Client Hint types.
void SetClientHintsForTestServerToRequest(const std::string& hint_types) {
GURL url = embedded_test_server()->GetURL(
std::string("/set-header?Accept-CH: ") + hint_types);
LoadUrlAndExpectResponse(frame_for_test_.GetNavigationController(), {},
url.spec());
frame_for_test_.navigation_listener().RunUntilUrlEquals(url);
}
// Gets the value of |header| returned by WebEngine on a navigation.
// Loads "/echoheader" which echoes the given |header|. The server responds to
// this navigation request with the header value. Returns the header value,
// which is read by JavaScript. Returns kHeaderNotPresent if header was not
// sent during the request.
std::string GetNavRequestHeaderValue(const std::string& header) {
GURL url =
embedded_test_server()->GetURL(std::string("/echoheader?") + header);
LoadUrlAndExpectResponse(frame_for_test_.GetNavigationController(), {},
url.spec());
frame_for_test_.navigation_listener().RunUntilUrlEquals(url);
absl::optional<base::Value> value =
ExecuteJavaScript(frame_for_test_.get(), "document.body.innerText;");
return value->GetString();
}
// Gets the value of |header| returned by WebEngine on a XMLHttpRequest.
// Loads "/echoheader" which echoes the given |header|. The server responds to
// the XMLHttpRequest with the header value, which is saved in a JavaScript
// Promise. Returns the value of Promise, and returns kHeaderNotPresent if
// header is not sent during the request. Requires a loaded page first. Call
// TestServerRequestsClientHints or GetNavRequestHeaderValue first to have a
// loaded page.
std::string GetXHRRequestHeaderValue(const std::string& header) {
constexpr char kScript[] = R"(
new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/echoheader?" + $1);
xhr.onload = () => {
resolve(xhr.response);
};
xhr.send();
})
)";
FrameImpl* frame_impl =
context_impl()->GetFrameImplForTest(&frame_for_test_.ptr());
content::WebContents* web_contents = frame_impl->web_contents_for_test();
return content::EvalJs(web_contents, content::JsReplace(kScript, header))
.ExtractString();
}
// Fetches value of Client Hint |hint_type| for both navigation and
// subresource requests, and calls |verify_callback| with the value.
void GetAndVerifyClientHint(
const std::string& hint_type,
base::RepeatingCallback<void(std::string&)> verify_callback) {
std::string result = GetNavRequestHeaderValue(hint_type);
verify_callback.Run(result);
result = GetXHRRequestHeaderValue(hint_type);
verify_callback.Run(result);
}
FrameForTest frame_for_test_;
};
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest, NumericalClientHints) {
SetClientHintsForTestServerToRequest(std::string(kRoundTripTimeCH) + "," +
std::string(kDeviceMemoryCH));
GetAndVerifyClientHint(kRoundTripTimeCH,
base::BindRepeating(&ExpectStringIsNonNegativeNumber));
GetAndVerifyClientHint(kDeviceMemoryCH,
base::BindRepeating(&ExpectStringIsNonNegativeNumber));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest, InvalidClientHint) {
// Check browser handles requests for an invalid Client Hint.
SetClientHintsForTestServerToRequest("not-a-client-hint");
GetAndVerifyClientHint("not-a-client-hint",
base::BindRepeating([](std::string& str) {
EXPECT_EQ(str, kHeaderNotPresent);
}));
}
// Low-entropy User Agent Client Hints are sent by default without the origin
// needing to request them. For a list of low-entropy Client Hints, see
// https://wicg.github.io/client-hints-infrastructure/#low-entropy-hint-table/
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest,
LowEntropyClientHintsAreSentByDefault) {
GetAndVerifyClientHint(
kUserAgentCH, base::BindRepeating([](std::string& str) {
EXPECT_TRUE(str.find("Chromium") != std::string::npos);
EXPECT_TRUE(str.find(version_info::GetMajorVersionNumber()) !=
std::string::npos);
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest,
LowEntropyClientHintsAreSentWhenRequested) {
SetClientHintsForTestServerToRequest(kUserAgentCH);
GetAndVerifyClientHint(
kUserAgentCH, base::BindRepeating([](std::string& str) {
EXPECT_TRUE(str.find("Chromium") != std::string::npos);
EXPECT_TRUE(str.find(version_info::GetMajorVersionNumber()) !=
std::string::npos);
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest,
HighEntropyClientHintsAreNotSentByDefault) {
GetAndVerifyClientHint(kFullVersionListCH,
base::BindRepeating([](std::string& str) {
EXPECT_EQ(str, kHeaderNotPresent);
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest,
HighEntropyClientHintsAreSentWhenRequested) {
SetClientHintsForTestServerToRequest(kFullVersionListCH);
GetAndVerifyClientHint(
kFullVersionListCH, base::BindRepeating([](std::string& str) {
EXPECT_TRUE(str.find("Chromium") != std::string::npos);
EXPECT_TRUE(str.find(version_info::GetVersionNumber()) !=
std::string::npos);
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest, ArchitectureIsArmOrX86) {
SetClientHintsForTestServerToRequest(kArchCH);
GetAndVerifyClientHint(kArchCH, base::BindRepeating([](std::string& str) {
#if defined(ARCH_CPU_X86_64)
EXPECT_EQ(str, "\"x86\"");
#elif defined(ARCH_CPU_ARM64)
EXPECT_EQ(str, "\"arm\"");
#else
#error Unsupported CPU architecture
#endif
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest, BitnessIs64) {
SetClientHintsForTestServerToRequest(kBitnessCH);
GetAndVerifyClientHint(kBitnessCH, base::BindRepeating([](std::string& str) {
EXPECT_EQ(str, "\"64\"");
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest, PlatformIsFuchsia) {
// Platform is a low-entropy Client Hint, so no need for test server to
// request it.
GetAndVerifyClientHint(kPlatformCH, base::BindRepeating([](std::string& str) {
EXPECT_EQ(str, "\"Fuchsia\"");
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest, RemoveClientHint) {
SetClientHintsForTestServerToRequest(std::string(kRoundTripTimeCH) + "," +
std::string(kDeviceMemoryCH));
GetAndVerifyClientHint(kDeviceMemoryCH,
base::BindRepeating(&ExpectStringIsNonNegativeNumber));
// Remove device memory from list of requested Client Hints. Removed hints
// should no longer be sent.
SetClientHintsForTestServerToRequest(kRoundTripTimeCH);
GetAndVerifyClientHint(kDeviceMemoryCH,
base::BindRepeating([](std::string& str) {
EXPECT_EQ(str, kHeaderNotPresent);
}));
}
IN_PROC_BROWSER_TEST_F(DISABLED_ClientHintsTest,
AdditionalClientHintsAreAlwaysSent) {
SetClientHintsForTestServerToRequest(kRoundTripTimeCH);
// Enable device memory as an additional Client Hint.
auto* client_hints_delegate =
context_impl()->browser_context()->GetClientHintsControllerDelegate();
client_hints_delegate->SetAdditionalClientHints(
{network::mojom::WebClientHintsType::kDeviceMemory});
GetAndVerifyClientHint(kRoundTripTimeCH,
base::BindRepeating(&ExpectStringIsNonNegativeNumber));
// The additional Client Hint is sent without needing to be requested.
GetAndVerifyClientHint(kDeviceMemoryCH,
base::BindRepeating(&ExpectStringIsNonNegativeNumber));
// Remove all additional Client Hints.
client_hints_delegate->ClearAdditionalClientHints();
// Request a different URL because the frame would not reload the page with
// the same URL.
GetAndVerifyClientHint(kRoundTripTimeCH,
base::BindRepeating(&ExpectStringIsNonNegativeNumber));
// Removed additional Client Hint is no longer sent.
GetAndVerifyClientHint(kDeviceMemoryCH,
base::BindRepeating([](std::string& str) {
EXPECT_EQ(str, kHeaderNotPresent);
}));
}