1 // Copyright 2017 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 "components/performance_manager/graph/page_node_impl.h"
6
7 #include "base/stl_util.h"
8 #include "components/performance_manager/graph/frame_node_impl.h"
9 #include "components/performance_manager/graph/graph_impl_operations.h"
10 #include "components/performance_manager/graph/process_node_impl.h"
11 #include "components/performance_manager/public/graph/page_node.h"
12 #include "components/performance_manager/test_support/graph_test_harness.h"
13 #include "components/performance_manager/test_support/mock_graphs.h"
14 #include "testing/gmock/include/gmock/gmock.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace performance_manager {
18
19 namespace {
20
21 using PageNodeImplTest = GraphTestHarness;
22
23 const std::string kHtmlMimeType = "text/html";
24 const std::string kPdfMimeType = "application/pdf";
25
ToPublic(PageNodeImpl * page_node)26 const PageNode* ToPublic(PageNodeImpl* page_node) {
27 return page_node;
28 }
29
ToPublic(FrameNodeImpl * frame_node)30 const FrameNode* ToPublic(FrameNodeImpl* frame_node) {
31 return frame_node;
32 }
33
34 } // namespace
35
TEST_F(PageNodeImplTest,SafeDowncast)36 TEST_F(PageNodeImplTest, SafeDowncast) {
37 auto page = CreateNode<PageNodeImpl>();
38 PageNode* node = page.get();
39 EXPECT_EQ(page.get(), PageNodeImpl::FromNode(node));
40 NodeBase* base = page.get();
41 EXPECT_EQ(base, NodeBase::FromNode(node));
42 EXPECT_EQ(static_cast<Node*>(node), base->ToNode());
43 }
44
45 using PageNodeImplDeathTest = PageNodeImplTest;
46
TEST_F(PageNodeImplDeathTest,SafeDowncast)47 TEST_F(PageNodeImplDeathTest, SafeDowncast) {
48 auto page = CreateNode<PageNodeImpl>();
49 ASSERT_DEATH_IF_SUPPORTED(FrameNodeImpl::FromNodeBase(page.get()), "");
50 }
51
TEST_F(PageNodeImplTest,AddFrameBasic)52 TEST_F(PageNodeImplTest, AddFrameBasic) {
53 auto process_node = CreateNode<ProcessNodeImpl>();
54 auto page_node = CreateNode<PageNodeImpl>();
55 auto parent_frame =
56 CreateFrameNodeAutoId(process_node.get(), page_node.get());
57 auto child1_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
58 parent_frame.get(), 1);
59 auto child2_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
60 parent_frame.get(), 2);
61
62 // Validate that all frames are tallied to the page.
63 EXPECT_EQ(3u, GraphImplOperations::GetFrameNodes(page_node.get()).size());
64 }
65
TEST_F(PageNodeImplTest,RemoveFrame)66 TEST_F(PageNodeImplTest, RemoveFrame) {
67 auto process_node = CreateNode<ProcessNodeImpl>();
68 auto page_node = CreateNode<PageNodeImpl>();
69 auto frame_node =
70 CreateFrameNodeAutoId(process_node.get(), page_node.get(), nullptr, 0);
71
72 // Ensure correct page-frame relationship has been established.
73 auto frame_nodes = GraphImplOperations::GetFrameNodes(page_node.get());
74 EXPECT_EQ(1u, frame_nodes.size());
75 EXPECT_TRUE(base::Contains(frame_nodes, frame_node.get()));
76 EXPECT_EQ(page_node.get(), frame_node->page_node());
77
78 frame_node.reset();
79
80 // Parent-child relationships should no longer exist.
81 EXPECT_EQ(0u, GraphImplOperations::GetFrameNodes(page_node.get()).size());
82 }
83
TEST_F(PageNodeImplTest,TimeSinceLastVisibilityChange)84 TEST_F(PageNodeImplTest, TimeSinceLastVisibilityChange) {
85 MockSinglePageInSingleProcessGraph mock_graph(graph());
86
87 mock_graph.page->SetIsVisible(true);
88 EXPECT_TRUE(mock_graph.page->is_visible());
89 AdvanceClock(base::TimeDelta::FromSeconds(42));
90 EXPECT_EQ(base::TimeDelta::FromSeconds(42),
91 mock_graph.page->TimeSinceLastVisibilityChange());
92
93 mock_graph.page->SetIsVisible(false);
94 AdvanceClock(base::TimeDelta::FromSeconds(23));
95 EXPECT_EQ(base::TimeDelta::FromSeconds(23),
96 mock_graph.page->TimeSinceLastVisibilityChange());
97 EXPECT_FALSE(mock_graph.page->is_visible());
98 }
99
TEST_F(PageNodeImplTest,TimeSinceLastNavigation)100 TEST_F(PageNodeImplTest, TimeSinceLastNavigation) {
101 MockSinglePageInSingleProcessGraph mock_graph(graph());
102 // Before any commit events, timedelta should be 0.
103 EXPECT_TRUE(mock_graph.page->TimeSinceLastNavigation().is_zero());
104
105 // 1st navigation.
106 GURL url("http://www.example.org");
107 mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
108 10u, url, kHtmlMimeType);
109 EXPECT_EQ(url, mock_graph.page->main_frame_url());
110 EXPECT_EQ(10u, mock_graph.page->navigation_id());
111 EXPECT_EQ(kHtmlMimeType, mock_graph.page->contents_mime_type());
112 AdvanceClock(base::TimeDelta::FromSeconds(11));
113 EXPECT_EQ(base::TimeDelta::FromSeconds(11),
114 mock_graph.page->TimeSinceLastNavigation());
115
116 // 2nd navigation.
117 url = GURL("http://www.example.org/bobcat");
118 mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
119 20u, url, kHtmlMimeType);
120 EXPECT_EQ(url, mock_graph.page->main_frame_url());
121 EXPECT_EQ(20u, mock_graph.page->navigation_id());
122 EXPECT_EQ(kHtmlMimeType, mock_graph.page->contents_mime_type());
123 AdvanceClock(base::TimeDelta::FromSeconds(17));
124 EXPECT_EQ(base::TimeDelta::FromSeconds(17),
125 mock_graph.page->TimeSinceLastNavigation());
126
127 // Test a same-document navigation.
128 url = GURL("http://www.example.org/bobcat#fun");
129 mock_graph.page->OnMainFrameNavigationCommitted(true, base::TimeTicks::Now(),
130 30u, url, kHtmlMimeType);
131 EXPECT_EQ(url, mock_graph.page->main_frame_url());
132 EXPECT_EQ(30u, mock_graph.page->navigation_id());
133 EXPECT_EQ(kHtmlMimeType, mock_graph.page->contents_mime_type());
134 AdvanceClock(base::TimeDelta::FromSeconds(17));
135 EXPECT_EQ(base::TimeDelta::FromSeconds(17),
136 mock_graph.page->TimeSinceLastNavigation());
137
138 // Test a navigation to a page with a different MIME type.
139 url = GURL("http://www.example.org/document.pdf");
140 mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
141 40u, url, kPdfMimeType);
142 EXPECT_EQ(url, mock_graph.page->main_frame_url());
143 EXPECT_EQ(40u, mock_graph.page->navigation_id());
144 EXPECT_EQ(kPdfMimeType, mock_graph.page->contents_mime_type());
145 AdvanceClock(base::TimeDelta::FromSeconds(17));
146 EXPECT_EQ(base::TimeDelta::FromSeconds(17),
147 mock_graph.page->TimeSinceLastNavigation());
148 }
149
TEST_F(PageNodeImplTest,BrowserContextID)150 TEST_F(PageNodeImplTest, BrowserContextID) {
151 const std::string kTestBrowserContextId =
152 base::UnguessableToken::Create().ToString();
153 auto page_node =
154 CreateNode<PageNodeImpl>(WebContentsProxy(), kTestBrowserContextId);
155 const PageNode* public_page_node = page_node.get();
156
157 EXPECT_EQ(page_node->browser_context_id(), kTestBrowserContextId);
158 EXPECT_EQ(public_page_node->GetBrowserContextID(), kTestBrowserContextId);
159 }
160
TEST_F(PageNodeImplTest,IsLoading)161 TEST_F(PageNodeImplTest, IsLoading) {
162 MockSinglePageInSingleProcessGraph mock_graph(graph());
163 auto* page_node = mock_graph.page.get();
164
165 // This should be initialized to false.
166 EXPECT_FALSE(page_node->is_loading());
167
168 // Set to false and the property should stay false.
169 page_node->SetIsLoading(false);
170 EXPECT_FALSE(page_node->is_loading());
171
172 // Set to true and the property should read true.
173 page_node->SetIsLoading(true);
174 EXPECT_TRUE(page_node->is_loading());
175
176 // Set to false and the property should read false again.
177 page_node->SetIsLoading(false);
178 EXPECT_FALSE(page_node->is_loading());
179 }
180
TEST_F(PageNodeImplTest,HadFormInteractions)181 TEST_F(PageNodeImplTest, HadFormInteractions) {
182 MockSinglePageInSingleProcessGraph mock_graph(graph());
183 auto* page_node = mock_graph.page.get();
184
185 // This should be initialized to false.
186 EXPECT_FALSE(page_node->had_form_interaction());
187
188 page_node->SetHadFormInteractionForTesting(true);
189 EXPECT_TRUE(page_node->had_form_interaction());
190
191 page_node->SetHadFormInteractionForTesting(false);
192 EXPECT_FALSE(page_node->had_form_interaction());
193 }
194
195 namespace {
196
197 class LenientMockObserver : public PageNodeImpl::Observer {
198 public:
LenientMockObserver()199 LenientMockObserver() {}
~LenientMockObserver()200 ~LenientMockObserver() override {}
201
202 MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
203 MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
204 // Note that opener functionality is actually tested in the FrameNodeImpl
205 // and GraphImpl unittests.
206 MOCK_METHOD3(OnOpenerFrameNodeChanged,
207 void(const PageNode*, const FrameNode*, OpenedType));
208 MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
209 MOCK_METHOD1(OnIsAudibleChanged, void(const PageNode*));
210 MOCK_METHOD1(OnIsLoadingChanged, void(const PageNode*));
211 MOCK_METHOD1(OnUkmSourceIdChanged, void(const PageNode*));
212 MOCK_METHOD1(OnPageLifecycleStateChanged, void(const PageNode*));
213 MOCK_METHOD1(OnPageOriginTrialFreezePolicyChanged, void(const PageNode*));
214 MOCK_METHOD1(OnPageIsHoldingWebLockChanged, void(const PageNode*));
215 MOCK_METHOD1(OnPageIsHoldingIndexedDBLockChanged, void(const PageNode*));
216 MOCK_METHOD1(OnMainFrameUrlChanged, void(const PageNode*));
217 MOCK_METHOD1(OnMainFrameDocumentChanged, void(const PageNode*));
218 MOCK_METHOD1(OnTitleUpdated, void(const PageNode*));
219 MOCK_METHOD1(OnFaviconUpdated, void(const PageNode*));
220 MOCK_METHOD1(OnHadFormInteractionChanged, void(const PageNode*));
221
SetNotifiedPageNode(const PageNode * page_node)222 void SetNotifiedPageNode(const PageNode* page_node) {
223 notified_page_node_ = page_node;
224 }
225
TakeNotifiedPageNode()226 const PageNode* TakeNotifiedPageNode() {
227 const PageNode* node = notified_page_node_;
228 notified_page_node_ = nullptr;
229 return node;
230 }
231
232 private:
233 const PageNode* notified_page_node_ = nullptr;
234 };
235
236 using MockObserver = ::testing::StrictMock<LenientMockObserver>;
237
238 using testing::_;
239 using testing::Invoke;
240
241 } // namespace
242
TEST_F(PageNodeImplTest,ObserverWorks)243 TEST_F(PageNodeImplTest, ObserverWorks) {
244 auto process = CreateNode<ProcessNodeImpl>();
245
246 MockObserver obs;
247 graph()->AddPageNodeObserver(&obs);
248
249 // Create a page node and expect a matching call to "OnPageNodeAdded".
250 EXPECT_CALL(obs, OnPageNodeAdded(_))
251 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
252 auto page_node = CreateNode<PageNodeImpl>();
253 const PageNode* raw_page_node = page_node.get();
254 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
255
256 EXPECT_CALL(obs, OnIsVisibleChanged(_))
257 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
258 page_node->SetIsVisible(true);
259 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
260
261 EXPECT_CALL(obs, OnIsAudibleChanged(_))
262 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
263 page_node->SetIsAudible(true);
264 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
265
266 EXPECT_CALL(obs, OnIsLoadingChanged(_))
267 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
268 page_node->SetIsLoading(true);
269 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
270
271 EXPECT_CALL(obs, OnUkmSourceIdChanged(_))
272 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
273 page_node->SetUkmSourceId(static_cast<ukm::SourceId>(0x1234));
274 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
275
276 EXPECT_CALL(obs, OnPageLifecycleStateChanged(_))
277 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
278 page_node->SetLifecycleStateForTesting(PageNodeImpl::LifecycleState::kFrozen);
279 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
280
281 const GURL kTestUrl = GURL("https://foo.com/");
282 int64_t navigation_id = 0x1234;
283 EXPECT_CALL(obs, OnMainFrameUrlChanged(_))
284 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
285 // Expect no OnMainFrameDocumentChanged for same-document navigation
286 page_node->OnMainFrameNavigationCommitted(
287 true, base::TimeTicks::Now(), ++navigation_id, kTestUrl, kHtmlMimeType);
288 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
289
290 EXPECT_CALL(obs, OnMainFrameDocumentChanged(_))
291 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
292 page_node->OnMainFrameNavigationCommitted(
293 false, base::TimeTicks::Now(), ++navigation_id, kTestUrl, kHtmlMimeType);
294 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
295
296 EXPECT_CALL(obs, OnTitleUpdated(_))
297 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
298 page_node->OnTitleUpdated();
299 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
300
301 EXPECT_CALL(obs, OnFaviconUpdated(_))
302 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
303 page_node->OnFaviconUpdated();
304 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
305
306 // Release the page node and expect a call to "OnBeforePageNodeRemoved".
307 EXPECT_CALL(obs, OnBeforePageNodeRemoved(_))
308 .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
309 page_node.reset();
310 EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
311
312 graph()->RemovePageNodeObserver(&obs);
313 }
314
TEST_F(PageNodeImplTest,PublicInterface)315 TEST_F(PageNodeImplTest, PublicInterface) {
316 auto page_node = CreateNode<PageNodeImpl>();
317 const PageNode* public_page_node = page_node.get();
318
319 // Simply test that the public interface impls yield the same result as their
320 // private counterpart.
321
322 EXPECT_EQ(page_node->browser_context_id(),
323 public_page_node->GetBrowserContextID());
324 EXPECT_EQ(page_node->is_visible(), public_page_node->IsVisible());
325 EXPECT_EQ(page_node->is_audible(), public_page_node->IsAudible());
326 EXPECT_EQ(page_node->is_loading(), public_page_node->IsLoading());
327 EXPECT_EQ(page_node->ukm_source_id(), public_page_node->GetUkmSourceID());
328 EXPECT_EQ(page_node->lifecycle_state(),
329 public_page_node->GetLifecycleState());
330
331 page_node->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(), 10u,
332 GURL("https://foo.com"),
333 kHtmlMimeType);
334 EXPECT_EQ(page_node->navigation_id(), public_page_node->GetNavigationID());
335 EXPECT_EQ(page_node->main_frame_url(), public_page_node->GetMainFrameUrl());
336 EXPECT_EQ(page_node->contents_mime_type(),
337 public_page_node->GetContentsMimeType());
338 }
339
TEST_F(PageNodeImplTest,GetMainFrameNodes)340 TEST_F(PageNodeImplTest, GetMainFrameNodes) {
341 auto process = CreateNode<ProcessNodeImpl>();
342 auto page = CreateNode<PageNodeImpl>();
343 auto frame1 = CreateFrameNodeAutoId(process.get(), page.get());
344 auto frame2 = CreateFrameNodeAutoId(process.get(), page.get());
345
346 auto frames = ToPublic(page.get())->GetMainFrameNodes();
347 EXPECT_THAT(frames, testing::UnorderedElementsAre(ToPublic(frame1.get()),
348 ToPublic(frame2.get())));
349 }
350
TEST_F(PageNodeImplTest,VisitMainFrameNodes)351 TEST_F(PageNodeImplTest, VisitMainFrameNodes) {
352 auto process = CreateNode<ProcessNodeImpl>();
353 auto page = CreateNode<PageNodeImpl>();
354 auto frame1 = CreateFrameNodeAutoId(process.get(), page.get());
355 auto frame2 = CreateFrameNodeAutoId(process.get(), page.get());
356
357 std::set<const FrameNode*> visited;
358 EXPECT_TRUE(
359 ToPublic(page.get())
360 ->VisitMainFrameNodes(base::BindRepeating(
361 [](std::set<const FrameNode*>* visited, const FrameNode* frame) {
362 EXPECT_TRUE(visited->insert(frame).second);
363 return true;
364 },
365 base::Unretained(&visited))));
366 EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(frame1.get()),
367 ToPublic(frame2.get())));
368
369 // Do an aborted visit.
370 visited.clear();
371 EXPECT_FALSE(
372 ToPublic(page.get())
373 ->VisitMainFrameNodes(base::BindRepeating(
374 [](std::set<const FrameNode*>* visited, const FrameNode* frame) {
375 EXPECT_TRUE(visited->insert(frame).second);
376 return false;
377 },
378 base::Unretained(&visited))));
379 EXPECT_EQ(1u, visited.size());
380 }
381
382 } // namespace performance_manager
383