123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- // Copyright 2017 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/performance_manager/graph/page_node_impl.h"
- #include "base/containers/contains.h"
- #include "base/memory/raw_ptr.h"
- #include "components/performance_manager/graph/frame_node_impl.h"
- #include "components/performance_manager/graph/graph_impl_operations.h"
- #include "components/performance_manager/graph/process_node_impl.h"
- #include "components/performance_manager/public/freezing/freezing.h"
- #include "components/performance_manager/public/graph/page_node.h"
- #include "components/performance_manager/test_support/graph_test_harness.h"
- #include "components/performance_manager/test_support/mock_graphs.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace performance_manager {
- namespace {
- using PageNodeImplTest = GraphTestHarness;
- const std::string kHtmlMimeType = "text/html";
- const std::string kPdfMimeType = "application/pdf";
- static const freezing::FreezingVote kFreezingVote(
- freezing::FreezingVoteValue::kCannotFreeze,
- "cannot freeze");
- const PageNode* ToPublic(PageNodeImpl* page_node) {
- return page_node;
- }
- const FrameNode* ToPublic(FrameNodeImpl* frame_node) {
- return frame_node;
- }
- } // namespace
- TEST_F(PageNodeImplTest, SafeDowncast) {
- auto page = CreateNode<PageNodeImpl>();
- PageNode* node = page.get();
- EXPECT_EQ(page.get(), PageNodeImpl::FromNode(node));
- NodeBase* base = page.get();
- EXPECT_EQ(base, NodeBase::FromNode(node));
- EXPECT_EQ(static_cast<Node*>(node), base->ToNode());
- }
- using PageNodeImplDeathTest = PageNodeImplTest;
- TEST_F(PageNodeImplDeathTest, SafeDowncast) {
- auto page = CreateNode<PageNodeImpl>();
- ASSERT_DEATH_IF_SUPPORTED(FrameNodeImpl::FromNodeBase(page.get()), "");
- }
- TEST_F(PageNodeImplTest, AddFrameBasic) {
- auto process_node = CreateNode<ProcessNodeImpl>();
- auto page_node = CreateNode<PageNodeImpl>();
- auto parent_frame =
- CreateFrameNodeAutoId(process_node.get(), page_node.get());
- auto child1_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
- parent_frame.get());
- auto child2_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
- parent_frame.get());
- // Validate that all frames are tallied to the page.
- EXPECT_EQ(3u, GraphImplOperations::GetFrameNodes(page_node.get()).size());
- }
- TEST_F(PageNodeImplTest, RemoveFrame) {
- auto process_node = CreateNode<ProcessNodeImpl>();
- auto page_node = CreateNode<PageNodeImpl>();
- auto frame_node =
- CreateFrameNodeAutoId(process_node.get(), page_node.get(), nullptr);
- // Ensure correct page-frame relationship has been established.
- auto frame_nodes = GraphImplOperations::GetFrameNodes(page_node.get());
- EXPECT_EQ(1u, frame_nodes.size());
- EXPECT_TRUE(base::Contains(frame_nodes, frame_node.get()));
- EXPECT_EQ(page_node.get(), frame_node->page_node());
- frame_node.reset();
- // Parent-child relationships should no longer exist.
- EXPECT_EQ(0u, GraphImplOperations::GetFrameNodes(page_node.get()).size());
- }
- TEST_F(PageNodeImplTest, TimeSinceLastVisibilityChange) {
- MockSinglePageInSingleProcessGraph mock_graph(graph());
- mock_graph.page->SetIsVisible(true);
- EXPECT_TRUE(mock_graph.page->is_visible());
- AdvanceClock(base::Seconds(42));
- EXPECT_EQ(base::Seconds(42),
- mock_graph.page->TimeSinceLastVisibilityChange());
- mock_graph.page->SetIsVisible(false);
- AdvanceClock(base::Seconds(23));
- EXPECT_EQ(base::Seconds(23),
- mock_graph.page->TimeSinceLastVisibilityChange());
- EXPECT_FALSE(mock_graph.page->is_visible());
- }
- TEST_F(PageNodeImplTest, TimeSinceLastNavigation) {
- MockSinglePageInSingleProcessGraph mock_graph(graph());
- // Before any commit events, timedelta should be 0.
- EXPECT_TRUE(mock_graph.page->TimeSinceLastNavigation().is_zero());
- // 1st navigation.
- GURL url("http://www.example.org");
- mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
- 10u, url, kHtmlMimeType);
- EXPECT_EQ(url, mock_graph.page->main_frame_url());
- EXPECT_EQ(10u, mock_graph.page->navigation_id());
- EXPECT_EQ(kHtmlMimeType, mock_graph.page->contents_mime_type());
- AdvanceClock(base::Seconds(11));
- EXPECT_EQ(base::Seconds(11), mock_graph.page->TimeSinceLastNavigation());
- // 2nd navigation.
- url = GURL("http://www.example.org/bobcat");
- mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
- 20u, url, kHtmlMimeType);
- EXPECT_EQ(url, mock_graph.page->main_frame_url());
- EXPECT_EQ(20u, mock_graph.page->navigation_id());
- EXPECT_EQ(kHtmlMimeType, mock_graph.page->contents_mime_type());
- AdvanceClock(base::Seconds(17));
- EXPECT_EQ(base::Seconds(17), mock_graph.page->TimeSinceLastNavigation());
- // Test a same-document navigation.
- url = GURL("http://www.example.org/bobcat#fun");
- mock_graph.page->OnMainFrameNavigationCommitted(true, base::TimeTicks::Now(),
- 30u, url, kHtmlMimeType);
- EXPECT_EQ(url, mock_graph.page->main_frame_url());
- EXPECT_EQ(30u, mock_graph.page->navigation_id());
- EXPECT_EQ(kHtmlMimeType, mock_graph.page->contents_mime_type());
- AdvanceClock(base::Seconds(17));
- EXPECT_EQ(base::Seconds(17), mock_graph.page->TimeSinceLastNavigation());
- // Test a navigation to a page with a different MIME type.
- url = GURL("http://www.example.org/document.pdf");
- mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
- 40u, url, kPdfMimeType);
- EXPECT_EQ(url, mock_graph.page->main_frame_url());
- EXPECT_EQ(40u, mock_graph.page->navigation_id());
- EXPECT_EQ(kPdfMimeType, mock_graph.page->contents_mime_type());
- AdvanceClock(base::Seconds(17));
- EXPECT_EQ(base::Seconds(17), mock_graph.page->TimeSinceLastNavigation());
- }
- TEST_F(PageNodeImplTest, BrowserContextID) {
- const std::string kTestBrowserContextId =
- base::UnguessableToken::Create().ToString();
- auto page_node =
- CreateNode<PageNodeImpl>(WebContentsProxy(), kTestBrowserContextId);
- const PageNode* public_page_node = page_node.get();
- EXPECT_EQ(page_node->browser_context_id(), kTestBrowserContextId);
- EXPECT_EQ(public_page_node->GetBrowserContextID(), kTestBrowserContextId);
- }
- TEST_F(PageNodeImplTest, LoadingState) {
- MockSinglePageInSingleProcessGraph mock_graph(graph());
- auto* page_node = mock_graph.page.get();
- // This should start at kLoadingNotStarted.
- EXPECT_EQ(PageNode::LoadingState::kLoadingNotStarted,
- page_node->loading_state());
- // Set to kLoadingNotStarted and the property should stay kLoadingNotStarted.
- page_node->SetLoadingState(PageNode::LoadingState::kLoadingNotStarted);
- EXPECT_EQ(PageNode::LoadingState::kLoadingNotStarted,
- page_node->loading_state());
- // Set to kLoading and the property should switch to kLoading.
- page_node->SetLoadingState(PageNode::LoadingState::kLoading);
- EXPECT_EQ(PageNode::LoadingState::kLoading, page_node->loading_state());
- // Set to kLoading again and the property should stay kLoading.
- page_node->SetLoadingState(PageNode::LoadingState::kLoading);
- EXPECT_EQ(PageNode::LoadingState::kLoading, page_node->loading_state());
- }
- TEST_F(PageNodeImplTest, HadFormInteractions) {
- MockSinglePageInSingleProcessGraph mock_graph(graph());
- auto* page_node = mock_graph.page.get();
- // This should be initialized to false.
- EXPECT_FALSE(page_node->had_form_interaction());
- page_node->SetHadFormInteractionForTesting(true);
- EXPECT_TRUE(page_node->had_form_interaction());
- page_node->SetHadFormInteractionForTesting(false);
- EXPECT_FALSE(page_node->had_form_interaction());
- }
- TEST_F(PageNodeImplTest, GetFreezingVote) {
- MockSinglePageInSingleProcessGraph mock_graph(graph());
- auto* page_node = mock_graph.page.get();
- // This should be initialized to absl::nullopt.
- EXPECT_FALSE(page_node->freezing_vote());
- page_node->set_freezing_vote(kFreezingVote);
- ASSERT_TRUE(page_node->freezing_vote().has_value());
- EXPECT_EQ(kFreezingVote, page_node->freezing_vote().value());
- page_node->set_freezing_vote(absl::nullopt);
- EXPECT_FALSE(page_node->freezing_vote());
- }
- namespace {
- class LenientMockObserver : public PageNodeImpl::Observer {
- public:
- LenientMockObserver() {}
- ~LenientMockObserver() override {}
- MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
- MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
- // Note that opener/embedder functionality is actually tested in the
- // FrameNodeImpl and GraphImpl unittests.
- MOCK_METHOD2(OnOpenerFrameNodeChanged,
- void(const PageNode*, const FrameNode*));
- MOCK_METHOD3(OnEmbedderFrameNodeChanged,
- void(const PageNode*, const FrameNode*, EmbeddingType));
- MOCK_METHOD1(OnTypeChanged, void(const PageNode*));
- MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
- MOCK_METHOD1(OnIsAudibleChanged, void(const PageNode*));
- MOCK_METHOD2(OnLoadingStateChanged,
- void(const PageNode*, PageNode::LoadingState));
- MOCK_METHOD1(OnUkmSourceIdChanged, void(const PageNode*));
- MOCK_METHOD1(OnPageLifecycleStateChanged, void(const PageNode*));
- MOCK_METHOD1(OnPageIsHoldingWebLockChanged, void(const PageNode*));
- MOCK_METHOD1(OnPageIsHoldingIndexedDBLockChanged, void(const PageNode*));
- MOCK_METHOD1(OnMainFrameUrlChanged, void(const PageNode*));
- MOCK_METHOD1(OnMainFrameDocumentChanged, void(const PageNode*));
- MOCK_METHOD1(OnTitleUpdated, void(const PageNode*));
- MOCK_METHOD1(OnFaviconUpdated, void(const PageNode*));
- MOCK_METHOD1(OnHadFormInteractionChanged, void(const PageNode*));
- MOCK_METHOD2(OnFreezingVoteChanged,
- void(const PageNode*, absl::optional<freezing::FreezingVote>));
- MOCK_METHOD2(OnPageStateChanged, void(const PageNode*, PageNode::PageState));
- void SetNotifiedPageNode(const PageNode* page_node) {
- notified_page_node_ = page_node;
- }
- const PageNode* TakeNotifiedPageNode() {
- const PageNode* node = notified_page_node_;
- notified_page_node_ = nullptr;
- return node;
- }
- private:
- // TODO(crbug.com/1298696): Breaks components_unittests.
- raw_ptr<const PageNode, DegradeToNoOpWhenMTE> notified_page_node_ = nullptr;
- };
- using MockObserver = ::testing::StrictMock<LenientMockObserver>;
- using testing::_;
- using testing::Invoke;
- } // namespace
- TEST_F(PageNodeImplTest, ObserverWorks) {
- auto process = CreateNode<ProcessNodeImpl>();
- MockObserver obs;
- graph()->AddPageNodeObserver(&obs);
- // Create a page node and expect a matching call to "OnPageNodeAdded".
- EXPECT_CALL(obs, OnPageNodeAdded(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- auto page_node = CreateNode<PageNodeImpl>();
- const PageNode* raw_page_node = page_node.get();
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnIsVisibleChanged(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->SetIsVisible(true);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnIsAudibleChanged(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->SetIsAudible(true);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnLoadingStateChanged(_, _))
- .WillOnce(testing::WithArg<0>(
- Invoke(&obs, &MockObserver::SetNotifiedPageNode)));
- page_node->SetLoadingState(PageNode::LoadingState::kLoading);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnUkmSourceIdChanged(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->SetUkmSourceId(static_cast<ukm::SourceId>(0x1234));
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnPageLifecycleStateChanged(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->SetLifecycleStateForTesting(PageNodeImpl::LifecycleState::kFrozen);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- const GURL kTestUrl = GURL("https://foo.com/");
- int64_t navigation_id = 0x1234;
- EXPECT_CALL(obs, OnMainFrameUrlChanged(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- // Expect no OnMainFrameDocumentChanged for same-document navigation
- page_node->OnMainFrameNavigationCommitted(
- true, base::TimeTicks::Now(), ++navigation_id, kTestUrl, kHtmlMimeType);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnMainFrameDocumentChanged(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->OnMainFrameNavigationCommitted(
- false, base::TimeTicks::Now(), ++navigation_id, kTestUrl, kHtmlMimeType);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnTitleUpdated(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->OnTitleUpdated();
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnFaviconUpdated(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node->OnFaviconUpdated();
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- EXPECT_CALL(obs, OnFreezingVoteChanged(_, testing::Eq(absl::nullopt)))
- .WillOnce(testing::WithArg<0>(
- Invoke(&obs, &MockObserver::SetNotifiedPageNode)));
- page_node->set_freezing_vote(kFreezingVote);
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- // Release the page node and expect a call to "OnBeforePageNodeRemoved".
- EXPECT_CALL(obs, OnBeforePageNodeRemoved(_))
- .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
- page_node.reset();
- EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
- graph()->RemovePageNodeObserver(&obs);
- }
- TEST_F(PageNodeImplTest, PublicInterface) {
- auto page_node = CreateNode<PageNodeImpl>();
- const PageNode* public_page_node = page_node.get();
- // Simply test that the public interface impls yield the same result as their
- // private counterpart.
- EXPECT_EQ(page_node->browser_context_id(),
- public_page_node->GetBrowserContextID());
- EXPECT_EQ(page_node->is_visible(), public_page_node->IsVisible());
- EXPECT_EQ(page_node->is_audible(), public_page_node->IsAudible());
- EXPECT_EQ(page_node->loading_state(), public_page_node->GetLoadingState());
- EXPECT_EQ(page_node->ukm_source_id(), public_page_node->GetUkmSourceID());
- EXPECT_EQ(page_node->lifecycle_state(),
- public_page_node->GetLifecycleState());
- page_node->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(), 10u,
- GURL("https://foo.com"),
- kHtmlMimeType);
- EXPECT_EQ(page_node->navigation_id(), public_page_node->GetNavigationID());
- EXPECT_EQ(page_node->main_frame_url(), public_page_node->GetMainFrameUrl());
- EXPECT_EQ(page_node->contents_mime_type(),
- public_page_node->GetContentsMimeType());
- EXPECT_EQ(page_node->freezing_vote(), public_page_node->GetFreezingVote());
- }
- TEST_F(PageNodeImplTest, GetMainFrameNodes) {
- auto process = CreateNode<ProcessNodeImpl>();
- auto page = CreateNode<PageNodeImpl>();
- auto frame1 = CreateFrameNodeAutoId(process.get(), page.get());
- auto frame2 = CreateFrameNodeAutoId(process.get(), page.get());
- auto frames = ToPublic(page.get())->GetMainFrameNodes();
- EXPECT_THAT(frames, testing::UnorderedElementsAre(ToPublic(frame1.get()),
- ToPublic(frame2.get())));
- }
- TEST_F(PageNodeImplTest, VisitMainFrameNodes) {
- auto process = CreateNode<ProcessNodeImpl>();
- auto page = CreateNode<PageNodeImpl>();
- auto frame1 = CreateFrameNodeAutoId(process.get(), page.get());
- auto frame2 = CreateFrameNodeAutoId(process.get(), page.get());
- std::set<const FrameNode*> visited;
- EXPECT_TRUE(
- ToPublic(page.get())
- ->VisitMainFrameNodes(base::BindRepeating(
- [](std::set<const FrameNode*>* visited, const FrameNode* frame) {
- EXPECT_TRUE(visited->insert(frame).second);
- return true;
- },
- base::Unretained(&visited))));
- EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(frame1.get()),
- ToPublic(frame2.get())));
- // Do an aborted visit.
- visited.clear();
- EXPECT_FALSE(
- ToPublic(page.get())
- ->VisitMainFrameNodes(base::BindRepeating(
- [](std::set<const FrameNode*>* visited, const FrameNode* frame) {
- EXPECT_TRUE(visited->insert(frame).second);
- return false;
- },
- base::Unretained(&visited))));
- EXPECT_EQ(1u, visited.size());
- }
- TEST_F(PageNodeImplTest, BackForwardCache) {
- auto process = CreateNode<ProcessNodeImpl>();
- auto page = CreateNode<PageNodeImpl>();
- EXPECT_EQ(PageNode::PageState::kActive, page->page_state());
- MockObserver obs;
- graph()->AddPageNodeObserver(&obs);
- EXPECT_CALL(obs, OnPageStateChanged(_, PageNode::PageState::kActive));
- page->set_page_state(PageNode::PageState::kBackForwardCache);
- EXPECT_EQ(PageNode::PageState::kBackForwardCache, page->page_state());
- graph()->RemovePageNodeObserver(&obs);
- }
- TEST_F(PageNodeImplTest, Prerendering) {
- auto process = CreateNode<ProcessNodeImpl>();
- auto page = CreateNode<PageNodeImpl>(
- WebContentsProxy(), // wc_proxy
- std::string(), // browser_context_id
- GURL(), // url
- false, // is_visible
- false, // is_audible
- base::TimeTicks::Now(), // visibility_change_time
- PageNode::PageState::kPrerendering); // page_state
- EXPECT_EQ(PageNode::PageState::kPrerendering, page->page_state());
- MockObserver obs;
- graph()->AddPageNodeObserver(&obs);
- EXPECT_CALL(obs, OnPageStateChanged(_, PageNode::PageState::kPrerendering));
- page->set_page_state(PageNode::PageState::kActive);
- EXPECT_EQ(PageNode::PageState::kActive, page->page_state());
- graph()->RemovePageNodeObserver(&obs);
- }
- } // namespace performance_manager
|