blob: 072d23a3392e06f6c48f4c794d4b228f50ea6273 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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 "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gtest_util.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/public/render_process_host_id.h"
#include "components/performance_manager/public/render_process_host_proxy.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;
}
const PageNode* ToPublic(PageNodeImpl* page_node) {
return page_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());
auto child3_node =
CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get());
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>(
RenderProcessHostProxy::CreateForTesting(RenderProcessHostId(42)));
auto process_b = CreateNode<ProcessNodeImpl>(
RenderProcessHostProxy::CreateForTesting(RenderProcessHostId(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->GetRenderProcessHostId(),
frame_a1->render_frame_id()),
frame_a1.get());
EXPECT_EQ(graph()->GetFrameNodeById(process_a->GetRenderProcessHostId(),
frame_a2->render_frame_id()),
frame_a2.get());
EXPECT_EQ(graph()->GetFrameNodeById(process_b->GetRenderProcessHostId(),
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->GetURL().is_empty());
const GURL url("http://www.foo.com/");
frame_node->OnNavigationCommitted(url, /* same_document */ true);
EXPECT_EQ(url, frame_node->GetURL());
}
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->GetURL().is_empty());
const GURL url("http://www.foo.com/");
frame_node->OnNavigationCommitted(url, /* same_document */ false);
EXPECT_EQ(url, frame_node->GetURL());
}
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());
// 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(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(OnHadUserEditsChanged, void(const FrameNode*));
MOCK_METHOD1(OnIsAudibleChanged, void(const FrameNode*));
MOCK_METHOD1(OnIsCapturingMediaStreamChanged, void(const FrameNode*));
MOCK_METHOD1(OnIntersectsViewportChanged, void(const FrameNode*));
MOCK_METHOD2(OnFrameVisibilityChanged,
void(const FrameNode*, FrameNode::Visibility));
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:
raw_ptr<const FrameNode, DanglingUntriaged> 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(false);
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);
}
// This is an end-to-end test of the logic that explodes when recursive
// notifications are dispatched during node creation and removal. There are
// other tests of the individual pieces of logic in NodeBase and
// ObservedProperty.
TEST_F(FrameNodeImplDeathTest, SetPropertyDuringNodeCreation) {
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
// Modifying a property during node addition should explode.
bool set_property = true;
EXPECT_CALL(obs, OnFrameNodeAdded(_))
.WillOnce(Invoke([&](const FrameNode* frame_node) {
if (set_property) {
auto* impl = FrameNodeImpl::FromNode(frame_node);
impl->SetIsAdFrame(true);
}
}));
EXPECT_DCHECK_DEATH(auto frame =
CreateFrameNodeAutoId(process.get(), page.get()));
// Now create the node but don't modify a property so that the mock
// expectation is satisfied.
set_property = false;
auto frame = CreateFrameNodeAutoId(process.get(), page.get());
// Modifying a property during node removal should explode.
set_property = true;
EXPECT_CALL(obs, OnBeforeFrameNodeRemoved(_))
.WillOnce(Invoke([&](const FrameNode* frame_node) {
if (set_property) {
auto* impl = FrameNodeImpl::FromNode(frame_node);
impl->SetIsAdFrame(true);
}
}));
EXPECT_DCHECK_DEATH(frame.reset());
// Now remove the node but don't modify a property so that the mock
// expectation is satisfied.
set_property = false;
frame.reset();
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);
// Observer will be notified once when IsAdFrame goes from false to true, and
// again when it goes from true to false.
EXPECT_CALL(obs, OnIsAdFrameChanged(frame_node.get())).Times(2);
EXPECT_FALSE(frame_node->IsAdFrame());
frame_node->SetIsAdFrame(true);
EXPECT_TRUE(frame_node->IsAdFrame());
frame_node->SetIsAdFrame(true);
EXPECT_TRUE(frame_node->IsAdFrame());
frame_node->SetIsAdFrame(false);
EXPECT_FALSE(frame_node->IsAdFrame());
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->IsHoldingWebLock());
EXPECT_CALL(obs, OnFrameIsHoldingWebLockChanged(frame_node.get()));
frame_node->SetIsHoldingWebLock(true);
EXPECT_TRUE(frame_node->IsHoldingWebLock());
EXPECT_CALL(obs, OnFrameIsHoldingWebLockChanged(frame_node.get()));
frame_node->SetIsHoldingWebLock(false);
EXPECT_FALSE(frame_node->IsHoldingWebLock());
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->IsHoldingIndexedDBLock());
EXPECT_CALL(obs, OnFrameIsHoldingIndexedDBLockChanged(frame_node.get()));
frame_node->SetIsHoldingIndexedDBLock(false);
EXPECT_FALSE(frame_node->IsHoldingIndexedDBLock());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, Priority) {
using PriorityAndReason = execution_context_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->GetPriorityAndReason().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->GetPriorityAndReason());
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->GetPriorityAndReason());
testing::Mock::VerifyAndClear(&obs);
// Change neither.
frame_node->SetPriorityAndReason(
PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason));
EXPECT_EQ(PriorityAndReason(base::TaskPriority::HIGHEST, kDummyReason),
frame_node->GetPriorityAndReason());
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->GetPriorityAndReason());
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->HadFormInteraction());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, UserEdits) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_CALL(obs, OnHadUserEditsChanged(frame_node.get()));
frame_node->SetHadUserEdits();
EXPECT_TRUE(frame_node->HadUserEdits());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, IsAudible) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
EXPECT_FALSE(frame_node->IsAudible());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_CALL(obs, OnIsAudibleChanged(frame_node.get()));
frame_node->SetIsAudible(true);
EXPECT_TRUE(frame_node->IsAudible());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, IsCapturingMediaStream) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
EXPECT_FALSE(frame_node->IsCapturingMediaStream());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_CALL(obs, OnIsCapturingMediaStreamChanged(frame_node.get()));
frame_node->SetIsCapturingMediaStream(true);
EXPECT_TRUE(frame_node->IsCapturingMediaStream());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, IntersectsViewport) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
// A child frame node is used because the intersection with the viewport of a
// main frame is not tracked.
auto main_frame_node = CreateFrameNodeAutoId(process.get(), page.get());
auto child_frame_node =
CreateFrameNodeAutoId(process.get(), page.get(), main_frame_node.get());
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
// Initially unknown.
EXPECT_FALSE(child_frame_node->IntersectsViewport().has_value());
EXPECT_CALL(obs, OnIntersectsViewportChanged(child_frame_node.get()));
child_frame_node->SetIntersectsViewport(true);
EXPECT_TRUE(child_frame_node->IntersectsViewport().value());
EXPECT_CALL(obs, OnIntersectsViewportChanged(child_frame_node.get()));
child_frame_node->SetIntersectsViewport(false);
EXPECT_FALSE(child_frame_node->IntersectsViewport().value());
graph()->RemoveFrameNodeObserver(&obs);
}
TEST_F(FrameNodeImplTest, Visibility) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
EXPECT_EQ(frame_node->GetVisibility(), FrameNode::Visibility::kUnknown);
MockObserver obs;
graph()->AddFrameNodeObserver(&obs);
EXPECT_CALL(obs, OnFrameVisibilityChanged(frame_node.get(),
FrameNode::Visibility::kUnknown));
frame_node->SetVisibility(FrameNode::Visibility::kVisible);
EXPECT_EQ(frame_node->GetVisibility(), FrameNode::Visibility::kVisible);
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::Milliseconds(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());
auto child_frame_node =
CreateFrameNodeAutoId(process.get(), page.get(), frame_node.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());
auto child_frame_nodes = public_frame_node->GetChildFrameNodes();
for (FrameNodeImpl* child : frame_node->child_frame_nodes()) {
EXPECT_TRUE(base::Contains(child_frame_nodes, child));
}
EXPECT_EQ(child_frame_nodes.size(), frame_node->child_frame_nodes().size());
}
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([&visited](const FrameNode* frame) {
EXPECT_TRUE(visited.insert(frame).second);
return true;
}));
EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(frame2.get()),
ToPublic(frame3.get())));
// Do an aborted visit.
visited.clear();
EXPECT_FALSE(ToPublic(frame1.get())
->VisitChildFrameNodes([&visited](const FrameNode* frame) {
EXPECT_TRUE(visited.insert(frame).second);
return false;
}));
EXPECT_EQ(1u, visited.size());
}
namespace {
class LenientMockPageObserver : public PageNode::ObserverDefaultImpl {
public:
LenientMockPageObserver() = default;
~LenientMockPageObserver() override = default;
MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode* page_node));
// Note that embedder functionality is actually tested in the
// performance_manager_browsertest.
MOCK_METHOD2(OnOpenerFrameNodeChanged,
void(const PageNode*, const FrameNode*));
MOCK_METHOD3(OnEmbedderFrameNodeChanged,
void(const PageNode*, const FrameNode*, EmbeddingType));
};
using MockPageObserver = ::testing::StrictMock<LenientMockPageObserver>;
} // namespace
TEST_F(FrameNodeImplTest, PageRelationships) {
using EmbeddingType = PageNode::EmbeddingType;
auto process = CreateNode<ProcessNodeImpl>();
auto pageA = CreateNode<PageNodeImpl>();
auto frameA1 = CreateFrameNodeAutoId(process.get(), pageA.get());
auto frameA2 =
CreateFrameNodeAutoId(process.get(), pageA.get(), frameA1.get());
auto pageB = CreateNode<PageNodeImpl>();
auto frameB1 = CreateFrameNodeAutoId(process.get(), pageB.get());
auto pageC = CreateNode<PageNodeImpl>();
auto frameC1 = CreateFrameNodeAutoId(process.get(), pageC.get());
// Use these to test the public APIs as well.
const FrameNode* pframeA1 = static_cast<const FrameNode*>(frameA1.get());
const PageNode* ppageB = static_cast<const PageNode*>(pageB.get());
MockPageObserver obs;
graph()->AddPageNodeObserver(&obs);
// You can always call the pre-delete embedder clearing helper, even if you
// have no such relationships.
frameB1->SeverPageRelationshipsAndMaybeReparentForTesting();
// You can't clear an embedder if you don't already have one.
EXPECT_DCHECK_DEATH(pageB->ClearEmbedderFrameNodeAndEmbeddingType());
// You can't be an embedder for your own frame tree.
EXPECT_DCHECK_DEATH(pageA->SetEmbedderFrameNodeAndEmbeddingType(
frameA1.get(), EmbeddingType::kPortal));
// You can't set a null embedder or an invalid embedded type.
EXPECT_DCHECK_DEATH(pageB->SetEmbedderFrameNodeAndEmbeddingType(
nullptr, EmbeddingType::kInvalid));
EXPECT_DCHECK_DEATH(pageB->SetEmbedderFrameNodeAndEmbeddingType(
frameA1.get(), EmbeddingType::kInvalid));
EXPECT_EQ(nullptr, pageB->embedder_frame_node());
EXPECT_EQ(nullptr, ppageB->GetEmbedderFrameNode());
EXPECT_EQ(EmbeddingType::kInvalid, ppageB->GetEmbeddingType());
EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
EXPECT_TRUE(pframeA1->GetEmbeddedPageNodes().empty());
// Set an embedder relationship.
EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), nullptr,
EmbeddingType::kInvalid));
pageB->SetEmbedderFrameNodeAndEmbeddingType(frameA1.get(),
EmbeddingType::kGuestView);
EXPECT_EQ(frameA1.get(), pageB->embedder_frame_node());
EXPECT_EQ(frameA1.get(), ppageB->GetEmbedderFrameNode());
EXPECT_EQ(EmbeddingType::kGuestView, ppageB->GetEmbeddingType());
EXPECT_EQ(1u, frameA1->embedded_page_nodes().size());
EXPECT_EQ(1u, pframeA1->GetEmbeddedPageNodes().size());
EXPECT_EQ(1u, frameA1->embedded_page_nodes().count(pageB.get()));
EXPECT_EQ(1u, pframeA1->GetEmbeddedPageNodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
// Set an opener relationship.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), nullptr));
pageC->SetOpenerFrameNode(frameA1.get());
EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
EXPECT_EQ(1u, frameA1->embedded_page_nodes().size());
EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
EXPECT_EQ(1u, frameA1->embedded_page_nodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
// Do an embedded page traversal.
std::set<const PageNode*> visited;
EXPECT_TRUE(ToPublic(frameA1.get())
->VisitEmbeddedPageNodes([&visited](const PageNode* page) {
EXPECT_TRUE(visited.insert(page).second);
return true;
}));
EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageB.get())));
// Do an opened page traversal.
visited.clear();
EXPECT_TRUE(ToPublic(frameA1.get())
->VisitOpenedPageNodes([&visited](const PageNode* page) {
EXPECT_TRUE(visited.insert(page).second);
return true;
}));
EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageC.get())));
// Do an aborted visit.
visited.clear();
EXPECT_FALSE(ToPublic(frameA1.get())
->VisitEmbeddedPageNodes([&visited](const PageNode* page) {
EXPECT_TRUE(visited.insert(page).second);
return false;
}));
EXPECT_EQ(1u, visited.size());
// Manually clear the embedder relationship (initiated from the page).
EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), frameA1.get(),
EmbeddingType::kGuestView));
pageB->ClearEmbedderFrameNodeAndEmbeddingType();
EXPECT_EQ(nullptr, pageB->embedder_frame_node());
EXPECT_EQ(EmbeddingType::kInvalid, pageB->GetEmbeddingType());
EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
testing::Mock::VerifyAndClear(&obs);
// Clear the opener relationship.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), frameA1.get()));
frameA1->SeverPageRelationshipsAndMaybeReparentForTesting();
EXPECT_EQ(nullptr, pageC->embedder_frame_node());
EXPECT_EQ(EmbeddingType::kInvalid, pageC->GetEmbeddingType());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
testing::Mock::VerifyAndClear(&obs);
// Set a popup relationship on node A2.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr));
pageB->SetOpenerFrameNode(frameA2.get());
EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
EXPECT_EQ(1u, frameA2->opened_page_nodes().size());
EXPECT_TRUE(frameA2->embedded_page_nodes().empty());
EXPECT_EQ(1u, frameA2->opened_page_nodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
// Clear it with the helper, and expect it to be reparented to node A1.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get()));
frameA2->SeverPageRelationshipsAndMaybeReparentForTesting();
EXPECT_EQ(frameA1.get(), pageB->opener_frame_node());
EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
EXPECT_TRUE(frameA2->opened_page_nodes().empty());
testing::Mock::VerifyAndClear(&obs);
// Clear it again with the helper. This time reparenting can't happen, as it
// was already parented to the root.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA1.get()));
frameA1->SeverPageRelationshipsAndMaybeReparentForTesting();
EXPECT_EQ(nullptr, pageB->opener_frame_node());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
EXPECT_TRUE(frameA2->opened_page_nodes().empty());
testing::Mock::VerifyAndClear(&obs);
// verify that the embedder relationship is torn down before any node removal
// notification arrives.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr));
pageB->SetOpenerFrameNode(frameA2.get());
EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
EXPECT_EQ(1u, frameA2->opened_page_nodes().size());
EXPECT_EQ(1u, frameA2->opened_page_nodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
{
::testing::InSequence seq;
// These must occur in sequence.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get()));
EXPECT_CALL(obs, OnBeforePageNodeRemoved(pageB.get()));
}
frameB1.reset();
pageB.reset();
testing::Mock::VerifyAndClear(&obs);
graph()->RemovePageNodeObserver(&obs);
}
} // namespace performance_manager