blob: 6a49e1c0daa4795c4f6cc7656ff66f82afead846 [file] [log] [blame]
// 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 "chrome/browser/performance_manager/graph/page_node_impl.h"
#include "base/stl_util.h"
#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
#include "chrome/browser/performance_manager/graph/graph_impl_operations.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include "chrome/browser/performance_manager/public/graph/page_node.h"
#include "chrome/browser/performance_manager/test_support/graph_test_harness.h"
#include "chrome/browser/performance_manager/test_support/mock_graphs.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
namespace {
using PageNodeImplTest = GraphTestHarness;
} // 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(), 1);
auto child2_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
parent_frame.get(), 2);
// 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, 0);
// 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, CalculatePageCPUUsageForSinglePageInSingleProcess) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
mock_graph.process->SetCPUUsage(40);
EXPECT_EQ(40, mock_graph.page->GetCPUUsage());
}
TEST_F(PageNodeImplTest, CalculatePageCPUUsageForMultiplePagesInSingleProcess) {
MockMultiplePagesInSingleProcessGraph mock_graph(graph());
mock_graph.process->SetCPUUsage(40);
EXPECT_EQ(20, mock_graph.page->GetCPUUsage());
EXPECT_EQ(20, mock_graph.other_page->GetCPUUsage());
}
TEST_F(PageNodeImplTest,
CalculatePageCPUUsageForSinglePageWithMultipleProcesses) {
MockSinglePageWithMultipleProcessesGraph mock_graph(graph());
mock_graph.process->SetCPUUsage(40);
mock_graph.other_process->SetCPUUsage(30);
EXPECT_EQ(70, mock_graph.page->GetCPUUsage());
}
TEST_F(PageNodeImplTest,
CalculatePageCPUUsageForMultiplePagesWithMultipleProcesses) {
MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
mock_graph.process->SetCPUUsage(40);
mock_graph.other_process->SetCPUUsage(30);
EXPECT_EQ(20, mock_graph.page->GetCPUUsage());
EXPECT_EQ(50, mock_graph.other_page->GetCPUUsage());
}
TEST_F(PageNodeImplTest, TimeSinceLastVisibilityChange) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
mock_graph.page->SetIsVisible(true);
EXPECT_TRUE(mock_graph.page->is_visible());
AdvanceClock(base::TimeDelta::FromSeconds(42));
EXPECT_EQ(base::TimeDelta::FromSeconds(42),
mock_graph.page->TimeSinceLastVisibilityChange());
mock_graph.page->SetIsVisible(false);
AdvanceClock(base::TimeDelta::FromSeconds(23));
EXPECT_EQ(base::TimeDelta::FromSeconds(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);
EXPECT_EQ(url, mock_graph.page->main_frame_url());
EXPECT_EQ(10u, mock_graph.page->navigation_id());
AdvanceClock(base::TimeDelta::FromSeconds(11));
EXPECT_EQ(base::TimeDelta::FromSeconds(11),
mock_graph.page->TimeSinceLastNavigation());
// 2nd navigation.
url = GURL("http://www.example.org/bobcat");
mock_graph.page->OnMainFrameNavigationCommitted(false, base::TimeTicks::Now(),
20u, url);
EXPECT_EQ(url, mock_graph.page->main_frame_url());
EXPECT_EQ(20u, mock_graph.page->navigation_id());
AdvanceClock(base::TimeDelta::FromSeconds(17));
EXPECT_EQ(base::TimeDelta::FromSeconds(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);
EXPECT_EQ(url, mock_graph.page->main_frame_url());
EXPECT_EQ(30u, mock_graph.page->navigation_id());
AdvanceClock(base::TimeDelta::FromSeconds(17));
EXPECT_EQ(base::TimeDelta::FromSeconds(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, IsLoading) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
auto* page_node = mock_graph.page.get();
// This should be initialized to false.
EXPECT_FALSE(page_node->is_loading());
// Set to false and the property should stay false.
page_node->SetIsLoading(false);
EXPECT_FALSE(page_node->is_loading());
// Set to true and the property should read true.
page_node->SetIsLoading(true);
EXPECT_TRUE(page_node->is_loading());
// Set to false and the property should read false again.
page_node->SetIsLoading(false);
EXPECT_FALSE(page_node->is_loading());
}
namespace {
class LenientMockObserver : public PageNodeImpl::Observer {
public:
LenientMockObserver() {}
~LenientMockObserver() override {}
MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
MOCK_METHOD1(OnIsAudibleChanged, void(const PageNode*));
MOCK_METHOD1(OnIsLoadingChanged, void(const PageNode*));
MOCK_METHOD1(OnUkmSourceIdChanged, void(const PageNode*));
MOCK_METHOD1(OnPageLifecycleStateChanged, void(const PageNode*));
MOCK_METHOD1(OnPageOriginTrialFreezePolicyChanged, void(const PageNode*));
MOCK_METHOD1(OnMainFrameUrlChanged, void(const PageNode*));
MOCK_METHOD1(OnPageAlmostIdleChanged, void(const PageNode*));
MOCK_METHOD1(OnMainFrameDocumentChanged, void(const PageNode*));
MOCK_METHOD1(OnTitleUpdated, void(const PageNode*));
MOCK_METHOD1(OnFaviconUpdated, void(const PageNode*));
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:
const PageNode* 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, OnIsLoadingChanged(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
page_node->SetIsLoading(true);
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());
EXPECT_CALL(obs, OnPageAlmostIdleChanged(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
page_node->SetPageAlmostIdleForTesting(true);
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);
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);
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());
// 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->page_almost_idle(),
public_page_node->IsPageAlmostIdle());
EXPECT_EQ(page_node->is_visible(), public_page_node->IsVisible());
EXPECT_EQ(page_node->is_audible(), public_page_node->IsAudible());
EXPECT_EQ(page_node->is_loading(), public_page_node->IsLoading());
EXPECT_EQ(page_node->ukm_source_id(), public_page_node->GetUkmSourceID());
EXPECT_EQ(page_node->lifecycle_state(),
public_page_node->GetLifecycleState());
EXPECT_EQ(page_node->navigation_id(), public_page_node->GetNavigationID());
EXPECT_EQ(page_node->main_frame_url(), public_page_node->GetMainFrameUrl());
}
} // namespace performance_manager