Commit 456503b8 authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

[PM] Add opener relationships between FrameNodes and PageNodes.

Follow-up CLs will add this to the graph display, and then
individually wire up the types of opener relationships.

BUG=1085129

Change-Id: If25406e0bc15fd6c6cbeeb62cc0db0c438fe9ea3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2215281
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarSigurður Ásgeirsson <siggi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772265}
parent e36d0659
......@@ -110,6 +110,12 @@ class DiscardsGraphDumpImpl : public discards::mojom::GraphDump,
void OnPageNodeAdded(const performance_manager::PageNode* page_node) override;
void OnBeforePageNodeRemoved(
const performance_manager::PageNode* page_node) override;
// Ignored for now.
// TODO(chrisha): Wire this relationship up with a dotted line!
void OnOpenerFrameNodeChanged(
const performance_manager::PageNode* page_node,
const performance_manager::FrameNode* previous_opener,
OpenedType previous_opened_type) override {}
void OnIsVisibleChanged(
const performance_manager::PageNode* page_node) override {} // Ignored.
void OnIsAudibleChanged(
......
......@@ -49,6 +49,7 @@ FrameNodeImpl::FrameNodeImpl(ProcessNodeImpl* process_node,
FrameNodeImpl::~FrameNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(child_worker_nodes_.empty());
DCHECK(opened_page_nodes_.empty());
}
void FrameNodeImpl::Bind(
......@@ -156,6 +157,11 @@ const base::flat_set<FrameNodeImpl*>& FrameNodeImpl::child_frame_nodes() const {
return child_frame_nodes_;
}
const base::flat_set<PageNodeImpl*>& FrameNodeImpl::opened_page_nodes() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return opened_page_nodes_;
}
mojom::LifecycleState FrameNodeImpl::lifecycle_state() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state_.value();
......@@ -321,6 +327,28 @@ void FrameNodeImpl::SetPriorityAndReason(
priority_and_reason_.SetAndMaybeNotify(this, priority_and_reason);
}
void FrameNodeImpl::AddOpenedPage(util::PassKey<PageNodeImpl>,
PageNodeImpl* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(page_node);
DCHECK_NE(page_node_, page_node);
DCHECK(graph()->NodeInGraph(page_node));
DCHECK_EQ(this, page_node->opener_frame_node());
bool inserted = opened_page_nodes_.insert(page_node).second;
DCHECK(inserted);
}
void FrameNodeImpl::RemoveOpenedPage(util::PassKey<PageNodeImpl>,
PageNodeImpl* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(page_node);
DCHECK_NE(page_node_, page_node);
DCHECK(graph()->NodeInGraph(page_node));
DCHECK_EQ(this, page_node->opener_frame_node());
size_t removed = opened_page_nodes_.erase(page_node);
DCHECK_EQ(1u, removed);
}
const FrameNode* FrameNodeImpl::GetParentFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return parent_frame_node();
......@@ -377,6 +405,26 @@ const base::flat_set<const FrameNode*> FrameNodeImpl::GetChildFrameNodes()
return children;
}
bool FrameNodeImpl::VisitOpenedPageNodes(const PageNodeVisitor& visitor) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto* page_impl : opened_page_nodes()) {
const PageNode* page = page_impl;
if (!visitor.Run(page))
return false;
}
return true;
}
const base::flat_set<const PageNode*> FrameNodeImpl::GetOpenedPageNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::flat_set<const PageNode*> opened;
for (auto* page : opened_page_nodes())
opened.insert(static_cast<const PageNode*>(page));
DCHECK_EQ(opened.size(), opened_page_nodes().size());
return opened;
}
FrameNodeImpl::LifecycleState FrameNodeImpl::GetLifecycleState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state();
......@@ -491,6 +539,9 @@ void FrameNodeImpl::OnBeforeLeavingGraph() {
DCHECK(child_frame_nodes_.empty());
// Sever opener relationships.
SeverOpenedPagesAndMaybeReparentPopups();
// Leave the page.
DCHECK(graph()->NodeInGraph(page_node_));
page_node_->RemoveFrame(this);
......@@ -510,6 +561,39 @@ void FrameNodeImpl::OnBeforeLeavingGraph() {
render_frame_id_, this);
}
void FrameNodeImpl::SeverOpenedPagesAndMaybeReparentPopups() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
using OpenedType = PageNode::OpenedType;
// Copy |opened_page_nodes_| as we'll be modifying it in this loop.
base::flat_set<PageNodeImpl*> opened_nodes = opened_page_nodes_;
for (auto* opened_node : opened_nodes) {
const bool is_popup = opened_node->opened_type() == OpenedType::kPopup;
opened_node->ClearOpenerFrameNodeAndOpenedType();
// Special case: child popups are reparented to the root of the frame tree.
// See WebContentsImpl::SetOpenerForNewContents.
if (is_popup) {
auto* main_frame = GetFrameTreeRoot();
if (main_frame != this) {
opened_node->SetOpenerFrameNodeAndOpenedType(main_frame,
OpenedType::kPopup);
}
}
}
DCHECK(opened_page_nodes_.empty());
}
FrameNodeImpl* FrameNodeImpl::GetFrameTreeRoot() const {
FrameNodeImpl* root = const_cast<FrameNodeImpl*>(this);
while (root->parent_frame_node())
root = parent_frame_node();
DCHECK_NE(nullptr, root);
return root;
}
bool FrameNodeImpl::HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (parent_frame_node_ == frame_node ||
......@@ -530,6 +614,11 @@ bool FrameNodeImpl::HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const {
return false;
}
bool FrameNodeImpl::HasFrameNodeInTree(FrameNodeImpl* frame_node) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return GetFrameTreeRoot() == frame_node->GetFrameTreeRoot();
}
FrameNodeImpl::DocumentProperties::DocumentProperties() = default;
FrameNodeImpl::DocumentProperties::~DocumentProperties() = default;
......
......@@ -11,6 +11,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/unguessable_token.h"
#include "base/util/type_safety/pass_key.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/render_frame_host_proxy.h"
......@@ -99,6 +100,7 @@ class FrameNodeImpl
// Getters for non-const properties. These are not thread safe.
const base::flat_set<FrameNodeImpl*>& child_frame_nodes() const;
const base::flat_set<PageNodeImpl*>& opened_page_nodes() const;
LifecycleState lifecycle_state() const;
InterventionPolicy origin_trial_freeze_policy() const;
bool has_nonempty_beforeunload() const;
......@@ -133,10 +135,22 @@ class FrameNodeImpl
return weak_factory_.GetWeakPtr();
}
void SeverOpenedPagesAndMaybeReparentPopupsForTesting() {
SeverOpenedPagesAndMaybeReparentPopups();
}
protected:
friend class PageNodeImpl;
// Invoked by opened pages when this frame is set/cleared as their opener.
// See PageNodeImpl::(Set|Clear)OpenerFrameNodeAndOpenedType.
void AddOpenedPage(util::PassKey<PageNodeImpl> key, PageNodeImpl* page_node);
void RemoveOpenedPage(util::PassKey<PageNodeImpl> key,
PageNodeImpl* page_node);
private:
friend class FrameNodeImplDescriber;
friend class FramePriorityAccess;
friend class PageNodeImpl;
friend class ProcessNodeImpl;
// Rest of FrameNode implementation. These are private so that users of the
......@@ -150,6 +164,8 @@ class FrameNodeImpl
int32_t GetSiteInstanceId() const override;
bool VisitChildFrameNodes(const FrameNodeVisitor& visitor) const override;
const base::flat_set<const FrameNode*> GetChildFrameNodes() const override;
bool VisitOpenedPageNodes(const PageNodeVisitor& visitor) const override;
const base::flat_set<const PageNode*> GetOpenedPageNodes() const override;
LifecycleState GetLifecycleState() const override;
InterventionPolicy GetOriginTrialFreezePolicy() const override;
bool HasNonemptyBeforeUnload() const override;
......@@ -208,8 +224,21 @@ class FrameNodeImpl
void OnJoiningGraph() override;
void OnBeforeLeavingGraph() override;
// Helper function to sever all opened page relationships. This is called
// before destroying the frame node in "OnBeforeLeavingGraph". Note that this
// will reparent popups to the root frame of the page if this frame isn't
// the root.
void SeverOpenedPagesAndMaybeReparentPopups();
// This is not quite the same as GetMainFrame, because there can be multiple
// main frames while the main frame is navigating. This explicitly walks up
// the tree to find the main frame that corresponds to this frame tree node,
// even if it is not current.
FrameNodeImpl* GetFrameTreeRoot() const;
bool HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const;
bool HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const;
bool HasFrameNodeInTree(FrameNodeImpl* frame_node) const;
mojo::Receiver<mojom::DocumentCoordinationUnit> receiver_{this};
......@@ -241,6 +270,9 @@ class FrameNodeImpl
base::flat_set<FrameNodeImpl*> child_frame_nodes_;
// The set of pages that have been opened by this frame.
base::flat_set<PageNodeImpl*> opened_page_nodes_;
// Does *not* change when a navigation is committed.
ObservedProperty::NotifiesOnlyOnChanges<
LifecycleState,
......
......@@ -4,6 +4,7 @@
#include "components/performance_manager/graph/frame_node_impl.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/test_support/graph_test_harness.h"
......@@ -21,6 +22,10 @@ const FrameNode* ToPublic(FrameNodeImpl* frame_node) {
return frame_node;
}
const PageNode* ToPublic(PageNodeImpl* page_node) {
return page_node;
}
} // namespace
TEST_F(FrameNodeImplTest, SafeDowncast) {
......@@ -455,4 +460,177 @@ TEST_F(FrameNodeImplTest, VisitChildFrameNodes) {
EXPECT_EQ(1u, visited.size());
}
namespace {
class LenientMockPageObserver : public PageNode::ObserverDefaultImpl {
public:
LenientMockPageObserver() = default;
~LenientMockPageObserver() override = default;
// Note that opener functionality is actually tested in the
MOCK_METHOD3(OnOpenerFrameNodeChanged,
void(const PageNode*, const FrameNode*, OpenedType));
};
using MockPageObserver = ::testing::StrictMock<LenientMockPageObserver>;
} // namespace
TEST_F(FrameNodeImplTest, OpenerRelationships) {
using OpenedType = PageNode::OpenedType;
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 opener clearing helper, even if you
// have no such relationships.
frameB1->SeverOpenedPagesAndMaybeReparentPopupsForTesting();
// You can't clear an opener if you don't already have one.
EXPECT_DCHECK_DEATH(pageB->ClearOpenerFrameNodeAndOpenedType());
// You can't be an opener for your own frame tree.
EXPECT_DCHECK_DEATH(pageA->SetOpenerFrameNodeAndOpenedType(
frameA1.get(), OpenedType::kPopup));
// You can't set a null opener or an invalid opened type.
EXPECT_DCHECK_DEATH(
pageB->SetOpenerFrameNodeAndOpenedType(nullptr, OpenedType::kInvalid));
EXPECT_DCHECK_DEATH(pageB->SetOpenerFrameNodeAndOpenedType(
frameA1.get(), OpenedType::kInvalid));
EXPECT_EQ(nullptr, pageB->opener_frame_node());
EXPECT_EQ(nullptr, ppageB->GetOpenerFrameNode());
EXPECT_EQ(OpenedType::kInvalid, pageB->opened_type());
EXPECT_EQ(OpenedType::kInvalid, ppageB->GetOpenedType());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
EXPECT_TRUE(pframeA1->GetOpenedPageNodes().empty());
// Set an opener relationship.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr,
OpenedType::kInvalid));
pageB->SetOpenerFrameNodeAndOpenedType(frameA1.get(), OpenedType::kGuestView);
EXPECT_EQ(frameA1.get(), pageB->opener_frame_node());
EXPECT_EQ(frameA1.get(), ppageB->GetOpenerFrameNode());
EXPECT_EQ(OpenedType::kGuestView, pageB->opened_type());
EXPECT_EQ(OpenedType::kGuestView, ppageB->GetOpenedType());
EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
EXPECT_EQ(1u, pframeA1->GetOpenedPageNodes().size());
EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
EXPECT_EQ(1u, pframeA1->GetOpenedPageNodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
// Once you have an opener, you can't set it again (it has to be cleared
// first).
EXPECT_DCHECK_DEATH(pageB->SetOpenerFrameNodeAndOpenedType(
frameA1.get(), OpenedType::kPopup));
// Set another opener relationship.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), nullptr,
OpenedType::kInvalid));
pageC->SetOpenerFrameNodeAndOpenedType(frameA1.get(), OpenedType::kPopup);
EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
EXPECT_EQ(OpenedType::kPopup, pageC->opened_type());
EXPECT_EQ(2u, frameA1->opened_page_nodes().size());
EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
// Do a traversal.
std::set<const PageNode*> visited;
EXPECT_TRUE(
ToPublic(frameA1.get())
->VisitOpenedPageNodes(base::BindRepeating(
[](std::set<const PageNode*>* visited, const PageNode* page) {
EXPECT_TRUE(visited->insert(page).second);
return true;
},
base::Unretained(&visited))));
EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageB.get()),
ToPublic(pageC.get())));
// Do an aborted visit.
visited.clear();
EXPECT_FALSE(
ToPublic(frameA1.get())
->VisitOpenedPageNodes(base::BindRepeating(
[](std::set<const PageNode*>* visited, const PageNode* page) {
EXPECT_TRUE(visited->insert(page).second);
return false;
},
base::Unretained(&visited))));
EXPECT_EQ(1u, visited.size());
// Manually clear the first relationship (initiated from the page).
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA1.get(),
OpenedType::kGuestView));
pageB->ClearOpenerFrameNodeAndOpenedType();
EXPECT_EQ(nullptr, pageB->opener_frame_node());
EXPECT_EQ(OpenedType::kInvalid, pageB->opened_type());
EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
EXPECT_EQ(OpenedType::kPopup, pageC->opened_type());
EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
EXPECT_EQ(0u, frameA1->opened_page_nodes().count(pageB.get()));
testing::Mock::VerifyAndClear(&obs);
// Clear the second relationship (initiated from the frame).
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), frameA1.get(),
OpenedType::kPopup));
frameA1->SeverOpenedPagesAndMaybeReparentPopupsForTesting();
EXPECT_EQ(nullptr, pageC->opener_frame_node());
EXPECT_EQ(OpenedType::kInvalid, pageC->opened_type());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
testing::Mock::VerifyAndClear(&obs);
// Set a popup opener relationship on node A2.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr,
OpenedType::kInvalid));
pageB->SetOpenerFrameNodeAndOpenedType(frameA2.get(), OpenedType::kPopup);
EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
EXPECT_EQ(OpenedType::kPopup, pageB->opened_type());
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);
// Clear it with the helper, and expect it to be reparented to node A1.
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get(),
OpenedType::kPopup));
EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr,
OpenedType::kInvalid));
frameA2->SeverOpenedPagesAndMaybeReparentPopupsForTesting();
EXPECT_EQ(frameA1.get(), pageB->opener_frame_node());
EXPECT_EQ(OpenedType::kPopup, pageB->opened_type());
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(),
OpenedType::kPopup));
frameA1->SeverOpenedPagesAndMaybeReparentPopupsForTesting();
EXPECT_EQ(nullptr, pageB->opener_frame_node());
EXPECT_EQ(OpenedType::kInvalid, pageB->opened_type());
EXPECT_TRUE(frameA1->opened_page_nodes().empty());
EXPECT_TRUE(frameA2->opened_page_nodes().empty());
testing::Mock::VerifyAndClear(&obs);
graph()->RemovePageNodeObserver(&obs);
}
} // namespace performance_manager
......@@ -322,4 +322,24 @@ TEST_F(GraphImplTest, NodeDataDescribers) {
EXPECT_EQ(0u, descr.DictSize());
}
TEST_F(GraphImplTest, OpenersClearedOnTeardown) {
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());
// Set up some opener relationships. These should be gracefully torn down as
// the graph cleans up nodes, otherwise the frame and page node destructors
// will explode.
pageB->SetOpenerFrameNodeAndOpenedType(frameA1.get(),
PageNode::OpenedType::kGuestView);
pageC->SetOpenerFrameNodeAndOpenedType(frameA2.get(),
PageNode::OpenedType::kPopup);
}
} // namespace performance_manager
......@@ -34,6 +34,8 @@ PageNodeImpl::PageNodeImpl(const WebContentsProxy& contents_proxy,
PageNodeImpl::~PageNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(nullptr, opener_frame_node_);
DCHECK_EQ(OpenedType::kInvalid, opened_type_);
}
const WebContentsProxy& PageNodeImpl::contents_proxy() const {
......@@ -150,6 +152,18 @@ FrameNodeImpl* PageNodeImpl::GetMainFrameNodeImpl() const {
return *main_frame_nodes_.begin();
}
FrameNodeImpl* PageNodeImpl::opener_frame_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(opener_frame_node_ || opened_type_ == OpenedType::kInvalid);
return opener_frame_node_;
}
PageNodeImpl::OpenedType PageNodeImpl::opened_type() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(opener_frame_node_ || opened_type_ == OpenedType::kInvalid);
return opened_type_;
}
bool PageNodeImpl::is_visible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_visible_.value();
......@@ -231,6 +245,42 @@ bool PageNodeImpl::had_form_interaction() const {
return had_form_interaction_.value();
}
void PageNodeImpl::SetOpenerFrameNodeAndOpenedType(FrameNodeImpl* opener,
OpenedType opened_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(opener);
DCHECK(graph()->NodeInGraph(opener));
DCHECK_NE(this, opener->page_node());
DCHECK_NE(OpenedType::kInvalid, opened_type);
// Can't already have an opener.
DCHECK_EQ(nullptr, opener_frame_node_);
DCHECK_EQ(OpenedType::kInvalid, opened_type_);
opener_frame_node_ = opener;
opened_type_ = opened_type;
opener->AddOpenedPage(PassKey(), this);
for (auto* observer : GetObservers())
observer->OnOpenerFrameNodeChanged(this, nullptr, OpenedType::kInvalid);
}
void PageNodeImpl::ClearOpenerFrameNodeAndOpenedType() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(nullptr, opener_frame_node_);
DCHECK_NE(OpenedType::kInvalid, opened_type_);
auto* previous_opener = opener_frame_node_;
auto previous_type = opened_type_;
opener_frame_node_->RemoveOpenedPage(PassKey(), this);
opener_frame_node_ = nullptr;
opened_type_ = OpenedType::kInvalid;
for (auto* observer : GetObservers())
observer->OnOpenerFrameNodeChanged(this, previous_opener, previous_type);
}
void PageNodeImpl::set_usage_estimate_time(
base::TimeTicks usage_estimate_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......@@ -262,6 +312,10 @@ void PageNodeImpl::OnJoiningGraph() {
void PageNodeImpl::OnBeforeLeavingGraph() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Sever opener relationships.
if (opener_frame_node_)
ClearOpenerFrameNodeAndOpenedType();
DCHECK_EQ(0u, frame_node_count_);
}
......@@ -270,6 +324,16 @@ const std::string& PageNodeImpl::GetBrowserContextID() const {
return browser_context_id();
}
const FrameNode* PageNodeImpl::GetOpenerFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return opener_frame_node();
}
PageNodeImpl::OpenedType PageNodeImpl::GetOpenedType() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return opened_type();
}
bool PageNodeImpl::IsVisible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_visible();
......
......@@ -13,6 +13,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/util/type_safety/pass_key.h"
#include "components/performance_manager/graph/node_attached_data.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/public/graph/page_node.h"
......@@ -27,6 +28,8 @@ class PageNodeImpl
: public PublicNodeImpl<PageNodeImpl, PageNode>,
public TypedNodeBase<PageNodeImpl, PageNode, PageNodeObserver> {
public:
using PassKey = util::PassKey<PageNodeImpl>;
static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kPage; }
PageNodeImpl(const WebContentsProxy& contents_proxy,
......@@ -70,6 +73,8 @@ class PageNodeImpl
// Accessors.
const std::string& browser_context_id() const;
FrameNodeImpl* opener_frame_node() const;
OpenedType opened_type() const;
bool is_visible() const;
bool is_audible() const;
bool is_loading() const;
......@@ -86,6 +91,11 @@ class PageNodeImpl
const std::string& contents_mime_type() const;
bool had_form_interaction() const;
// Invoked to set/clear the opener of this page.
void SetOpenerFrameNodeAndOpenedType(FrameNodeImpl* opener,
OpenedType opened_type);
void ClearOpenerFrameNodeAndOpenedType();
void set_usage_estimate_time(base::TimeTicks usage_estimate_time);
void set_private_footprint_kb_estimate(
uint64_t private_footprint_kb_estimate);
......@@ -116,6 +126,8 @@ class PageNodeImpl
// PageNode implementation.
const std::string& GetBrowserContextID() const override;
const FrameNode* GetOpenerFrameNode() const override;
OpenedType GetOpenedType() const override;
bool IsVisible() const override;
base::TimeDelta GetTimeSinceLastVisibilityChange() const override;
bool IsAudible() const override;
......@@ -196,6 +208,12 @@ class PageNodeImpl
// The unique ID of the browser context that this page belongs to.
const std::string browser_context_id_;
// The opener of this page, if there is one.
FrameNodeImpl* opener_frame_node_ = nullptr;
// The way in which this page was opened, if it was opened.
OpenedType opened_type_ = OpenedType::kInvalid;
// Whether or not the page is visible. Driven by browser instrumentation.
// Initialized on construction.
ObservedProperty::NotifiesOnlyOnChanges<bool,
......
......@@ -201,6 +201,10 @@ class LenientMockObserver : public PageNodeImpl::Observer {
MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
// Note that opener functionality is actually tested in the FrameNodeImpl
// and GraphImpl unittests.
MOCK_METHOD3(OnOpenerFrameNodeChanged,
void(const PageNode*, const FrameNode*, OpenedType));
MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
MOCK_METHOD1(OnIsAudibleChanged, void(const PageNode*));
MOCK_METHOD1(OnIsLoadingChanged, void(const PageNode*));
......
......@@ -57,6 +57,7 @@ class FrameNode : public Node {
using InterventionPolicy = mojom::InterventionPolicy;
using LifecycleState = mojom::LifecycleState;
using Observer = FrameNodeObserver;
using PageNodeVisitor = base::RepeatingCallback<bool(const PageNode*)>;
using PriorityAndReason = frame_priority::PriorityAndReason;
class ObserverDefaultImpl;
......@@ -110,6 +111,17 @@ class FrameNode : public Node {
// VisitChildFrameNodes when that makes sense.
virtual const base::flat_set<const FrameNode*> GetChildFrameNodes() const = 0;
// Visits the page nodes that have been opened by this frame. The iteration
// is halted if the visitor returns false. Returns true if every call to the
// visitor returned true, false otherwise.
virtual bool VisitOpenedPageNodes(const PageNodeVisitor& visitor) const = 0;
// Returns the set of opened pages associatted with this frame. Note that
// this incurs a full container copy all the opened nodes. Please use
// VisitOpenedPageNodes when that makes sense. This can change over the
// lifetime of the frame.
virtual const base::flat_set<const PageNode*> GetOpenedPageNodes() const = 0;
// Returns the current lifecycle state of this frame. See
// FrameNodeObserver::OnFrameLifecycleStateChanged.
virtual LifecycleState GetLifecycleState() const = 0;
......
......@@ -33,12 +33,31 @@ class PageNode : public Node {
using Observer = PageNodeObserver;
class ObserverDefaultImpl;
// Reasons for which one frame can become the opener of a page.
enum class OpenedType {
// Returned if this node doesn't have an opener.
kInvalid,
// This node is a popup (the opener created it via window.open).
kPopup,
// This node is a guest view. This can be many things (<webview>, <portal>,
// <appview>, etc) but backed by the same inner/outer WebContents mechanism.
kGuestView,
};
PageNode();
~PageNode() override;
// Returns the unique ID of the browser context that this page belongs to.
virtual const std::string& GetBrowserContextID() const = 0;
// Returns the opener frame node, if there is one. This may change over the
// lifetime of this page. See "OnOpenerFrameNodeChanged".
virtual const FrameNode* GetOpenerFrameNode() const = 0;
// Returns the type of relationship this node has with its opener, if it has
// an opener.
virtual OpenedType GetOpenedType() const = 0;
// Returns true if this page is currently visible, false otherwise.
// See PageNodeObserver::OnIsVisibleChanged.
virtual bool IsVisible() const = 0;
......@@ -132,6 +151,8 @@ class PageNode : public Node {
// implement the entire interface.
class PageNodeObserver {
public:
using OpenedType = PageNode::OpenedType;
PageNodeObserver();
virtual ~PageNodeObserver();
......@@ -145,6 +166,13 @@ class PageNodeObserver {
// Notifications of property changes.
// Invoked when this page has been assigned an opener, or had one removed.
// This can happen a page is opened via window.open, webviews, portals, etc,
// or when that relationship is subsequently severed.
virtual void OnOpenerFrameNodeChanged(const PageNode* page_node,
const FrameNode* previous_opener,
OpenedType previous_opened_type) = 0;
// Invoked when the IsVisible property changes.
virtual void OnIsVisibleChanged(const PageNode* page_node) = 0;
......@@ -207,6 +235,9 @@ class PageNode::ObserverDefaultImpl : public PageNodeObserver {
// PageNodeObserver implementation:
void OnPageNodeAdded(const PageNode* page_node) override {}
void OnBeforePageNodeRemoved(const PageNode* page_node) override {}
void OnOpenerFrameNodeChanged(const PageNode* page_node,
const FrameNode* previous_opener,
OpenedType previous_opened_type) override {}
void OnIsVisibleChanged(const PageNode* page_node) override {}
void OnIsAudibleChanged(const PageNode* page_node) override {}
void OnIsLoadingChanged(const PageNode* page_node) override {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment