blob: c3896682a004a2b83531efdfcdc7d7ced2647686 [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 "content/browser/renderer_host/recently_destroyed_hosts.h"
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "content/browser/browsing_instance.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/isolation_context.h"
#include "content/browser/origin_agent_cluster_isolation_state.h"
#include "content/browser/process_lock.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_exposed_isolation_info.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/storage_partition_test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
class RecentlyDestroyedHostsTest : public testing::Test {
protected:
RecentlyDestroyedHostsTest()
: task_environment_(BrowserTaskEnvironment::TimeSource::MOCK_TIME),
instance_(RecentlyDestroyedHosts::GetInstance(&browser_context_)) {}
void AddReuseInterval(base::TimeDelta interval) {
instance_->AddReuseInterval(interval);
task_environment_.FastForwardBy(base::Seconds(1));
}
std::vector<base::TimeDelta> GetReuseIntervals() {
std::vector<base::TimeDelta> intervals;
for (auto& reuse_interval : instance_->reuse_intervals_) {
intervals.push_back(reuse_interval.interval);
}
return intervals;
}
BrowserTaskEnvironment task_environment_;
TestBrowserContext browser_context_;
raw_ptr<RecentlyDestroyedHosts> instance_;
};
TEST_F(RecentlyDestroyedHostsTest, RecordSubframeMetric) {
const IsolationContext isolation_context(
BrowsingInstanceId(1), &browser_context_,
/*is_guest=*/false,
/*is_fenced=*/false,
OriginAgentClusterIsolationState::CreateForDefaultIsolation(
&browser_context_));
const ProcessLock process_lock = ProcessLock::Create(
isolation_context,
UrlInfo::CreateForTesting(GURL("https://www.google.com"),
CreateStoragePartitionConfigForTesting()));
constexpr char kTimingHistogram[] =
"SiteIsolation.ReusePendingOrCommittedSite."
"TimeSinceReusableProcessDestroyed.Subframe";
constexpr char kFoundHistogram[] =
"SiteIsolation.MissedReuseOpportunity.Found.Subframe";
base::HistogramTester histogram_tester;
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kSubframe, base::TimeTicks::Now(),
process_lock, &browser_context_);
// No entry exists, so "Found" should be false, and timing should be empty.
histogram_tester.ExpectUniqueSample(kFoundHistogram, false, 1);
histogram_tester.ExpectTotalCount(kTimingHistogram, 0);
// Add a RenderProcessHost for |process_lock| to RecentlyDestroyedHosts.
MockRenderProcessHost host(&browser_context_, /*is_for_guests_only=*/false);
host.SetProcessLock(isolation_context, process_lock);
RecentlyDestroyedHosts::Add(
&host, /*time_spent_running_unload_handlers=*/base::TimeDelta(),
&browser_context_);
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kSubframe, base::TimeTicks::Now(),
process_lock, &browser_context_);
// After 2 actions, we check the cumulative state:
histogram_tester.ExpectBucketCount(kFoundHistogram, false, 1);
// The "true" bucket for the hit should now have 1 sample.
histogram_tester.ExpectBucketCount(kFoundHistogram, true, 1);
// The total count for the boolean histogram is now 2.
histogram_tester.ExpectTotalCount(kFoundHistogram, 2);
// The timing histogram should have its first and only sample.
histogram_tester.ExpectUniqueTimeSample(kTimingHistogram, base::TimeDelta(),
1);
// Advance time almost to the expiration point, then re-add the host to
// refresh its timestamp.
task_environment_.FastForwardBy(
RecentlyDestroyedHosts::kSubframeStorageTimeout - base::Seconds(1));
RecentlyDestroyedHosts::Add(
&host, /*time_spent_running_unload_handlers=*/base::TimeDelta(),
&browser_context_);
// Advance time a bit more and check that the host is still found.
constexpr base::TimeDelta kReuseInterval = base::Seconds(5);
task_environment_.FastForwardBy(kReuseInterval);
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kSubframe, base::TimeTicks::Now(),
process_lock, &browser_context_);
// After 3 actions, we check the cumulative state:
histogram_tester.ExpectBucketCount(kFoundHistogram, false, 1);
// The "true" bucket for the hit should now have 2 sample.
histogram_tester.ExpectBucketCount(kFoundHistogram, true, 2);
// The total count for the boolean histogram is now 3.
histogram_tester.ExpectTotalCount(kFoundHistogram, 3);
// The timing histogram should a new entry for the new interval.
histogram_tester.ExpectTimeBucketCount(kTimingHistogram, kReuseInterval, 1);
// The total number of timing samples is now 2.
histogram_tester.ExpectTotalCount(kTimingHistogram, 2);
// After the storage timeout passes, host should no longer be present.
task_environment_.FastForwardBy(
RecentlyDestroyedHosts::kSubframeStorageTimeout + base::Seconds(1));
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kSubframe, base::TimeTicks::Now(),
process_lock, &browser_context_);
// After 4 actions, we check the final cumulative state:
// The "false" bucket now has 2 samples: the initial miss and this expiration
// miss.
histogram_tester.ExpectBucketCount(kFoundHistogram, false, 2);
// The "true" bucket for the hit should now have 2 sample.
histogram_tester.ExpectBucketCount(kFoundHistogram, true, 2);
// The total count for the boolean histogram is now 4.
histogram_tester.ExpectTotalCount(kFoundHistogram, 4);
// The total number of timing samples is still 2, as the miss was not
// recorded.
histogram_tester.ExpectTotalCount(kTimingHistogram, 2);
}
TEST_F(RecentlyDestroyedHostsTest, AddReuseInterval) {
const base::TimeDelta t1 = base::Seconds(4);
const base::TimeDelta t2 = base::Seconds(5);
const base::TimeDelta t3 = base::Seconds(6.7);
const base::TimeDelta t4 = base::Seconds(8.2);
const base::TimeDelta t5 = base::Seconds(10);
const base::TimeDelta t6 = base::Seconds(11);
AddReuseInterval(t2);
EXPECT_THAT(GetReuseIntervals(), testing::ElementsAre(t2));
// The list of reuse intervals should be sorted from smallest to largest.
AddReuseInterval(t1);
AddReuseInterval(t1);
AddReuseInterval(t3);
AddReuseInterval(t6);
EXPECT_THAT(GetReuseIntervals(), testing::ElementsAre(t1, t1, t2, t3, t6));
// When the list of reuse intervals is at its maximum size and a new entry is
// added, it should replace the oldest entry.
AddReuseInterval(t5);
EXPECT_THAT(GetReuseIntervals(), testing::ElementsAre(t1, t1, t3, t5, t6));
AddReuseInterval(t4);
EXPECT_THAT(GetReuseIntervals(), testing::ElementsAre(t1, t3, t4, t5, t6));
}
TEST_F(RecentlyDestroyedHostsTest, RecordMainFrameMetric) {
const IsolationContext isolation_context(
BrowsingInstanceId(1), &browser_context_,
/*is_guest=*/false,
/*is_fenced=*/false,
OriginAgentClusterIsolationState::CreateForDefaultIsolation(
&browser_context_));
const ProcessLock process_lock = ProcessLock::Create(
isolation_context,
UrlInfo::CreateForTesting(GURL("https://www.google.com"),
CreateStoragePartitionConfigForTesting()));
constexpr char kFoundHistogram[] =
"SiteIsolation.MissedReuseOpportunity.Found.MainFrame";
constexpr char kTimingHistogram[] =
"SiteIsolation.ReusePendingOrCommittedSite."
"TimeSinceReusableProcessDestroyed.MainFrame";
base::HistogramTester histogram_tester;
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kMainFrame, base::TimeTicks::Now(),
process_lock, &browser_context_);
// No entry exists, so "Found" should be false, and timing should be empty.
histogram_tester.ExpectUniqueSample(kFoundHistogram, false, 1);
histogram_tester.ExpectTotalCount(kTimingHistogram, 0);
// Add a host, advance time, and check for a hit.
MockRenderProcessHost host(&browser_context_);
host.SetProcessLock(isolation_context, process_lock);
RecentlyDestroyedHosts::Add(
&host, /*time_spent_running_unload_handlers=*/base::TimeDelta(),
&browser_context_);
constexpr base::TimeDelta kReuseInterval = base::Minutes(10);
task_environment_.FastForwardBy(kReuseInterval);
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kMainFrame, base::TimeTicks::Now(),
process_lock, &browser_context_);
// After 2 actions, we check the cumulative state:
// The "false" bucket for the initial miss should still have 1 sample.
histogram_tester.ExpectBucketCount(kFoundHistogram, false, 1);
// The "true" bucket for the hit should now have 1 sample.
histogram_tester.ExpectBucketCount(kFoundHistogram, true, 1);
// The total count for the boolean histogram is now 2.
histogram_tester.ExpectTotalCount(kFoundHistogram, 2);
// The timing histogram should have its first and only sample.
histogram_tester.ExpectUniqueTimeSample(kTimingHistogram, kReuseInterval, 1);
// Advance time past the 2-hour timeout and check that we get a "miss".
task_environment_.FastForwardBy(
RecentlyDestroyedHosts::kMainFrameStorageTimeout + base::Seconds(1));
RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
RecentlyDestroyedHosts::Context::kMainFrame, base::TimeTicks::Now(),
process_lock, &browser_context_);
// After 3 actions, we check the final cumulative state.
// The "false" bucket now has 2 samples: the initial miss and this expiration
// miss.
histogram_tester.ExpectBucketCount(kFoundHistogram, false, 2);
// The "true" bucket still has 1 sample from the previous step.
histogram_tester.ExpectBucketCount(kFoundHistogram, true, 1);
// The total count for the boolean histogram is now 3.
histogram_tester.ExpectTotalCount(kFoundHistogram, 3);
// The total number of timing samples is still 1, as the miss was not
// recorded.
histogram_tester.ExpectTotalCount(kTimingHistogram, 1);
}
} // namespace content