blob: 0b67fa11737951e3097b0c5e6f32097e249f0a7f [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "chrome/common/privacy_budget/scoped_privacy_budget_config.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/test/base/android/android_browser_test.h"
#else
#include "chrome/test/base/in_process_browser_test.h"
#endif
namespace {
using base::StringToInt64;
using base::StringToUint64;
using blink::IdentifiableSurface;
constexpr char kFingerprintingScriptUrlSwitch[] = "fingerprinting-script-url";
constexpr char kFingerprintExpectationSwitch[] = "fingerprint-expectation";
constexpr char kInputKeyExpectationSwitch[] = "input-key-expectation";
constexpr char kValueExpectationSwitch[] = "value-expectation";
// NOTE: This test is *disabled* so that it doesn't run on waterfall -- to run
// the test, invoke the test binary as follows:
//
// testing/xvfb.py out/Default/browser_tests --gtest_also_run_disabled_tests
// --gtest_filter="*CanvasInputKeyBrowserTest*"
// --fingerprinting-script-url="file URL goes here"
// [--fingerprint-expectation="optional expected fingerprint goes here"]
// [--input-key-expectation="optional key expectation goes here"]
// [--value-expectation="optional value expectation goes here"]
//
// The --fingerprinting-script-url must resolve to an HTML page that runs a
// script that calls window.domAutomationController.send() with the computed
// fingerprint.
//
// This test runs on Android as well as desktop platforms.
class DISABLED_CanvasInputKeyBrowserTest : public PlatformBrowserTest {
public:
DISABLED_CanvasInputKeyBrowserTest() {
privacy_budget_config_.Apply(test::ScopedPrivacyBudgetConfig::Parameters(
test::ScopedPrivacyBudgetConfig::Presets::kEnableRandomSampling));
}
content::WebContents* web_contents() {
return chrome_test_utils::GetActiveWebContents(this);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
fingerprinting_script_url_ =
command_line->GetSwitchValueASCII(kFingerprintingScriptUrlSwitch);
fingerprint_expectation_ =
command_line->GetSwitchValueASCII(kFingerprintExpectationSwitch);
input_key_expectation_ =
command_line->GetSwitchValueASCII(kInputKeyExpectationSwitch);
value_expectation_ =
command_line->GetSwitchValueASCII(kValueExpectationSwitch);
}
void SetUpOnMainThread() override {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
ukm::TestUkmRecorder& recorder() { return *ukm_recorder_; }
protected:
std::string fingerprinting_script_url_;
std::string fingerprint_expectation_;
std::string input_key_expectation_;
std::string value_expectation_;
test::ScopedPrivacyBudgetConfig privacy_budget_config_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};
struct MetricKeyValue {
uint64_t input_key;
int64_t value;
};
// Verify that there's only one entry of type |type|, and return the the
// |input_key|, |value| pair.
template <typename MapType>
absl::optional<MetricKeyValue> ExtractKeyOfType(IdentifiableSurface::Type type,
const MapType& metrics) {
MetricKeyValue last_result = {};
for (const auto& pair : metrics) {
auto surface = IdentifiableSurface::FromMetricHash(pair.first);
if (surface.GetType() == type) {
if (last_result.input_key != 0) {
ADD_FAILURE() << "Saw at least 2 surfaces of type "
<< static_cast<uint64_t>(type)
<< ". First input hash: " << last_result.input_key
<< " second input hash: " << surface.GetInputHash();
return absl::nullopt;
}
last_result.input_key = surface.GetInputHash();
last_result.value = pair.second;
}
}
return last_result;
}
IN_PROC_BROWSER_TEST_F(DISABLED_CanvasInputKeyBrowserTest,
TestCanvasFingerprint) {
ASSERT_TRUE(embedded_test_server()->Start());
content::DOMMessageQueue messages(web_contents());
base::RunLoop run_loop;
recorder().SetOnAddEntryCallback(ukm::builders::Identifiability::kEntryName,
run_loop.QuitClosure());
ASSERT_TRUE(
content::NavigateToURL(web_contents(), GURL(fingerprinting_script_url_)));
// The document computes the canvas fingerprint and sends a message back to
// the test. Receipt of the message indicates that the script successfully
// completed.
std::string fingerprint;
ASSERT_TRUE(messages.WaitForMessage(&fingerprint));
// Navigating away from the test page causes the document to be unloaded. That
// will cause any buffered metrics to be flushed.
content::NavigateToURLBlockUntilNavigationsComplete(web_contents(),
GURL("about:blank"), 1);
// Wait for the metrics to come down the pipe.
content::RunAllTasksUntilIdle();
run_loop.Run();
auto merged_entries = recorder().GetMergedEntriesByName(
ukm::builders::Identifiability::kEntryName);
// Shouldn't be more than one source here. If this changes, then we'd need to
// adjust this test to deal.
ASSERT_EQ(1u, merged_entries.size());
absl::optional<MetricKeyValue> canvas_key_value =
ExtractKeyOfType(IdentifiableSurface::Type::kCanvasReadback,
merged_entries.begin()->second->metrics);
ASSERT_TRUE(canvas_key_value);
LOG(INFO) << "Canvas fingerprint is: " << fingerprint;
LOG(INFO) << "Input key is: " << canvas_key_value->input_key;
LOG(INFO) << "Value is: " << canvas_key_value->value;
if (!fingerprint_expectation_.empty())
EXPECT_EQ(fingerprint_expectation_, fingerprint);
if (!input_key_expectation_.empty()) {
uint64_t parsed_input_key_expectation;
EXPECT_TRUE(
StringToUint64(input_key_expectation_, &parsed_input_key_expectation));
EXPECT_EQ(parsed_input_key_expectation, canvas_key_value->input_key);
}
if (!value_expectation_.empty()) {
int64_t parsed_value_expectation;
EXPECT_TRUE(StringToInt64(value_expectation_, &parsed_value_expectation));
EXPECT_EQ(parsed_value_expectation, canvas_key_value->value);
}
}
} // namespace