1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/execution_context/agent_metrics_collector.h"
6 #include "base/test/simple_test_tick_clock.h"
7 #include "base/time/default_tick_clock.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
10 #include "third_party/blink/renderer/core/frame/local_frame.h"
11 #include "third_party/blink/renderer/core/frame/location.h"
12 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
13 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
14 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
15 #include "third_party/blink/renderer/platform/testing/histogram_tester.h"
16 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
17 
18 namespace blink {
19 
20 class AgentMetricsCollectorUnitTest : public SimTest {
21  public:
22   AgentMetricsCollectorUnitTest() = default;
23 
SetUp()24   void SetUp() override {
25     SimTest::SetUp();
26 
27     tick_clock_.SetNowTicks(base::TimeTicks::Now());
28 
29     // Tests turn this on but it would force all frames into a single agent.
30     // Turn it off so we get an agent per-origin.
31     WebView().GetPage()->GetSettings().SetAllowUniversalAccessFromFileURLs(
32         false);
33 
34     WebView().GetPage()->GetAgentMetricsCollector()->SetTickClockForTesting(
35         &tick_clock_);
36     WebView().MainFrameWidget()->Resize(WebSize(800, 600));
37   }
38 
TearDown()39   void TearDown() override {
40     if (!torn_down_) {
41       // Avoid UAF when tick_clock_ is destroyed.
42       auto* collector = WebView().GetPage()->GetAgentMetricsCollector();
43 
44       SimTest::TearDown();
45       torn_down_ = true;
46 
47       collector->SetTickClockForTesting(base::DefaultTickClock::GetInstance());
48     }
49   }
50 
51   bool torn_down_ = false;
52 
53   HistogramTester tester_;
54   const char* kHistogramName = "PerformanceManager.AgentsPerRendererByTime";
55 
56   base::SimpleTestTickClock tick_clock_;
57 };
58 
59 // Tests that we record samples across a navigation.
TEST_F(AgentMetricsCollectorUnitTest,AgentsPerRendererRecordOnDocumentChange)60 TEST_F(AgentMetricsCollectorUnitTest, AgentsPerRendererRecordOnDocumentChange) {
61   SimRequest request_a("https://example.com/a.html", "text/html");
62   SimRequest request_b("https://example.com/b.html", "text/html");
63 
64   // Load the first page. The histogram won't yet have any data because it
65   // records samples by time and only when documents are created/destroyed.
66   // Immediately after we load a.html, no time has passed yet.
67   LoadURL("https://example.com/a.html");
68   request_a.Complete(R"HTML(
69     <!DOCTYPE html>
70   )HTML");
71   Compositor().BeginFrame();
72 
73   tick_clock_.Advance(base::TimeDelta::FromSeconds(10));
74 
75   // Load the second page. Since 10 seconds have now elapsed, as the documents
76   // are swapped we should see 10 samples recorded in the 1 agent bucket.
77   LoadURL("https://example.com/b.html");
78   request_b.Complete(R"HTML(
79     <!DOCTYPE html>
80   )HTML");
81   Compositor().BeginFrame();
82 
83   tester_.ExpectUniqueSample(kHistogramName, 1, 10);
84 }
85 
86 // Test that we correctly record the case where a second agent is added to the
87 // page.
TEST_F(AgentMetricsCollectorUnitTest,MultipleAgents)88 TEST_F(AgentMetricsCollectorUnitTest, MultipleAgents) {
89   SimRequest request_a("https://foo.com/a.html", "text/html");
90   SimRequest request_b("https://bar.com/b.html", "text/html");
91 
92   // Load the first page. The histogram won't yet have any data because it
93   // records samples by time and only when documents are created/destroyed.
94   // Immediately after we load a.html, no time has passed yet.
95   LoadURL("https://foo.com/a.html");
96   request_a.Complete(R"HTML(
97     <!DOCTYPE html>
98     <iframe src="about:blank"></iframe>
99   )HTML");
100   Compositor().BeginFrame();
101 
102   tick_clock_.Advance(base::TimeDelta::FromSeconds(10));
103 
104   // Navigate to a cross-origin page, this should create a second agent.
105   frame_test_helpers::LoadFrameDontWait(
106       MainFrame().FirstChild()->ToWebLocalFrame(),
107       KURL("https://bar.com/b.html"));
108   request_b.Complete(R"HTML(
109     <!DOCTYPE html>
110   )HTML");
111   tester_.ExpectBucketCount(kHistogramName, 1, 10);
112 
113   tick_clock_.Advance(base::TimeDelta::FromSeconds(20));
114 
115   // Simulate closing the page. This should cause us to report the metrics.
116   TearDown();
117 
118   // The final 20 seconds had 2 agents.
119   tester_.ExpectBucketCount(kHistogramName, 1, 10);
120   tester_.ExpectBucketCount(kHistogramName, 2, 20);
121 }
122 
123 // Ensure that multiple Pages in the same Agent are reported as only one agent.
TEST_F(AgentMetricsCollectorUnitTest,WindowOpenSameAgents)124 TEST_F(AgentMetricsCollectorUnitTest, WindowOpenSameAgents) {
125   SimRequest request_a("https://example.com/a.html", "text/html");
126   SimRequest request_b("https://example.com/b.html", "text/html");
127 
128   LoadURL("https://example.com/a.html");
129   request_a.Complete(R"HTML(
130     <!DOCTYPE html>
131     <script>
132       window.open('https://example.com/b.html');
133     </script>
134   )HTML");
135   request_b.Complete(R"HTML(
136     <!DOCTYPE html>
137   )HTML");
138   Compositor().BeginFrame();
139 
140   ASSERT_EQ(2u, Page::OrdinaryPages().size());
141 
142   tick_clock_.Advance(base::TimeDelta::FromSeconds(10));
143 
144   // Simulate closing the page. This should cause us to report the metrics.
145   TearDown();
146 
147   // Both documents should end up in the same Agent, despite having separate
148   // WebViews/Page.
149   tester_.ExpectUniqueSample(kHistogramName, 1, 10);
150 }
151 
152 // Ensure that multiple Pages in different Agents are reported as multiple
153 // agents.
TEST_F(AgentMetricsCollectorUnitTest,WindowOpenDifferentAgents)154 TEST_F(AgentMetricsCollectorUnitTest, WindowOpenDifferentAgents) {
155   SimRequest request_a("https://example.com/a.html", "text/html");
156   SimRequest request_b("https://different.com/a.html", "text/html");
157 
158   LoadURL("https://example.com/a.html");
159   request_a.Complete(R"HTML(
160     <!DOCTYPE html>
161     <script>
162       window.open('https://different.com/a.html');
163     </script>
164   )HTML");
165   request_b.Complete(R"HTML(
166     <!DOCTYPE html>
167   )HTML");
168   Compositor().BeginFrame();
169 
170   ASSERT_EQ(2u, Page::OrdinaryPages().size());
171 
172   tick_clock_.Advance(base::TimeDelta::FromSeconds(10));
173 
174   // Simulate closing the page. This should cause us to report the metrics.
175   TearDown();
176 
177   // Each document should have its own Agent.
178   tester_.ExpectUniqueSample(kHistogramName, 2, 10);
179 }
180 
181 }  // namespace blink
182