| // 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/frame_node_impl.h" |
| |
| #include <utility> |
| |
| #include "chrome/browser/performance_manager/graph/graph_impl.h" |
| #include "chrome/browser/performance_manager/graph/page_node_impl.h" |
| #include "chrome/browser/performance_manager/graph/process_node_impl.h" |
| #include "chrome/browser/performance_manager/graph/worker_node_impl.h" |
| #include "chrome/browser/performance_manager/public/frame_priority/frame_priority.h" |
| |
| namespace performance_manager { |
| |
| using PriorityAndReason = frame_priority::PriorityAndReason; |
| |
| FrameNodeImpl::FrameNodeImpl(GraphImpl* graph, |
| ProcessNodeImpl* process_node, |
| PageNodeImpl* page_node, |
| FrameNodeImpl* parent_frame_node, |
| int frame_tree_node_id, |
| int render_frame_id, |
| const base::UnguessableToken& dev_tools_token, |
| int32_t browsing_instance_id, |
| int32_t site_instance_id) |
| : TypedNodeBase(graph), |
| parent_frame_node_(parent_frame_node), |
| page_node_(page_node), |
| process_node_(process_node), |
| frame_tree_node_id_(frame_tree_node_id), |
| render_frame_id_(render_frame_id), |
| dev_tools_token_(dev_tools_token), |
| browsing_instance_id_(browsing_instance_id), |
| site_instance_id_(site_instance_id) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| DCHECK(process_node); |
| DCHECK(page_node); |
| } |
| |
| FrameNodeImpl::~FrameNodeImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(child_worker_nodes_.empty()); |
| } |
| |
| void FrameNodeImpl::Bind( |
| mojo::PendingReceiver<resource_coordinator::mojom::DocumentCoordinationUnit> |
| receiver) { |
| // It is possible to receive a mojo::PendingReceiver<DocumentCoordinationUnit> |
| // when |receiver_| is already bound in these cases: |
| // - Navigation from the initial empty document to the first real document. |
| // - Navigation rejected by RenderFrameHostImpl::ValidateDidCommitParams(). |
| // See discussion: |
| // https://chromium-review.googlesource.com/c/chromium/src/+/1572459/6#message-bd31f3e73f96bd9f7721be81ba6ac0076d053147 |
| receiver_.reset(); |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| void FrameNodeImpl::SetNetworkAlmostIdle() { |
| document_.network_almost_idle.SetAndMaybeNotify(this, true); |
| } |
| |
| void FrameNodeImpl::SetLifecycleState( |
| resource_coordinator::mojom::LifecycleState state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| lifecycle_state_.SetAndMaybeNotify(this, state); |
| } |
| |
| void FrameNodeImpl::SetHasNonEmptyBeforeUnload(bool has_nonempty_beforeunload) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| document_.has_nonempty_beforeunload = has_nonempty_beforeunload; |
| } |
| |
| void FrameNodeImpl::SetOriginTrialFreezePolicy( |
| resource_coordinator::mojom::InterventionPolicy policy) { |
| document_.origin_trial_freeze_policy.SetAndMaybeNotify(this, policy); |
| } |
| |
| void FrameNodeImpl::SetIsAdFrame() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| is_ad_frame_.SetAndMaybeNotify(this, true); |
| } |
| |
| void FrameNodeImpl::OnNonPersistentNotificationCreated() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| for (auto* observer : GetObservers()) |
| observer->OnNonPersistentNotificationCreated(this); |
| } |
| |
| bool FrameNodeImpl::IsMainFrame() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return !parent_frame_node_; |
| } |
| |
| FrameNodeImpl* FrameNodeImpl::parent_frame_node() const { |
| return parent_frame_node_; |
| } |
| |
| PageNodeImpl* FrameNodeImpl::page_node() const { |
| return page_node_; |
| } |
| |
| ProcessNodeImpl* FrameNodeImpl::process_node() const { |
| return process_node_; |
| } |
| |
| int FrameNodeImpl::frame_tree_node_id() const { |
| return frame_tree_node_id_; |
| } |
| |
| int FrameNodeImpl::render_frame_id() const { |
| return render_frame_id_; |
| } |
| |
| const base::UnguessableToken& FrameNodeImpl::dev_tools_token() const { |
| return dev_tools_token_; |
| } |
| |
| int32_t FrameNodeImpl::browsing_instance_id() const { |
| return browsing_instance_id_; |
| } |
| |
| int32_t FrameNodeImpl::site_instance_id() const { |
| return site_instance_id_; |
| } |
| |
| const base::flat_set<FrameNodeImpl*>& FrameNodeImpl::child_frame_nodes() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return child_frame_nodes_; |
| } |
| |
| resource_coordinator::mojom::LifecycleState FrameNodeImpl::lifecycle_state() |
| const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return lifecycle_state_.value(); |
| } |
| |
| resource_coordinator::mojom::InterventionPolicy |
| FrameNodeImpl::origin_trial_freeze_policy() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return document_.origin_trial_freeze_policy.value(); |
| } |
| |
| bool FrameNodeImpl::has_nonempty_beforeunload() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return document_.has_nonempty_beforeunload; |
| } |
| |
| const GURL& FrameNodeImpl::url() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return document_.url.value(); |
| } |
| |
| bool FrameNodeImpl::is_current() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return is_current_.value(); |
| } |
| |
| bool FrameNodeImpl::network_almost_idle() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return document_.network_almost_idle.value(); |
| } |
| |
| bool FrameNodeImpl::is_ad_frame() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return is_ad_frame_.value(); |
| } |
| |
| bool FrameNodeImpl::holds_web_lock() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return holds_web_lock_.value(); |
| } |
| |
| bool FrameNodeImpl::holds_indexed_db_lock() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return holds_indexed_db_lock_.value(); |
| } |
| |
| const base::flat_set<WorkerNodeImpl*>& FrameNodeImpl::child_worker_nodes() |
| const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return child_worker_nodes_; |
| } |
| |
| const PriorityAndReason& FrameNodeImpl::priority_and_reason() const { |
| return priority_and_reason_.value(); |
| } |
| |
| void FrameNodeImpl::SetIsCurrent(bool is_current) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| is_current_.SetAndMaybeNotify(this, is_current); |
| |
| #if DCHECK_IS_ON() |
| // We maintain an invariant that of all sibling nodes with the same |
| // |frame_tree_node_id|, at most one may be current. |
| if (is_current) { |
| const base::flat_set<FrameNodeImpl*>* siblings = nullptr; |
| if (parent_frame_node_) { |
| siblings = &parent_frame_node_->child_frame_nodes(); |
| } else { |
| siblings = &page_node_->main_frame_nodes(); |
| } |
| |
| size_t current_siblings = 0; |
| for (auto* frame : *siblings) { |
| if (frame->frame_tree_node_id() == frame_tree_node_id_ && |
| frame->is_current()) { |
| ++current_siblings; |
| } |
| } |
| DCHECK_EQ(1u, current_siblings); |
| } |
| #endif |
| } |
| |
| void FrameNodeImpl::SetHoldsWebLock(bool holds_web_lock) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(holds_web_lock, holds_web_lock_.value()); |
| holds_web_lock_.SetAndMaybeNotify(this, holds_web_lock); |
| } |
| |
| void FrameNodeImpl::SetHoldsIndexedDBLock(bool holds_indexed_db_lock) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(holds_indexed_db_lock, holds_indexed_db_lock_.value()); |
| holds_indexed_db_lock_.SetAndMaybeNotify(this, holds_indexed_db_lock); |
| } |
| |
| void FrameNodeImpl::OnNavigationCommitted(const GURL& url, bool same_document) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (same_document) { |
| document_.url.SetAndMaybeNotify(this, url); |
| return; |
| } |
| |
| // Close |receiver_| to ensure that messages queued by the previous document |
| // before the navigation commit are dropped. |
| // |
| // Note: It is guaranteed that |receiver_| isn't yet bound to the new |
| // document. |
| // This is important because it would be incorrect to close the new |
| // document's binding. |
| // |
| // Renderer: blink::DocumentLoader::DidCommitNavigation |
| // ... content::RenderFrameImpl::DidCommitProvisionalLoad |
| // ... mojom::FrameHost::DidCommitProvisionalLoad |
| // Browser: RenderFrameHostImpl::DidCommitNavigation |
| // Bind the new document's interface provider [A] |
| // PMTabHelper::DidFinishNavigation |
| // (async) FrameNodeImpl::OnNavigationCommitted [B] |
| // Renderer: Request DocumentCoordinationUnit interface |
| // Browser: PMTabHelper::OnInterfaceRequestFromFrame [C] |
| // (async) FrameNodeImpl::Bind [D] |
| // |
| // A happens before C, because no interface request can be processed |
| // before the interface provider is bound. A posts B to PM sequence and |
| // C posts D to PM sequence, therefore B happens before D. |
| receiver_.reset(); |
| |
| // Reset properties. |
| document_.Reset(this, url); |
| } |
| |
| void FrameNodeImpl::AddChildWorker(WorkerNodeImpl* worker_node) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| bool inserted = child_worker_nodes_.insert(worker_node).second; |
| DCHECK(inserted); |
| } |
| |
| void FrameNodeImpl::RemoveChildWorker(WorkerNodeImpl* worker_node) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| size_t removed = child_worker_nodes_.erase(worker_node); |
| DCHECK_EQ(1u, removed); |
| } |
| |
| void FrameNodeImpl::SetPriorityAndReason( |
| const PriorityAndReason& priority_and_reason) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| priority_and_reason_.SetAndMaybeNotify(this, priority_and_reason); |
| } |
| |
| const FrameNode* FrameNodeImpl::GetParentFrameNode() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return parent_frame_node(); |
| } |
| |
| const PageNode* FrameNodeImpl::GetPageNode() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return page_node(); |
| } |
| |
| const ProcessNode* FrameNodeImpl::GetProcessNode() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return process_node(); |
| } |
| |
| int FrameNodeImpl::GetFrameTreeNodeId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return frame_tree_node_id(); |
| } |
| |
| const base::UnguessableToken& FrameNodeImpl::GetDevToolsToken() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return dev_tools_token(); |
| } |
| |
| int32_t FrameNodeImpl::GetBrowsingInstanceId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return browsing_instance_id(); |
| } |
| |
| int32_t FrameNodeImpl::GetSiteInstanceId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return site_instance_id(); |
| } |
| |
| const base::flat_set<const FrameNode*> FrameNodeImpl::GetChildFrameNodes() |
| const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::flat_set<const FrameNode*> children; |
| for (auto* child : child_frame_nodes()) |
| children.insert(static_cast<const FrameNode*>(child)); |
| DCHECK_EQ(children.size(), child_frame_nodes().size()); |
| return children; |
| } |
| |
| FrameNodeImpl::LifecycleState FrameNodeImpl::GetLifecycleState() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return lifecycle_state(); |
| } |
| |
| FrameNodeImpl::InterventionPolicy FrameNodeImpl::GetOriginTrialFreezePolicy() |
| const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return origin_trial_freeze_policy(); |
| } |
| |
| bool FrameNodeImpl::HasNonemptyBeforeUnload() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return has_nonempty_beforeunload(); |
| } |
| |
| const GURL& FrameNodeImpl::GetURL() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return url(); |
| } |
| |
| bool FrameNodeImpl::IsCurrent() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return is_current(); |
| } |
| |
| bool FrameNodeImpl::GetNetworkAlmostIdle() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return network_almost_idle(); |
| } |
| |
| bool FrameNodeImpl::IsAdFrame() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return is_ad_frame(); |
| } |
| |
| bool FrameNodeImpl::HoldsWebLock() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return holds_web_lock(); |
| } |
| |
| bool FrameNodeImpl::HoldsIndexedDBLock() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return holds_indexed_db_lock(); |
| } |
| |
| const base::flat_set<const WorkerNode*> FrameNodeImpl::GetChildWorkerNodes() |
| const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::flat_set<const WorkerNode*> children; |
| for (auto* child : child_worker_nodes()) |
| children.insert(static_cast<const WorkerNode*>(child)); |
| DCHECK_EQ(children.size(), child_worker_nodes().size()); |
| return children; |
| } |
| |
| const PriorityAndReason& FrameNodeImpl::GetPriorityAndReason() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return priority_and_reason(); |
| } |
| |
| void FrameNodeImpl::AddChildFrame(FrameNodeImpl* child_frame_node) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(child_frame_node); |
| DCHECK_EQ(this, child_frame_node->parent_frame_node()); |
| DCHECK_NE(this, child_frame_node); |
| DCHECK(graph()->NodeInGraph(child_frame_node)); |
| DCHECK(!HasFrameNodeInAncestors(child_frame_node) && |
| !child_frame_node->HasFrameNodeInDescendants(this)); |
| |
| bool inserted = child_frame_nodes_.insert(child_frame_node).second; |
| DCHECK(inserted); |
| } |
| |
| void FrameNodeImpl::RemoveChildFrame(FrameNodeImpl* child_frame_node) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(child_frame_node); |
| DCHECK_EQ(this, child_frame_node->parent_frame_node()); |
| DCHECK_NE(this, child_frame_node); |
| DCHECK(graph()->NodeInGraph(child_frame_node)); |
| |
| size_t removed = child_frame_nodes_.erase(child_frame_node); |
| DCHECK_EQ(1u, removed); |
| } |
| |
| void FrameNodeImpl::JoinGraph() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Enable querying this node using process and frame routing ids. |
| graph()->RegisterFrameNodeForId(process_node_->GetRenderProcessId(), |
| render_frame_id_, this); |
| |
| // Wire this up to the other nodes in the graph. |
| if (parent_frame_node_) |
| parent_frame_node_->AddChildFrame(this); |
| page_node_->AddFrame(this); |
| process_node_->AddFrame(this); |
| |
| NodeBase::JoinGraph(); |
| } |
| |
| void FrameNodeImpl::LeaveGraph() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| NodeBase::LeaveGraph(); |
| |
| DCHECK(child_frame_nodes_.empty()); |
| |
| // Leave the page. |
| DCHECK(graph()->NodeInGraph(page_node_)); |
| page_node_->RemoveFrame(this); |
| |
| // Leave the frame hierarchy. |
| if (parent_frame_node_) { |
| DCHECK(graph()->NodeInGraph(parent_frame_node_)); |
| parent_frame_node_->RemoveChildFrame(this); |
| } |
| |
| // And leave the process. |
| DCHECK(graph()->NodeInGraph(process_node_)); |
| process_node_->RemoveFrame(this); |
| |
| // Disable querying this node using process and frame routing ids. |
| graph()->UnregisterFrameNodeForId(process_node_->GetRenderProcessId(), |
| render_frame_id_, this); |
| } |
| |
| bool FrameNodeImpl::HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (parent_frame_node_ == frame_node || |
| (parent_frame_node_ && |
| parent_frame_node_->HasFrameNodeInAncestors(frame_node))) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool FrameNodeImpl::HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| for (FrameNodeImpl* child : child_frame_nodes_) { |
| if (child == frame_node || child->HasFrameNodeInDescendants(frame_node)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| FrameNodeImpl::DocumentProperties::DocumentProperties() = default; |
| FrameNodeImpl::DocumentProperties::~DocumentProperties() = default; |
| |
| void FrameNodeImpl::DocumentProperties::Reset(FrameNodeImpl* frame_node, |
| const GURL& url_in) { |
| url.SetAndMaybeNotify(frame_node, url_in); |
| has_nonempty_beforeunload = false; |
| // Network is busy on navigation. |
| network_almost_idle.SetAndMaybeNotify(frame_node, false); |
| origin_trial_freeze_policy.SetAndMaybeNotify( |
| frame_node, resource_coordinator::mojom::InterventionPolicy::kUnknown); |
| } |
| |
| } // namespace performance_manager |