| // 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 |