page_node_impl_unittest.cc 18 KB

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