blob: 5abb195b1fc76acd80789baea6b11ea825f4d927 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <strstream>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/extend.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/page/content_to_visible_time_reporter.h"
#include "ui/gfx/presentation_feedback.h"
namespace blink {
constexpr char kBfcacheRestoreHistogram[] =
"BackForwardCache.Restore.NavigationToFirstPaint";
constexpr base::TimeDelta kDuration = base::Milliseconds(42);
constexpr base::TimeDelta kOtherDuration = base::Milliseconds(4242);
// Combinations of tab states that log different histogram suffixes.
struct TabStateParams {
bool has_saved_frames;
bool destination_is_loaded;
bool tab_switch_metrics2_enabled;
const char* histogram_suffix;
};
constexpr TabStateParams kTabStatesToTest[] = {
// WithSavedFrames
{
.has_saved_frames = true,
.destination_is_loaded = true,
.tab_switch_metrics2_enabled = false,
.histogram_suffix = "WithSavedFrames",
},
{
.has_saved_frames = true,
.destination_is_loaded = true,
.tab_switch_metrics2_enabled = true,
.histogram_suffix = "WithSavedFrames",
},
// NoSavedFrames_Loaded
{
.has_saved_frames = false,
.destination_is_loaded = true,
.tab_switch_metrics2_enabled = false,
.histogram_suffix = "NoSavedFrames_Loaded",
},
{
.has_saved_frames = false,
.destination_is_loaded = true,
.tab_switch_metrics2_enabled = true,
.histogram_suffix = "NoSavedFrames_Loaded",
},
// NoSavedFrames_NotLoaded
{
.has_saved_frames = false,
.destination_is_loaded = false,
.tab_switch_metrics2_enabled = false,
.histogram_suffix = "NoSavedFrames_NotLoaded",
},
{
.has_saved_frames = false,
.destination_is_loaded = false,
.tab_switch_metrics2_enabled = true,
.histogram_suffix = "NoSavedFrames_NotLoaded",
},
};
class ContentToVisibleTimeReporterTest
: public ::testing::TestWithParam<TabStateParams> {
protected:
ContentToVisibleTimeReporterTest() : tab_state_(GetParam()) {
scoped_feature_list_.InitWithFeatureState(
blink::features::kTabSwitchMetrics2,
tab_state_.tab_switch_metrics2_enabled);
duration_histograms_.push_back(base::StrCat(
{"Browser.Tabs.TotalSwitchDuration.", tab_state_.histogram_suffix}));
incomplete_duration_histograms_.push_back(
base::StrCat({"Browser.Tabs.TotalIncompleteSwitchDuration.",
tab_state_.histogram_suffix}));
result_histograms_.push_back(base::StrCat(
{"Browser.Tabs.TabSwitchResult.", tab_state_.histogram_suffix}));
if (tab_state_.tab_switch_metrics2_enabled) {
// Additional metrics logged when the TabSwitchMetrics2 feature is
// enabled.
// TODO(crbug.com/1164477): When the feature is enabled both the old and
// new histograms are logged, so that the old histograms with and without
// the feature can be easily compared in an A/B test. When the feature
// ships by default remove the old histograms.
duration_histograms_.push_back(base::StrCat(
{"Browser.Tabs.TotalSwitchDuration2.", tab_state_.histogram_suffix}));
incomplete_duration_histograms_.push_back(
base::StrCat({"Browser.Tabs.TotalIncompleteSwitchDuration2.",
tab_state_.histogram_suffix}));
result_histograms_.push_back(base::StrCat(
{"Browser.Tabs.TabSwitchResult2.", tab_state_.histogram_suffix}));
}
// Expect all histograms to be empty.
ExpectHistogramsEmptyExcept({});
}
void ExpectHistogramsEmptyExcept(
const std::vector<std::string>& histograms_with_values) {
constexpr const char* kAllHistograms[] = {
// Pre-TabSwitchMetrics2 feature.
"Browser.Tabs.TotalSwitchDuration.WithSavedFrames",
"Browser.Tabs.TotalSwitchDuration.NoSavedFrames_Loaded",
"Browser.Tabs.TotalSwitchDuration.NoSavedFrames_NotLoaded",
"Browser.Tabs.TotalIncompleteSwitchDuration.WithSavedFrames",
"Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_Loaded",
"Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_"
"NotLoaded",
"Browser.Tabs.TabSwitchResult.WithSavedFrames",
"Browser.Tabs.TabSwitchResult.NoSavedFrames_Loaded",
"Browser.Tabs.TabSwitchResult.NoSavedFrames_NotLoaded",
// With TabSwitchMetrics2 feature.
"Browser.Tabs.TotalSwitchDuration2.WithSavedFrames",
"Browser.Tabs.TotalSwitchDuration2.NoSavedFrames_Loaded",
"Browser.Tabs.TotalSwitchDuration2.NoSavedFrames_NotLoaded",
"Browser.Tabs.TotalIncompleteSwitchDuration2.WithSavedFrames",
"Browser.Tabs.TotalIncompleteSwitchDuration2.NoSavedFrames_"
"Loaded",
"Browser.Tabs.TotalIncompleteSwitchDuration2.NoSavedFrames_"
"NotLoaded",
"Browser.Tabs.TabSwitchResult2.WithSavedFrames",
"Browser.Tabs.TabSwitchResult2.NoSavedFrames_Loaded",
"Browser.Tabs.TabSwitchResult2.NoSavedFrames_NotLoaded",
// Non-tab switch.
kBfcacheRestoreHistogram};
std::vector<std::string> unexpected_histograms;
for (const char* histogram : kAllHistograms) {
if (!base::Contains(histograms_with_values, histogram))
unexpected_histograms.push_back(histogram);
}
ExpectTotalSamples(unexpected_histograms, 0);
}
void ExpectTotalSamples(const std::vector<std::string>& histogram_names,
int expected_count) {
for (const std::string& histogram_name : histogram_names) {
SCOPED_TRACE(base::StringPrintf("Expect %d samples in %s.",
expected_count, histogram_name.c_str()));
EXPECT_EQ(static_cast<int>(
histogram_tester_.GetAllSamples(histogram_name).size()),
expected_count);
}
}
void ExpectTimeBucketCounts(const std::vector<std::string>& histogram_names,
base::TimeDelta value,
int count) {
for (const std::string& histogram_name : histogram_names) {
histogram_tester_.ExpectTimeBucketCount(histogram_name, value, count);
}
}
void ExpectResultBucketCounts(
const std::vector<std::string>& histogram_names,
ContentToVisibleTimeReporter::TabSwitchResult value,
int count) {
for (const std::string& histogram_name : histogram_names) {
histogram_tester_.ExpectBucketCount(histogram_name, value, count);
}
}
// Create `feature_list_` before `task_environment_` and destroy it after to
// avoid a race in destruction.
base::test::ScopedFeatureList scoped_feature_list_;
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
ContentToVisibleTimeReporter tab_switch_time_recorder_;
base::HistogramTester histogram_tester_;
TabStateParams tab_state_;
// Expected histogram names to be logged for the given TabStateParams.
std::vector<std::string> duration_histograms_;
std::vector<std::string> incomplete_duration_histograms_;
std::vector<std::string> result_histograms_;
};
INSTANTIATE_TEST_SUITE_P(All,
ContentToVisibleTimeReporterTest,
::testing::ValuesIn(kTabStatesToTest));
// Time is properly recorded to histogram if we have a proper matching
// TabWasShown and callback execution.
TEST_P(ContentToVisibleTimeReporterTest, TimeIsRecorded) {
const auto start = base::TimeTicks::Now();
auto callback = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ false));
const auto end = start + kDuration;
auto presentation_feedback = gfx::PresentationFeedback(
end, end - start, gfx::PresentationFeedback::Flags::kHWCompletion);
std::move(callback).Run(presentation_feedback);
std::vector<std::string> expected_histograms;
base::Extend(expected_histograms, duration_histograms_);
base::Extend(expected_histograms, result_histograms_);
ExpectHistogramsEmptyExcept(expected_histograms);
// Duration.
ExpectTotalSamples(duration_histograms_, 1);
ExpectTimeBucketCounts(duration_histograms_, kDuration, 1);
// Result.
ExpectTotalSamples(result_histograms_, 1);
ExpectResultBucketCounts(
result_histograms_,
ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
}
// A failure should be reported if gfx::PresentationFeedback contains the
// kFailure flag.
TEST_P(ContentToVisibleTimeReporterTest, PresentationFailure) {
const auto start = base::TimeTicks::Now();
auto callback = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ false));
std::move(callback).Run(gfx::PresentationFeedback::Failure());
ExpectHistogramsEmptyExcept(result_histograms_);
// Result (no duration is recorded on presentation failure).
ExpectTotalSamples(result_histograms_, 1);
ExpectResultBucketCounts(
result_histograms_,
ContentToVisibleTimeReporter::TabSwitchResult::kPresentationFailure, 1);
}
// An incomplete tab switch is reported when no frame is shown before a tab is
// hidden.
TEST_P(ContentToVisibleTimeReporterTest, HideBeforePresentFrame) {
const auto start1 = base::TimeTicks::Now();
auto callback1 = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start1, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ false));
task_environment_.FastForwardBy(kDuration);
tab_switch_time_recorder_.TabWasHidden();
std::vector<std::string> expected_histograms;
base::Extend(expected_histograms, result_histograms_);
base::Extend(expected_histograms, incomplete_duration_histograms_);
ExpectHistogramsEmptyExcept(expected_histograms);
// Duration.
ExpectTotalSamples(incomplete_duration_histograms_, 1);
ExpectTimeBucketCounts(incomplete_duration_histograms_, kDuration, 1);
// Result.
ExpectTotalSamples(result_histograms_, 1);
ExpectResultBucketCounts(
result_histograms_,
ContentToVisibleTimeReporter::TabSwitchResult::kIncomplete, 1);
const auto start2 = base::TimeTicks::Now();
auto callback2 = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start2, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ false));
const auto end2 = start2 + kOtherDuration;
auto presentation_feedback = gfx::PresentationFeedback(
end2, end2 - start2, gfx::PresentationFeedback::Flags::kHWCompletion);
std::move(callback2).Run(presentation_feedback);
// Now the tab switch completes, and adds a duration histogram.
base::Extend(expected_histograms, duration_histograms_);
ExpectHistogramsEmptyExcept(expected_histograms);
// Duration.
ExpectTotalSamples(incomplete_duration_histograms_, 1);
ExpectTimeBucketCounts(incomplete_duration_histograms_, kDuration, 1);
ExpectTotalSamples(duration_histograms_, 1);
ExpectTimeBucketCounts(duration_histograms_, kOtherDuration, 1);
// Result.
ExpectTotalSamples(result_histograms_, 2);
ExpectResultBucketCounts(
result_histograms_,
ContentToVisibleTimeReporter::TabSwitchResult::kIncomplete, 1);
ExpectResultBucketCounts(
result_histograms_,
ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
}
// When the TabSwitchMetrics2 feature is enabled, if TabWasHidden is not called
// an incomplete tab switch is reported.
// TODO(crbug.com/1289266): Find and remove all cases where TabWasHidden is not
// called.
TEST_P(ContentToVisibleTimeReporterTest, MissingTabWasHidden) {
if (!tab_state_.tab_switch_metrics2_enabled)
GTEST_SKIP();
const auto start1 = base::TimeTicks::Now();
auto callback1 = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start1, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ false));
task_environment_.FastForwardBy(kDuration);
ExpectHistogramsEmptyExcept({});
const auto start2 = base::TimeTicks::Now();
auto callback2 = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start2, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ false));
const auto end2 = start2 + kOtherDuration;
auto presentation_feedback = gfx::PresentationFeedback(
end2, end2 - start2, gfx::PresentationFeedback::Flags::kHWCompletion);
std::move(callback2).Run(presentation_feedback);
// IncompleteDuration should be logged for the first TabWasShown, and Duration
// for the second.
std::vector<std::string> expected_histograms;
base::Extend(expected_histograms, duration_histograms_);
base::Extend(expected_histograms, result_histograms_);
base::Extend(expected_histograms, incomplete_duration_histograms_);
ExpectHistogramsEmptyExcept(expected_histograms);
// Duration.
ExpectTotalSamples({incomplete_duration_histograms_}, 1);
ExpectTimeBucketCounts({incomplete_duration_histograms_}, kDuration, 1);
ExpectTotalSamples({duration_histograms_}, 1);
ExpectTimeBucketCounts({duration_histograms_}, kOtherDuration, 1);
// Result.
ExpectTotalSamples({result_histograms_}, 2);
ExpectResultBucketCounts(
{result_histograms_},
ContentToVisibleTimeReporter::TabSwitchResult::kMissedTabHide, 1);
ExpectResultBucketCounts(
{result_histograms_},
ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
}
// Time is properly recorded to histogram when we have bfcache restore event.
TEST_P(ContentToVisibleTimeReporterTest, BfcacheRestoreTimeIsRecorded) {
const auto start = base::TimeTicks::Now();
auto callback = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ false,
/* show_reason_bfcache_restore */ true));
const auto end = start + kDuration;
auto presentation_feedback = gfx::PresentationFeedback(
end, end - start, gfx::PresentationFeedback::Flags::kHWCompletion);
std::move(callback).Run(presentation_feedback);
ExpectHistogramsEmptyExcept({kBfcacheRestoreHistogram});
// Bfcache restore.
ExpectTotalSamples({kBfcacheRestoreHistogram}, 1);
ExpectTimeBucketCounts({kBfcacheRestoreHistogram}, kDuration, 1);
}
// Time is properly recorded to histogram when we have unoccluded event
// and some other events too.
TEST_P(ContentToVisibleTimeReporterTest,
TimeIsRecordedWithSavedFramesPlusBfcacheRestoreTimeIsRecorded) {
const auto start = base::TimeTicks::Now();
auto callback = tab_switch_time_recorder_.TabWasShown(
tab_state_.has_saved_frames,
blink::mojom::RecordContentToVisibleTimeRequest::New(
start, tab_state_.destination_is_loaded,
/* show_reason_tab_switching */ true,
/* show_reason_bfcache_restore */ true));
const auto end = start + kDuration;
auto presentation_feedback = gfx::PresentationFeedback(
end, end - start, gfx::PresentationFeedback::Flags::kHWCompletion);
std::move(callback).Run(presentation_feedback);
std::vector<std::string> expected_histograms{kBfcacheRestoreHistogram};
base::Extend(expected_histograms, duration_histograms_);
base::Extend(expected_histograms, result_histograms_);
ExpectHistogramsEmptyExcept(expected_histograms);
// Duration.
ExpectTotalSamples(duration_histograms_, 1);
ExpectTimeBucketCounts(duration_histograms_, kDuration, 1);
// Result.
ExpectTotalSamples(result_histograms_, 1);
ExpectResultBucketCounts(
result_histograms_,
ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
// Bfcache restore.
ExpectTotalSamples({kBfcacheRestoreHistogram}, 1);
ExpectTimeBucketCounts({kBfcacheRestoreHistogram}, kDuration, 1);
}
} // namespace blink