blob: cb971e443ebcb907fcd65d82ad6827375b52f601 [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 "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.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"
namespace performance_manager {
namespace {
using FrameNodeImplTest = GraphTestHarness;
const FrameNode* ToPublic(FrameNodeImpl* frame_node) {
return frame_node;
}
} // namespace
TEST_F(FrameNodeImplTest, SafeDowncast) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame = CreateFrameNodeAutoId(process.get(), page.get());
FrameNode* node = frame.get();
EXPECT_EQ(frame.get(), FrameNodeImpl::FromNode(node));
NodeBase* base = frame.get();
EXPECT_EQ(base, NodeBase::FromNode(node));
EXPECT_EQ(static_cast<Node*>(node), base->ToNode());
}
using FrameNodeImplDeathTest = FrameNodeImplTest;
TEST_F(FrameNodeImplDeathTest, SafeDowncast) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame = CreateFrameNodeAutoId(process.get(), page.get());
ASSERT_DEATH_IF_SUPPORTED(PageNodeImpl::FromNodeBase(frame.get()), "");
}
TEST_F(FrameNodeImplTest, AddFrameHierarchyBasic) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto parent_node = CreateFrameNodeAutoId(process.get(), page.get());
auto child2_node =
CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get(), 1);
auto child3_node =
CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get(), 2);
EXPECT_EQ(nullptr, parent_node->parent_frame_node());
EXPECT_EQ(2u, parent_node->child_frame_nodes().size());
EXPECT_EQ(parent_node.get(), child2_node->parent_frame_node());
EXPECT_EQ(parent_node.get(), child3_node->parent_frame_node());
}
TEST_F(FrameNodeImplTest, GetFrameNodeById) {
auto process_a =
CreateNode<ProcessNodeImpl>(content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(42));
auto process_b =
CreateNode<ProcessNodeImpl>(content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(43));
auto page = CreateNode<PageNodeImpl>();
auto frame_a1 = CreateFrameNodeAutoId(process_a.get(), page.get());
auto frame_a2 = CreateFrameNodeAutoId(process_a.get(), page.get());
auto frame_b1 = CreateFrameNodeAutoId(process_b.get(), page.get());
EXPECT_EQ(graph()->GetFrameNodeById(process_a->GetRenderProcessId(),
frame_a1->render_frame_id()),
frame_a1.get());
EXPECT_EQ(graph()->GetFrameNodeById(process_a->GetRenderProcessId(),
frame_a2->render_frame_id()),
frame_a2.get());
EXPECT_EQ(graph()->GetFrameNodeById(process_b->GetRenderProcessId(),
frame_b1->render_frame_id()),
frame_b1.get());
}
TEST_F(FrameNodeImplTest, NavigationCommitted_SameDocument) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
EXPECT_TRUE(frame_node->url().is_empty());
const GURL url("http://www.foo.com/");
frame_node->OnNavigationCommitted(url, /* same_document */ true);
EXPECT_EQ(url, frame_node->url());
}
TEST_F(FrameNodeImplTest, NavigationCommitted_DifferentDocument) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
EXPECT_TRUE(frame_node->url().is_empty());
const GURL url("http://www.foo.com/");
frame_node->OnNavigationCommitted(url, /* same_document */ false);
EXPECT_EQ(url, frame_node->url());
}
TEST_F(FrameNodeImplTest, RemoveChildFrame) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto parent_frame_node = CreateFrameNodeAutoId(process.get(), page.get());
auto child_frame_node = CreateFrameNodeAutoId(process.get(), page.get(),
parent_frame_node.get(), 1);
// Ensure correct Parent-child relationships have been established.
EXPECT_EQ(1u, parent_frame_node->child_frame_nodes().size());
EXPECT_TRUE(!parent_frame_node->parent_frame_node());
EXPECT_EQ(0u, child_frame_node->child_frame_nodes().size());
EXPECT_EQ(parent_frame_node.get(), child_frame_node->parent_frame_node());
child_frame_node.reset();
// Parent-child relationships should no longer exist.
EXPECT_EQ(0u, parent_frame_node->child_frame_nodes().size());
EXPECT_TRUE(!parent_frame_node->parent_frame_node());
}
namespace {
class LenientMockObserver : public FrameNodeImpl::Observer {
public:
LenientMockObserver() = default;
~LenientMockObserver() override = default;
MOCK_METHOD1(OnFrameNodeAdded, void(const FrameNode*));
MOCK_METHOD1(OnBeforeFrameNodeRemoved, void(const FrameNode*));
MOCK_METHOD1(OnIsCurrentChanged, void(const FrameNode*));
MOCK_METHOD1(OnNetworkAlmostIdleChanged, void(const FrameNode*));
MOCK_METHOD1(OnFrameLifecycleStateChanged, void(const FrameNode*));
MOCK_METHOD2(OnOriginTrialFreezePolicyChanged,
void(const FrameNode*, const mojom::InterventionPolicy&));
MOCK_METHOD2(OnURLChanged, void(const FrameNode*, const GURL&));
MOCK_METHOD1(OnIsAdFrameChanged, void(const FrameNode*));
MOCK_METHOD1(OnFrameIsHoldingWebLockChanged, void(const FrameNode*));
MOCK_METHOD1(OnFrameIsHoldingIndexedDBLockChanged, void(const FrameNode*));
MOCK_METHOD2(OnPriorityAndReasonChanged,
void(const FrameNode*, const PriorityAndReason& previous_value));
MOCK_METHOD1(OnHadFormInteractionChanged, void(const FrameNode*));
MOCK_METHOD1(OnNonPersistentNotificationCreated, void(const FrameNode*));
MOCK_METHOD2(OnFirstContentfulPaint, void(const FrameNode*, base::TimeDelta));
void SetCreatedFrameNode(const FrameNode* frame_node) {
created_frame_node_ = frame_node;
}
const FrameNode* created_frame_node() { return created_frame_node_; }
private:
const FrameNode* created_frame_node_ = nullptr;
};
using MockObserver = ::testing::StrictMock<LenientMockObserver>;
using testing::_;
using testing::Invoke;
} // namespace
TEST_F(FrameNodeImplTest, ObserverWorks) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
// Create a frame node and expect a matching call to "OnFrameNodeAdded".
EXPECT_CALL(obs, OnFrameNodeAdded(_))
.WillOnce(Invoke(&obs, &MockObserver::SetCreatedFrameNode));
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
testing::Mock::VerifyAndClear(&obs);
const FrameNode* raw_frame_node = frame_node.get();
EXPECT_EQ(raw_frame_node, obs.created_frame_node());
// Invoke "SetIsCurrent" and expect a "OnIsCurrentChanged" callback.
EXPECT_CALL(obs, OnIsCurrentChanged(raw_frame_node));
frame_node->SetIsCurrent(true);
testing::Mock::VerifyAndClear(&obs);
// Invoke "SetNetworkAlmostIdle" and expect an "OnNetworkAlmostIdleChanged"
// callback.
EXPECT_CALL(obs, OnNetworkAlmostIdleChanged(raw_frame_node));
frame_node->SetNetworkAlmostIdle();
testing::Mock::VerifyAndClear(&obs);
// Invoke "SetLifecycleState" and expect an "OnFrameLifecycleStateChanged"
// callback.
EXPECT_CALL(obs, OnFrameLifecycleStateChanged(raw_frame_node));
frame_node->SetLifecycleState(mojom::LifecycleState::kFrozen);
testing::Mock::VerifyAndClear(&obs);
// Invoke "OnNonPersistentNotificationCreated" and expect an
// "OnNonPersistentNotificationCreated" callback.
EXPECT_CALL(obs, OnNonPersistentNotificationCreated(raw_frame_node));
frame_node->OnNonPersistentNotificationCreated();
testing::Mock::VerifyAndClear(&obs);
// Invoke "OnNavigationCommitted" and expect an "OnURLChanged" callback.
EXPECT_CALL(obs, OnURLChanged(raw_frame_node, _));
frame_node->OnNavigationCommitted(GURL("https://foo.com/"), true);
testing::Mock::VerifyAndClear(&obs);
// Release the frame node and expect a call to "OnBeforeFrameNodeRemoved".
EXPECT_CALL(obs, OnBeforeFrameNodeRemoved(raw_frame_node));
frame_node.reset();
testing::Mock::VerifyAndClear(&obs);
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, IsAdFrame) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_FALSE(frame_node->is_ad_frame());
EXPECT_CALL(obs, OnIsAdFrameChanged(frame_node.get()));
frame_node->SetIsAdFrame();
EXPECT_TRUE(frame_node->is_ad_frame());
frame_node->SetIsAdFrame();
EXPECT_TRUE(frame_node->is_ad_frame());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, IsHoldingWebLock) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_FALSE(frame_node->is_holding_weblock());
EXPECT_CALL(obs, OnFrameIsHoldingWebLockChanged(frame_node.get()));
frame_node->SetIsHoldingWebLock(true);
EXPECT_TRUE(frame_node->is_holding_weblock());
EXPECT_CALL(obs, OnFrameIsHoldingWebLockChanged(frame_node.get()));
frame_node->SetIsHoldingWebLock(false);
EXPECT_FALSE(frame_node->is_holding_weblock());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, IsHoldingIndexedDBLock) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_CALL(obs, OnFrameIsHoldingIndexedDBLockChanged(frame_node.get()));
frame_node->SetIsHoldingIndexedDBLock(true);
EXPECT_TRUE(frame_node->is_holding_indexeddb_lock());
EXPECT_CALL(obs, OnFrameIsHoldingIndexedDBLockChanged(frame_node.get()));
frame_node->SetIsHoldingIndexedDBLock(false);
EXPECT_FALSE(frame_node->is_holding_indexeddb_lock());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, Priority) {
using PriorityAndReason = frame_priority::PriorityAndReason;
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
// By default the priority should be "lowest".
EXPECT_EQ(base::TaskPriority::LOWEST,
frame_node->priority_and_reason().priority());
// Changed the reason only.
static const char kDummyReason[] = "this is a reason!";
EXPECT_CALL(obs,
OnPriorityAndReasonChanged(
frame_node.get(),
PriorityAndReason(base::TaskPriority::LOWEST,
FrameNodeImpl::kDefaultPriorityReason)));
frame_node->SetPriorityAndReason(
PriorityAndReason(base::TaskPriority::LOWEST, kDummyReason));
EXPECT_EQ(PriorityAndReason(base::TaskPriority::LOWEST, kDummyReason),
frame_node->priority_and_reason());
testing::Mock::VerifyAndClear(&obs);
// Change the priority only.
EXPECT_CALL(obs,
OnPriorityAndReasonChanged(
frame_node.get(),
PriorityAndReason(base::TaskPriority::LOWEST, kDummyReason)));
frame_node->SetPriorityAndReason(
PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason));
EXPECT_EQ(PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason),
frame_node->priority_and_reason());
testing::Mock::VerifyAndClear(&obs);
// Change neither.
frame_node->SetPriorityAndReason(
PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason));
EXPECT_EQ(PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason),
frame_node->priority_and_reason());
testing::Mock::VerifyAndClear(&obs);
// Change both the priority and the reason.
EXPECT_CALL(
obs, OnPriorityAndReasonChanged(
frame_node.get(),
PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason)));
frame_node->SetPriorityAndReason(
PriorityAndReason(base::TaskPriority::LOWEST, nullptr));
EXPECT_EQ(PriorityAndReason(base::TaskPriority::LOWEST, nullptr),
frame_node->priority_and_reason());
testing::Mock::VerifyAndClear(&obs);
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, FormInteractions) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_CALL(obs, OnHadFormInteractionChanged(frame_node.get()));
frame_node->SetHadFormInteraction();
EXPECT_TRUE(frame_node->had_form_interaction());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, FirstContentfulPaint) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
base::TimeDelta fcp = base::TimeDelta::FromMilliseconds(1364);
EXPECT_CALL(obs, OnFirstContentfulPaint(frame_node.get(), fcp));
frame_node->OnFirstContentfulPaint(fcp);
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, PublicInterface) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
const FrameNode* public_frame_node = frame_node.get();
// Simply test that the public interface impls yield the same result as their
// private counterpart.
EXPECT_EQ(static_cast<const FrameNode*>(frame_node->parent_frame_node()),
public_frame_node->GetParentFrameNode());
EXPECT_EQ(static_cast<const PageNode*>(frame_node->page_node()),
public_frame_node->GetPageNode());
EXPECT_EQ(static_cast<const ProcessNode*>(frame_node->process_node()),
public_frame_node->GetProcessNode());
EXPECT_EQ(frame_node->frame_tree_node_id(),
public_frame_node->GetFrameTreeNodeId());
EXPECT_EQ(frame_node->dev_tools_token(),
public_frame_node->GetDevToolsToken());
EXPECT_EQ(frame_node->browsing_instance_id(),
public_frame_node->GetBrowsingInstanceId());
EXPECT_EQ(frame_node->site_instance_id(),
public_frame_node->GetSiteInstanceId());
auto child_frame_nodes = public_frame_node->GetChildFrameNodes();
for (auto* child_frame_node : frame_node->child_frame_nodes()) {
const FrameNode* child = child_frame_node;
EXPECT_TRUE(base::Contains(child_frame_nodes, child));
}
EXPECT_EQ(child_frame_nodes.size(), frame_node->child_frame_nodes().size());
EXPECT_EQ(frame_node->lifecycle_state(),
public_frame_node->GetLifecycleState());
EXPECT_EQ(frame_node->has_nonempty_beforeunload(),
public_frame_node->HasNonemptyBeforeUnload());
EXPECT_EQ(frame_node->url(), public_frame_node->GetURL());
EXPECT_EQ(frame_node->is_current(), public_frame_node->IsCurrent());
EXPECT_EQ(frame_node->network_almost_idle(),
public_frame_node->GetNetworkAlmostIdle());
EXPECT_EQ(frame_node->is_ad_frame(), public_frame_node->IsAdFrame());
EXPECT_EQ(frame_node->is_holding_weblock(),
public_frame_node->IsHoldingWebLock());
EXPECT_EQ(frame_node->is_holding_indexeddb_lock(),
public_frame_node->IsHoldingIndexedDBLock());
EXPECT_EQ(frame_node->had_form_interaction(),
public_frame_node->HadFormInteraction());
}
TEST_F(FrameNodeImplTest, VisitChildFrameNodes) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame1 = CreateFrameNodeAutoId(process.get(), page.get());
auto frame2 = CreateFrameNodeAutoId(process.get(), page.get(), frame1.get());
auto frame3 = CreateFrameNodeAutoId(process.get(), page.get(), frame1.get());
std::set<const FrameNode*> visited;
EXPECT_TRUE(
ToPublic(frame1.get())
->VisitChildFrameNodes(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(frame2.get()),
ToPublic(frame3.get())));
// Do an aborted visit.
visited.clear();
EXPECT_FALSE(
ToPublic(frame1.get())
->VisitChildFrameNodes(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());
}
} // namespace performance_manager