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