blob: 3335608037a851dcf5af92a0e9451c9f6a3b6d36 [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 <utility>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/dcheck_is_on.h"
#include "base/functional/bind.h"
#include "base/functional/overloaded.h"
#include "base/memory/raw_ptr.h"
#include "components/performance_manager/graph/graph_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/graph/worker_node_impl.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/frame/lifecycle.mojom.h"
#include "third_party/blink/public/mojom/frame/viewport_intersection_state.mojom.h"
namespace performance_manager {
namespace {
bool IsParentIntersectingLargeArea(FrameNodeImpl* frame_node) {
FrameNodeImpl* parent = frame_node->parent_frame_node();
if (!parent || parent->process_node() != frame_node->process_node()) {
// `frame_node is a local root. Assume it is intersecting with a large area
// of the viewport.
return true;
}
return parent->GetViewportIntersection() &&
parent->GetViewportIntersection()->is_intersecting_large_area();
}
} // namespace
// static
constexpr char FrameNodeImpl::kDefaultPriorityReason[] =
"default frame priority";
using PriorityAndReason = execution_context_priority::PriorityAndReason;
FrameNodeImpl::FrameNodeImpl(
ProcessNodeImpl* process_node,
PageNodeImpl* page_node,
FrameNodeImpl* parent_frame_node,
FrameNodeImpl* outer_document_for_inner_frame_root,
int render_frame_id,
const blink::LocalFrameToken& frame_token,
content::BrowsingInstanceId browsing_instance_id,
content::SiteInstanceGroupId site_instance_group_id,
bool is_current)
: parent_frame_node_(parent_frame_node),
outer_document_for_inner_frame_root_(outer_document_for_inner_frame_root),
page_node_(page_node),
process_node_(process_node),
render_frame_id_(render_frame_id),
frame_token_(frame_token),
browsing_instance_id_(browsing_instance_id),
site_instance_group_id_(site_instance_group_id),
render_frame_host_proxy_(content::GlobalRenderFrameHostId(
process_node->GetRenderProcessHostId().value(),
render_frame_id)),
is_current_(is_current) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(process_node);
DCHECK(page_node);
// A <fencedframe>, MPArch <webview> has no parent node.
CHECK(!outer_document_for_inner_frame_root_ || !parent_frame_node_);
}
FrameNodeImpl::~FrameNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(child_worker_nodes_.empty());
DCHECK(opened_page_nodes_.empty());
DCHECK(embedded_page_nodes_.empty());
}
void FrameNodeImpl::Bind(
mojo::PendingReceiver<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(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::SetIsAdFrame(bool is_ad_frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_ad_frame_.SetAndMaybeNotify(this, is_ad_frame);
}
void FrameNodeImpl::SetHadFormInteraction() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
document_.had_form_interaction.SetAndMaybeNotify(this, true);
}
void FrameNodeImpl::SetHadUserEdits() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
document_.had_user_edits.SetAndMaybeNotify(this, true);
}
void FrameNodeImpl::OnStartedUsingWebRTC() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
document_.uses_web_rtc.SetAndMaybeNotify(this, true);
}
void FrameNodeImpl::OnStoppedUsingWebRTC() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
document_.uses_web_rtc.SetAndMaybeNotify(this, false);
}
void FrameNodeImpl::OnNonPersistentNotificationCreated() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : GetObservers()) {
observer.OnNonPersistentNotificationCreated(this);
}
}
void FrameNodeImpl::OnFirstContentfulPaint(
base::TimeDelta time_since_navigation_start) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : GetObservers()) {
observer.OnFirstContentfulPaint(this, time_since_navigation_start);
}
}
void FrameNodeImpl::OnWebMemoryMeasurementRequested(
mojom::WebMemoryMeasurement::Mode mode,
OnWebMemoryMeasurementRequestedCallback callback) {
v8_memory::WebMeasureMemory(
this, mode, v8_memory::WebMeasureMemorySecurityChecker::Create(),
std::move(callback), mojo::GetBadMessageCallback());
}
void FrameNodeImpl::OnFreezingOriginTrialOptOut() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
document_.has_freezing_origin_trial_opt_out.SetAndMaybeNotify(this, true);
}
const blink::LocalFrameToken& FrameNodeImpl::GetFrameToken() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return frame_token_;
}
content::BrowsingInstanceId FrameNodeImpl::GetBrowsingInstanceId() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return browsing_instance_id_;
}
content::SiteInstanceGroupId FrameNodeImpl::GetSiteInstanceGroupId() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return site_instance_group_id_;
}
resource_attribution::FrameContext FrameNodeImpl::GetResourceContext() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return resource_attribution::FrameContext::FromFrameNode(this);
}
bool FrameNodeImpl::IsMainFrame() const {
return !parent_frame_node_;
}
FrameNodeImpl::LifecycleState FrameNodeImpl::GetLifecycleState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state_.value();
}
bool FrameNodeImpl::HasNonemptyBeforeUnload() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.has_nonempty_beforeunload;
}
const GURL& FrameNodeImpl::GetURL() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.url;
}
const std::optional<url::Origin>& FrameNodeImpl::GetOrigin() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.origin;
}
bool FrameNodeImpl::IsCurrent() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_current_;
}
const PriorityAndReason& FrameNodeImpl::GetPriorityAndReason() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return priority_and_reason_.value();
}
bool FrameNodeImpl::GetNetworkAlmostIdle() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.network_almost_idle.value();
}
bool FrameNodeImpl::IsAdFrame() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_ad_frame_.value();
}
bool FrameNodeImpl::IsHoldingWebLock() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_holding_weblock_.value();
}
bool FrameNodeImpl::IsHoldingBlockingIndexedDBLock() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_holding_blocking_indexeddb_lock_.value();
}
bool FrameNodeImpl::UsesWebRTC() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.uses_web_rtc.value();
}
bool FrameNodeImpl::HadUserActivation() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return had_user_activation_.value();
}
bool FrameNodeImpl::HadFormInteraction() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.had_form_interaction.value();
}
bool FrameNodeImpl::HadUserEdits() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.had_user_edits.value();
}
bool FrameNodeImpl::IsAudible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_audible_.value();
}
bool FrameNodeImpl::IsCapturingMediaStream() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_capturing_media_stream_.value();
}
bool FrameNodeImpl::HasFreezingOriginTrialOptOut() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return document_.has_freezing_origin_trial_opt_out.value();
}
std::optional<ViewportIntersection> FrameNodeImpl::GetViewportIntersection()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The outermost main frame or embedder is always fully intersecting with the
// viewport.
if (!parent_or_outer_document_or_embedder()) {
return std::make_optional<ViewportIntersection>(
ViewportIntersection::CreateIntersecting(
/*is_intersecting_large_area=*/true));
}
return viewport_intersection_.value();
}
FrameNode::Visibility FrameNodeImpl::GetVisibility() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return visibility_.value();
}
bool FrameNodeImpl::IsImportant() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_important_.value();
}
const RenderFrameHostProxy& FrameNodeImpl::GetRenderFrameHostProxy() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return render_frame_host_proxy_;
}
uint64_t FrameNodeImpl::GetResidentSetKbEstimate() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return resident_set_kb_estimate_;
}
uint64_t FrameNodeImpl::GetPrivateFootprintKbEstimate() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return private_footprint_kb_estimate_;
}
FrameNodeImpl* FrameNodeImpl::parent_frame_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return parent_frame_node_;
}
FrameNodeImpl* FrameNodeImpl::parent_or_outer_document_or_embedder() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (parent_frame_node_) {
return parent_frame_node_;
}
if (outer_document_for_inner_frame_root_) {
return outer_document_for_inner_frame_root_;
}
if (page_node()->embedder_frame_node()) {
return page_node()->embedder_frame_node();
}
return nullptr;
}
PageNodeImpl* FrameNodeImpl::page_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return page_node_;
}
ProcessNodeImpl* FrameNodeImpl::process_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return process_node_;
}
int FrameNodeImpl::render_frame_id() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return render_frame_id_;
}
FrameNode::NodeSetView<FrameNodeImpl*> FrameNodeImpl::child_frame_nodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return NodeSetView<FrameNodeImpl*>(child_frame_nodes_);
}
FrameNode::NodeSetView<PageNodeImpl*> FrameNodeImpl::opened_page_nodes() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return NodeSetView<PageNodeImpl*>(opened_page_nodes_);
}
FrameNode::NodeSetView<PageNodeImpl*> FrameNodeImpl::embedded_page_nodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return NodeSetView<PageNodeImpl*>(embedded_page_nodes_);
}
FrameNode::NodeSetView<WorkerNodeImpl*> FrameNodeImpl::child_worker_nodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return NodeSetView<WorkerNodeImpl*>(child_worker_nodes_);
}
// static
void FrameNodeImpl::UpdateCurrentFrame(FrameNodeImpl* previous_frame_node,
FrameNodeImpl* current_frame_node,
GraphImpl* graph) {
if (previous_frame_node) {
bool did_change = previous_frame_node->SetIsCurrent(false);
// Don't notify if the frame was already not current.
if (!did_change) {
previous_frame_node = nullptr;
}
}
if (current_frame_node) {
bool did_change = current_frame_node->SetIsCurrent(true);
// Don't notify if the frame was already current.
if (!did_change) {
current_frame_node = nullptr;
}
}
// No need to notify observers.
if (!previous_frame_node && !current_frame_node) {
return;
}
// Notify observers.
for (auto& observer : graph->GetObservers<FrameNodeObserver>()) {
observer.OnCurrentFrameChanged(previous_frame_node, current_frame_node);
}
// TODO(crbug.com/40182881): We maintain an invariant that of all sibling
// frame nodes in the same FrameTreeNode, at most one may be current. We used
// to save the RenderFrameHost's `frame_tree_node_id` at FrameNode creation
// time to check this invariant, but prerendering RenderFrameHost's can be
// moved to a new FrameTreeNode when they're activated so the
// `frame_tree_node_id` can go out of date. Because of this,
// RenderFrameHost::GetFrameTreeNodeId() is being deprecated. (See the
// discussion at crbug.com/1179502 and in the comment thread at
// https://chromium-review.googlesource.com/c/chromium/src/+/2966195/comments/58550eac_5795f790
// for more details.) We need to find another way to check this invariant
// here. (altimin suggests simply relying on RFH::GetLifecycleState to
// correctly track "active" frame nodes instead of using "current", and not
// checking this invariant.)
}
void FrameNodeImpl::SetHadUserActivation() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
had_user_activation_.SetAndMaybeNotify(this, true);
}
void FrameNodeImpl::SetIsHoldingWebLock(bool is_holding_weblock) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(is_holding_weblock, is_holding_weblock_.value());
is_holding_weblock_.SetAndMaybeNotify(this, is_holding_weblock);
}
void FrameNodeImpl::SetIsHoldingBlockingIndexedDBLock(
bool is_holding_blocking_indexeddb_lock) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(is_holding_blocking_indexeddb_lock,
is_holding_blocking_indexeddb_lock_.value());
is_holding_blocking_indexeddb_lock_.SetAndMaybeNotify(
this, is_holding_blocking_indexeddb_lock);
}
void FrameNodeImpl::SetIsAudible(bool is_audible) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(is_audible, is_audible_.value());
is_audible_.SetAndMaybeNotify(this, is_audible);
}
void FrameNodeImpl::SetIsCapturingMediaStream(bool is_capturing_media_stream) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(is_capturing_media_stream, is_capturing_media_stream_.value());
is_capturing_media_stream_.SetAndMaybeNotify(this, is_capturing_media_stream);
}
void FrameNodeImpl::SetViewportIntersection(
const blink::mojom::ViewportIntersectionState&
viewport_intersection_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_viewport_intersection_updates_ = true;
const gfx::Rect& viewport_intersection =
viewport_intersection_state.viewport_intersection;
if (viewport_intersection.IsEmpty()) {
SetViewportIntersectionImpl(ViewportIntersection::CreateNotIntersecting());
return;
}
int viewport_intersect_area =
viewport_intersection.size().GetCheckedArea().ValueOrDefault(INT_MAX);
int outermost_main_frame_area =
viewport_intersection_state.outermost_main_frame_size.GetCheckedArea()
.ValueOrDefault(INT_MAX);
float ratio = 1.0f * viewport_intersect_area / outermost_main_frame_area;
const float ratio_threshold =
blink::features::kLargeFrameSizePercentThreshold.Get() / 100.f;
bool is_intersecting_large_area = ratio > ratio_threshold;
SetViewportIntersectionImpl(
ViewportIntersection::CreateIntersecting(is_intersecting_large_area));
}
void FrameNodeImpl::SetViewportIntersection(
blink::mojom::FrameVisibility frame_visibility) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If this frame is getting blink::mojom::ViewportIntersectionState updates,
// then ignore blink::mojom::FrameVisibility updates. The latter is basically
// a subset of the former.
if (has_viewport_intersection_updates_) {
return;
}
bool is_intersecting_viewport = [&]() {
switch (frame_visibility) {
case blink::mojom::FrameVisibility::kNotRendered:
case blink::mojom::FrameVisibility::kRenderedOutOfViewport:
return false;
case blink::mojom::FrameVisibility::kRenderedInViewport:
// Since we don't know if this frame is intersecting with a large area
// of the viewport, it'll be inherited from the parent.
return true;
}
NOTREACHED();
}();
SetViewportIntersectionImpl(is_intersecting_viewport);
}
void FrameNodeImpl::SetInitialVisibility(Visibility visibility) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
visibility_.Set(this, visibility);
}
void FrameNodeImpl::SetVisibility(Visibility visibility) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
visibility_.SetAndMaybeNotify(this, visibility);
}
void FrameNodeImpl::SetIsImportant(bool is_important) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_important_.SetAndMaybeNotify(this, is_important);
}
void FrameNodeImpl::SetResidentSetKbEstimate(uint64_t rss_estimate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
resident_set_kb_estimate_ = rss_estimate;
}
void FrameNodeImpl::SetPrivateFootprintKbEstimate(
uint64_t private_footprint_estimate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
private_footprint_kb_estimate_ = private_footprint_estimate;
}
void FrameNodeImpl::OnNavigationCommitted(
GURL url,
url::Origin origin,
bool same_document,
bool is_served_from_back_forward_cache) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (same_document) {
DCHECK(CanSetAndNotifyProperty());
url = std::exchange(document_.url, std::move(url));
if (url != document_.url) {
for (auto& observer : GetObservers()) {
observer.OnURLChanged(this, /*previous_value=*/url);
}
}
return;
}
// If this frame is being served from the back-forward cache, then this frame
// does not host a new document. We don't need to reset the document's
// properties, and more importantly, we can't reset the `receiver_` as there
// won't be another Bind() request to rebind it.
if (is_served_from_back_forward_cache) {
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, std::move(url), std::move(origin));
}
void FrameNodeImpl::OnPrimaryPageAboutToBeDiscarded() {
// When a page is discarded by the browser, it is immediately marked as
// discarded and kicks off an async process to install a new empty document,
// clearing the existing frame tree.
//
// Close `receiver_` to ensure that messages queued by the previous document
// before the discard are dropped.
receiver_.reset();
for (const Node* child_frame_node : child_frame_nodes_) {
FrameNodeImpl::FromNode(child_frame_node)
->OnPrimaryPageAboutToBeDiscarded();
}
for (const Node* embedded_page_node : embedded_page_nodes_) {
if (FrameNodeImpl* main_frame_node =
PageNodeImpl::FromNode(embedded_page_node)->main_frame_node()) {
main_frame_node->OnPrimaryPageAboutToBeDiscarded();
}
}
}
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_);
// This is also called during initialization to set the initial value. In this
// case, do not notify the observers as they aren't even aware of this frame
// node anyways.
if (CanSetProperty()) {
priority_and_reason_.Set(this, priority_and_reason);
return;
}
priority_and_reason_.SetAndMaybeNotify(this, priority_and_reason);
}
base::WeakPtr<FrameNodeImpl> FrameNodeImpl::GetWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_factory_.GetWeakPtr();
}
void FrameNodeImpl::AddOpenedPage(base::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(base::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);
}
void FrameNodeImpl::AddEmbeddedPage(base::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->embedder_frame_node());
bool inserted = embedded_page_nodes_.insert(page_node).second;
DCHECK(inserted);
}
void FrameNodeImpl::RemoveEmbeddedPage(base::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->embedder_frame_node());
size_t removed = embedded_page_nodes_.erase(page_node);
DCHECK_EQ(1u, removed);
}
void FrameNodeImpl::SetViewportIntersectionForTesting(
bool is_intersecting_viewport) {
SetViewportIntersectionImpl(is_intersecting_viewport);
}
void FrameNodeImpl::SetViewportIntersectionForTesting(
ViewportIntersection viewport_intersection) {
SetViewportIntersectionImpl(viewport_intersection);
}
const FrameNode* FrameNodeImpl::GetParentFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return graph()->NodeEdgesArePublic(this) ? parent_frame_node() : nullptr;
}
const FrameNode* FrameNodeImpl::GetParentOrOuterDocumentOrEmbedder() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return graph()->NodeEdgesArePublic(this)
? parent_or_outer_document_or_embedder()
: nullptr;
}
const PageNode* FrameNodeImpl::GetPageNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return graph()->NodeEdgesArePublic(this) ? page_node() : nullptr;
}
const ProcessNode* FrameNodeImpl::GetProcessNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return graph()->NodeEdgesArePublic(this) ? process_node() : nullptr;
}
FrameNode::NodeSetView<const FrameNode*> FrameNodeImpl::GetChildFrameNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || child_frame_nodes_.empty());
return NodeSetView<const FrameNode*>(child_frame_nodes_);
}
FrameNode::NodeSetView<const PageNode*> FrameNodeImpl::GetOpenedPageNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || opened_page_nodes_.empty());
return NodeSetView<const PageNode*>(opened_page_nodes_);
}
FrameNode::NodeSetView<const PageNode*> FrameNodeImpl::GetEmbeddedPageNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || embedded_page_nodes_.empty());
return NodeSetView<const PageNode*>(embedded_page_nodes_);
}
FrameNode::NodeSetView<const WorkerNode*> FrameNodeImpl::GetChildWorkerNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || child_worker_nodes_.empty());
return NodeSetView<const WorkerNode*>(child_worker_nodes_);
}
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::OnInitializingProperties() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NodeAttachedDataStorage::Create(this);
execution_context::FrameExecutionContext::Create(this, this);
// Enable querying this node using process and frame routing ids.
graph()->RegisterFrameNodeForId(process_node_->GetRenderProcessHostId(),
render_frame_id_, this);
}
void FrameNodeImpl::OnInitializingEdges() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Wire this up to the other nodes in the graph.
if (parent_frame_node_)
parent_frame_node_->AddChildFrame(this);
page_node_->AddFrame(base::PassKey<FrameNodeImpl>(), this);
process_node_->AddFrame(this);
}
void FrameNodeImpl::OnBeforeLeavingGraph() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(child_frame_nodes_.empty());
SeverPageRelationshipsAndMaybeReparent();
}
void FrameNodeImpl::OnUninitializingEdges() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(child_frame_nodes_.empty());
// Leave the page.
DCHECK(graph()->NodeInGraph(page_node_));
page_node_->RemoveFrame(base::PassKey<FrameNodeImpl>(), 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);
}
void FrameNodeImpl::CleanUpNodeState() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Disable querying this node using process and frame routing ids.
graph()->UnregisterFrameNodeForId(process_node_->GetRenderProcessHostId(),
render_frame_id_, this);
DestroyNodeInlineDataStorage();
}
void FrameNodeImpl::SeverPageRelationshipsAndMaybeReparent() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Be careful when iterating: when we call
// PageNodeImpl::(Set|Clear)(Opener|Embedder)FrameNode() this will call
// back into this frame node and call Remove(Opened|Embedded)Page(), which
// modifies |opened_page_nodes_| and |embedded_page_nodes_|.
//
// We also reparent related pages to this frame's parent to maintain the
// relationship between the distinct frame trees for bookkeeping. For the
// relationship to be finally severed one of the frame trees must completely
// disappear.
NodeSet opened_page_nodes_copy = opened_page_nodes_;
for (const Node* opened_page_node : opened_page_nodes_copy) {
PageNodeImpl* opened_page = PageNodeImpl::FromNode(opened_page_node);
if (parent_frame_node_) {
opened_page->SetOpenerFrameNode(parent_frame_node_);
} else {
opened_page->ClearOpenerFrameNode();
}
}
NodeSet embedded_page_nodes_copy = embedded_page_nodes_;
for (const Node* embedded_page_node : embedded_page_nodes_copy) {
PageNodeImpl* embedded_page = PageNodeImpl::FromNode(embedded_page_node);
auto embedding_type = embedded_page->GetEmbeddingType();
if (parent_frame_node_) {
embedded_page->SetEmbedderFrameNodeAndEmbeddingType(parent_frame_node_,
embedding_type);
} else {
embedded_page->ClearEmbedderFrameNodeAndEmbeddingType();
}
}
// Expect each page node to have called RemoveEmbeddedPage(), and for this to
// now be empty.
DCHECK(opened_page_nodes_.empty());
DCHECK(embedded_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 ||
(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;
}
bool FrameNodeImpl::HasFrameNodeInTree(FrameNodeImpl* frame_node) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return GetFrameTreeRoot() == frame_node->GetFrameTreeRoot();
}
bool FrameNodeImpl::SetIsCurrent(bool is_current) {
CHECK(CanSetAndNotifyProperty());
bool was_current = std::exchange(is_current_, is_current);
return was_current != is_current_;
}
void FrameNodeImpl::SetViewportIntersectionImpl(bool is_intersecting_viewport) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The outermost main frame or embedder is always fully intersecting with the
// viewport, so it is not tracked.
CHECK(parent_or_outer_document_or_embedder());
ViewportIntersection viewport_intersection = [&, this]() {
if (is_intersecting_viewport) {
// An intersecting viewport intersection needs to inherit its
// `is_intersecting_large_area` bit from its parent.
return ViewportIntersection::CreateIntersecting(
IsParentIntersectingLargeArea(this));
} else {
return ViewportIntersection::CreateNotIntersecting();
}
}();
SetViewportIntersectionImpl(viewport_intersection);
}
void FrameNodeImpl::SetViewportIntersectionImpl(
ViewportIntersection viewport_intersection) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Nothing to do if the value didn't change.
if (GetViewportIntersection() &&
GetViewportIntersection().value() == viewport_intersection) {
return;
}
viewport_intersection_.SetAndMaybeNotify(this, viewport_intersection);
// Child frames can be inheriting the `is_intersecting_large_area` bit from
// their parent. Update them if this frame's value change.
if (viewport_intersection.is_intersecting()) {
for (FrameNodeImpl* child_frame_node : child_frame_nodes()) {
if (child_frame_node->process_node() == process_node()) {
child_frame_node->SetInheritedIsIntersectingLargeArea(
viewport_intersection.is_intersecting_large_area());
}
}
}
}
void FrameNodeImpl::SetInheritedIsIntersectingLargeArea(
bool is_intersecting_large_area) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Since this frame's viewport intersection is derived from an accurate
// blink::mojom::ViewportIntersectionState update, it doesn't have to inherit
// the `is_intersecting_large_area` bit from its parent.
if (has_viewport_intersection_updates_) {
return;
}
std::optional<ViewportIntersection> viewport_intersection =
GetViewportIntersection();
if (!viewport_intersection) {
return;
}
if (!viewport_intersection->is_intersecting()) {
return;
}
SetViewportIntersectionImpl(
ViewportIntersection::CreateIntersecting(is_intersecting_large_area));
}
FrameNodeImpl::DocumentProperties::DocumentProperties() = default;
FrameNodeImpl::DocumentProperties::~DocumentProperties() = default;
// LINT.IfChange(document_prop_reset)
void FrameNodeImpl::DocumentProperties::Reset(FrameNodeImpl* frame_node,
GURL url_in,
url::Origin origin_in) {
DCHECK(frame_node->CanSetAndNotifyProperty());
// Update the URL and origin properties.
url_in = std::exchange(url, std::move(url_in));
std::optional<url::Origin> previous_origin = std::move(origin);
origin = std::move(origin_in);
// Notify observers of the URL and origin change after updating both
// properties, to avoid having observers that mistakenly access the URL and
// origin of different documents.
if (url != url_in) {
for (auto& observer : frame_node->GetObservers()) {
observer.OnURLChanged(frame_node, /*previous_value=*/url_in);
}
}
if (origin != previous_origin) {
for (auto& observer : frame_node->GetObservers()) {
observer.OnOriginChanged(frame_node, /*previous_value=*/previous_origin);
}
}
// Update other properties.
has_nonempty_beforeunload = false;
// Network is busy on navigation.
network_almost_idle.SetAndMaybeNotify(frame_node, false);
had_form_interaction.SetAndMaybeNotify(frame_node, false);
had_user_edits.SetAndMaybeNotify(frame_node, false);
uses_web_rtc.SetAndMaybeNotify(frame_node, false);
has_freezing_origin_trial_opt_out.SetAndMaybeNotify(frame_node, false);
}
// LINT.ThenChange()
} // namespace performance_manager