blob: 17e92b79242de99a900e758bd70c0b91f7312172 [file] [log] [blame]
// Copyright 2019 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/performance_manager_tab_helper.h"
#include <type_traits>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/performance_manager_impl.h"
#include "components/performance_manager/performance_manager_registry_impl.h"
#include "components/performance_manager/render_process_user_data.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "url/origin.h"
namespace performance_manager {
namespace {
// Returns true if the opener relationship exists, false otherwise.
bool ConnectWindowOpenRelationshipIfExists(PerformanceManagerTabHelper* helper,
content::WebContents* web_contents) {
// Prefer to use GetOpener() if available, as it is more specific and points
// directly to the frame that actually called window.open.
auto* opener_rfh = web_contents->GetOpener();
if (!opener_rfh) {
// If the child page is opened with "noopener" then the parent document
// maintains the ability to close the child, but the child can't reach back
// and see it's parent. In this case there will be no "opener", but there
// will be an "original opener".
if (content::WebContents* original_opener_wc =
web_contents->GetFirstWebContentsInLiveOriginalOpenerChain()) {
opener_rfh = original_opener_wc->GetPrimaryMainFrame();
}
}
if (!opener_rfh)
return false;
// Connect this new page to its opener.
auto* opener_wc = content::WebContents::FromRenderFrameHost(opener_rfh);
auto* opener_helper = PerformanceManagerTabHelper::FromWebContents(opener_wc);
DCHECK(opener_helper); // We should already have seen the opener WC.
// On CrOS the opener can be the ChromeKeyboardWebContents, whose RFHs never
// make it to a "created" state, so the PM never learns about them.
// https://crbug.com/1090374
auto* opener_frame_node = opener_helper->GetFrameNode(opener_rfh);
if (!opener_frame_node)
return false;
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetOpenerFrameNode,
base::Unretained(helper->primary_page_node()),
base::Unretained(opener_frame_node)));
return true;
}
} // namespace
PerformanceManagerTabHelper::PageData::PageData() = default;
PerformanceManagerTabHelper::PageData::~PageData() = default;
PerformanceManagerTabHelper::PerformanceManagerTabHelper(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<PerformanceManagerTabHelper>(*web_contents) {
// We have an early WebContents creation hook so should see it when there is
// only a single frame, and it is not yet created. We sanity check that here.
#if DCHECK_IS_ON()
DCHECK(!web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
size_t frame_count = 0;
web_contents->ForEachRenderFrameHost(
[&frame_count](content::RenderFrameHost* render_frame_host) {
++frame_count;
});
DCHECK_EQ(1u, frame_count);
#endif
PagePropertyFlags initial_property_flags;
if (web_contents->GetVisibility() == content::Visibility::VISIBLE) {
initial_property_flags.Put(PagePropertyFlag::kIsVisible);
}
if (web_contents->IsCurrentlyAudible()) {
initial_property_flags.Put(PagePropertyFlag::kIsAudible);
}
if (web_contents->HasPictureInPictureVideo() ||
web_contents->HasPictureInPictureDocument()) {
initial_property_flags.Put(PagePropertyFlag::kHasPictureInPicture);
}
// Create the page node.
std::unique_ptr<PageData> page = std::make_unique<PageData>();
page->page_node = PerformanceManagerImpl::CreatePageNode(
WebContentsProxy(web_contents->GetWeakPtr()),
web_contents->GetBrowserContext()->UniqueId(),
web_contents->GetVisibleURL(), initial_property_flags,
web_contents->GetLastActiveTime(),
// TODO(crbug.com/40182881): Support MPArch fully!
PageNode::PageState::kActive);
content::RenderFrameHost* main_rfh = web_contents->GetPrimaryMainFrame();
DCHECK(main_rfh);
primary_page_ = page.get();
auto result = pages_.insert(std::move(page));
DCHECK(result.second);
ConnectWindowOpenRelationshipIfExists(this, web_contents);
}
PerformanceManagerTabHelper::~PerformanceManagerTabHelper() {
DCHECK(pages_.empty());
DCHECK(!primary_page_);
DCHECK(frames_.empty());
}
void PerformanceManagerTabHelper::TearDown() {
// Ship our page and frame nodes to the PerformanceManagerImpl for
// incineration.
std::vector<std::unique_ptr<NodeBase>> nodes;
for (auto& kv : frames_) {
std::unique_ptr<FrameNodeImpl> frame_node = std::move(kv.second);
// Notify observers.
for (Observer& observer : observers_)
observer.OnBeforeFrameNodeRemoved(this, frame_node.get());
// Ensure the node will be deleted on the graph sequence.
nodes.push_back(std::move(frame_node));
}
for (auto& kv : pages_) {
std::unique_ptr<PageNodeImpl> page_node = std::move(kv->page_node);
nodes.push_back(std::move(page_node));
}
// primary_page ptr should be cleared before pages_ is cleared, otherwise
// it becomes dangling.
primary_page_ = nullptr;
pages_.clear();
frames_.clear();
// Delete the page and its entire frame tree from the graph.
PerformanceManagerImpl::BatchDeleteNodes(std::move(nodes));
if (destruction_observer_) {
destruction_observer_->OnPerformanceManagerTabHelperDestroying(
web_contents());
}
MaybeUnsubscribeFromNotificationPermissionStatusChange(
web_contents()->GetBrowserContext()->GetPermissionController());
// Unsubscribe from the associated WebContents.
Observe(nullptr);
}
PageNodeImpl* PerformanceManagerTabHelper::GetPageNodeForRenderFrameHost(
content::RenderFrameHost* rfh) {
DCHECK_NE(nullptr, rfh);
// TODO(crbug.com/40182881): Make this lookup the appropriate PageNode once
// MPArch support is completed. For now, everything is artifically descended
// from the primary page node. Add tests for this function at that point.
auto* wc = content::WebContents::FromRenderFrameHost(rfh);
if (wc != web_contents())
return nullptr;
return primary_page_node();
}
void PerformanceManagerTabHelper::SetDestructionObserver(
DestructionObserver* destruction_observer) {
DCHECK(!destruction_observer || !destruction_observer_);
destruction_observer_ = destruction_observer;
}
void PerformanceManagerTabHelper::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
DCHECK_NE(nullptr, render_frame_host);
// This must not exist in the map yet.
DCHECK(!base::Contains(frames_, render_frame_host));
content::RenderFrameHost* parent = render_frame_host->GetParent();
FrameNodeImpl* parent_frame_node = nullptr;
if (parent) {
DCHECK(base::Contains(frames_, parent));
parent_frame_node = frames_[parent].get();
}
// Get the outer document for a <fencedframe>.
FrameNodeImpl* outer_document_for_fenced_frame = nullptr;
if (render_frame_host->IsFencedFrameRoot()) {
CHECK(!parent_frame_node);
content::RenderFrameHost* outer_document =
render_frame_host->GetParentOrOuterDocument();
CHECK(outer_document);
outer_document_for_fenced_frame = GetExistingFrameNode(outer_document);
}
// Ideally, creation would not be required here, but it is possible in tests
// for the RenderProcessUserData to not have attached at this point.
PerformanceManagerRegistryImpl::GetInstance()
->EnsureProcessNodeForRenderProcessHost(render_frame_host->GetProcess());
auto* process_node = RenderProcessUserData::GetForRenderProcessHost(
render_frame_host->GetProcess())
->process_node();
auto* site_instance = render_frame_host->GetSiteInstance();
// Create the frame node, and provide a callback that will run in the graph to
// initialize it.
// TODO(crbug.com/40182881): Actually look up the appropriate page to wire
// this frame up to!
std::unique_ptr<FrameNodeImpl> frame =
PerformanceManagerImpl::CreateFrameNode(
process_node, primary_page_node(), parent_frame_node,
outer_document_for_fenced_frame, render_frame_host->GetRoutingID(),
blink::LocalFrameToken(render_frame_host->GetFrameToken()),
site_instance->GetBrowsingInstanceId(), site_instance->GetId(),
render_frame_host->IsActive(),
base::BindOnce(
[](GURL url, url::Origin origin, FrameNodeImpl* frame_node) {
if (!url.is_empty())
frame_node->OnNavigationCommitted(std::move(url),
std::move(origin),
/*same_document=*/false);
},
render_frame_host->GetLastCommittedURL(),
render_frame_host->GetLastCommittedOrigin()));
frames_[render_frame_host] = std::move(frame);
}
void PerformanceManagerTabHelper::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
auto it = frames_.find(render_frame_host);
DCHECK(it != frames_.end());
std::unique_ptr<FrameNodeImpl> frame_node = std::move(it->second);
// Notify observers.
for (Observer& observer : observers_)
observer.OnBeforeFrameNodeRemoved(this, frame_node.get());
// Then delete the node.
PerformanceManagerImpl::DeleteNode(std::move(frame_node));
frames_.erase(it);
}
void PerformanceManagerTabHelper::RenderFrameHostChanged(
content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) {
// |old_host| is null when a new frame tree position is being created and a
// new frame is its first occupant.
FrameNodeImpl* old_frame = nullptr;
if (old_host) {
auto it = frames_.find(old_host);
if (it != frames_.end()) {
// This can be received for a frame that hasn't yet been created. We can
// safely ignore this. It would be nice to track those frames too, but
// since they're not yet "created" we'd have no guarantee of seeing a
// corresponding delete and the frames can be leaked.
old_frame = it->second.get();
}
}
// It's entirely possible that this is the first time we're seeing this frame.
// We'll eventually see a corresponding RenderFrameCreated if the frame ends
// up actually being needed, so we can ignore it until that time. Artificially
// creating the frame causes problems because we aren't guaranteed to see a
// subsequent RenderFrameCreated call, meaning we won't see a
// RenderFrameDeleted, and the frame node will be leaked until process tear
// down.
DCHECK(new_host);
FrameNodeImpl* new_frame = nullptr;
auto it = frames_.find(new_host);
if (it != frames_.end()) {
new_frame = it->second.get();
} else {
DCHECK(!new_host->IsRenderFrameLive())
<< "There shouldn't be a case where RenderFrameHostChanged is "
"dispatched before RenderFrameCreated with a live RenderFrame\n";
}
// If neither frame could be looked up there's nothing to do.
if (!old_frame && !new_frame)
return;
// Perform the swap in the graph.
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(
[](FrameNodeImpl* old_frame, FrameNodeImpl* new_frame) {
if (old_frame) {
// Prerendering is a special case where
// old_frame->is_current() may be false.
// TODO(crbug.com/40182881): assert that
// old_frame->is_current() or its PageState is
// kPrerendering.
old_frame->SetIsCurrent(false);
}
if (new_frame) {
// The very first frame to be created is already
// current by default except in the special case of
// prerendering.
// TODO(crbug.com/40182881): assert that
// old_frame is null or its PageState is kPrerendering.
new_frame->SetIsCurrent(true);
}
},
old_frame, new_frame));
}
void PerformanceManagerTabHelper::OnVisibilityChanged(
content::Visibility visibility) {
const bool is_visible = visibility == content::Visibility::VISIBLE;
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::SetIsVisible,
base::Unretained(primary_page_node()), is_visible));
}
void PerformanceManagerTabHelper::OnAudioStateChanged(bool audible) {
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::SetIsAudible,
base::Unretained(primary_page_node()), audible));
}
void PerformanceManagerTabHelper::OnFrameAudioStateChanged(
content::RenderFrameHost* render_frame_host,
bool is_audible) {
auto frame_it = frames_.find(render_frame_host);
// Ideally this would be a DCHECK, but it's possible to receive a notification
// for an unknown frame.
// TODO(crbug.com/40940232): Figure out how.
if (frame_it == frames_.end()) {
// We should only ever see this for a frame transitioning to *not* audible.
DCHECK(!is_audible);
return;
}
auto* frame_node = frame_it->second.get();
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&FrameNodeImpl::SetIsAudible,
base::Unretained(frame_node), is_audible));
}
void PerformanceManagerTabHelper::OnFrameVisibilityChanged(
content::RenderFrameHost* render_frame_host,
blink::mojom::FrameVisibility visibility) {
auto frame_it = frames_.find(render_frame_host);
// This can be invoked for a crashed RenderFrameHost, as its view still
// occupies space on the page. Just ignore it as clearly its content is not
// visible.
if (frame_it == frames_.end()) {
CHECK(!render_frame_host->IsRenderFrameLive());
return;
}
CHECK(render_frame_host->IsRenderFrameLive());
auto* frame_node = frame_it->second.get();
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(
&FrameNodeImpl::SetIntersectsViewport, base::Unretained(frame_node),
visibility == blink::mojom::FrameVisibility::kRenderedInViewport));
}
void PerformanceManagerTabHelper::OnFrameIsCapturingMediaStreamChanged(
content::RenderFrameHost* render_frame_host,
bool is_capturing_media_stream) {
// Ignore notifications that are received after the frame was deleted.
auto frame_it = frames_.find(render_frame_host);
if (frame_it == frames_.end()) {
return;
}
auto* frame_node = frame_it->second.get();
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&FrameNodeImpl::SetIsCapturingMediaStream,
base::Unretained(frame_node), is_capturing_media_stream));
}
void PerformanceManagerTabHelper::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted())
return;
// Grab the current time up front, as this is as close as we'll get to the
// original commit time.
base::TimeTicks navigation_committed_time = base::TimeTicks::Now();
// Find the associated frame node.
content::RenderFrameHost* render_frame_host =
navigation_handle->GetRenderFrameHost();
auto frame_it = frames_.find(render_frame_host);
// TODO(siggi): Ideally this would be a DCHECK, but it seems it's possible
// to get a DidFinishNavigation notification for a deleted frame with
// the network service.
if (frame_it == frames_.end())
return;
auto* frame_node = frame_it->second.get();
// Notify the frame of the committed URL.
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&FrameNodeImpl::OnNavigationCommitted,
base::Unretained(frame_node),
render_frame_host->GetLastCommittedURL(),
render_frame_host->GetLastCommittedOrigin(),
navigation_handle->IsSameDocument()));
if (!navigation_handle->IsInPrimaryMainFrame())
return;
// Make sure the hierarchical structure is constructed before sending signal
// to the performance manager.
OnMainFrameNavigation(navigation_handle->GetNavigationId());
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::OnMainFrameNavigationCommitted,
base::Unretained(primary_page_node()),
navigation_handle->IsSameDocument(),
navigation_committed_time,
navigation_handle->GetNavigationId(),
render_frame_host->GetLastCommittedURL(),
navigation_handle->GetWebContents()->GetContentsMimeType(),
GetNotificationPermissionStatusAndObserveChanges()));
}
std::optional<blink::mojom::PermissionStatus> PerformanceManagerTabHelper::
GetNotificationPermissionStatusAndObserveChanges() {
// Don't get the content settings on android on each navigation because it may
// induce scroll jank. There are many same-document navigations while
// scrolling and getting the settings can invoke expensive platform APIs on
// Android. Moreover, this information is only used to decide if a tab should
// be discarded, which doesn't happen through Chrome code on that platform.
#if BUILDFLAG(IS_ANDROID)
return std::nullopt;
#else
content::PermissionController* permission_controller =
web_contents()->GetBrowserContext()->GetPermissionController();
if (!permission_controller) {
CHECK(permission_controller_subscription_id_.is_null());
return std::nullopt;
}
// Cancel previous change subscription.
MaybeUnsubscribeFromNotificationPermissionStatusChange(permission_controller);
// Create new change subscription.
permission_controller_subscription_id_ =
permission_controller->SubscribeToPermissionStatusChange(
blink::PermissionType::NOTIFICATIONS,
web_contents()->GetPrimaryMainFrame()->GetProcess(),
url::Origin::Create(web_contents()->GetLastCommittedURL()),
base::BindRepeating(&PerformanceManagerTabHelper::
OnNotificationPermissionStatusChange,
// Unretained is safe because the subscription
// is removed when `this` is deleted.
base::Unretained(this)));
// Return current status.
return permission_controller->GetPermissionStatusForCurrentDocument(
blink::PermissionType::NOTIFICATIONS,
web_contents()->GetPrimaryMainFrame());
#endif // BUILDFLAG(IS_ANDROID)
}
void PerformanceManagerTabHelper::OnNotificationPermissionStatusChange(
blink::mojom::PermissionStatus permission_status) {
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::OnNotificationPermissionStatusChange,
base::Unretained(primary_page_node()), permission_status));
}
void PerformanceManagerTabHelper::
MaybeUnsubscribeFromNotificationPermissionStatusChange(
content::PermissionController* permission_controller) {
if (permission_controller_subscription_id_.is_null()) {
return;
}
CHECK(permission_controller);
permission_controller->UnsubscribeFromPermissionStatusChange(
permission_controller_subscription_id_);
}
void PerformanceManagerTabHelper::TitleWasSet(content::NavigationEntry* entry) {
DCHECK(primary_page_);
// TODO(crbug.com/40894717): This logic belongs in the policy layer rather
// than here. If a page has no <title> element on first load, the first change
// of title will be ignored no matter much later it happens.
if (!primary_page_->first_time_title_set) {
primary_page_->first_time_title_set = true;
return;
}
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::OnTitleUpdated,
base::Unretained(primary_page_node())));
}
void PerformanceManagerTabHelper::InnerWebContentsAttached(
content::WebContents* inner_web_contents,
content::RenderFrameHost* render_frame_host,
bool /* is_full_page */) {
// Note that we sometimes learn of contents creation at this point (before
// other helpers get a chance to attach), so we need to ensure our helper
// exists.
CreateForWebContents(inner_web_contents);
auto* helper = FromWebContents(inner_web_contents);
DCHECK(helper);
auto* page = helper->primary_page_node();
DCHECK(page);
auto* frame = GetFrameNode(render_frame_host);
// For a guest view, the RFH should already have been seen.
// Note that guest views can simultaneously have openers *and* be embedded.
auto embedding_type = PageNode::EmbeddingType::kGuestView;
DCHECK(frame);
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::SetEmbedderFrameNodeAndEmbeddingType,
base::Unretained(page), base::Unretained(frame),
embedding_type));
}
void PerformanceManagerTabHelper::InnerWebContentsDetached(
content::WebContents* inner_web_contents) {
auto* helper = FromWebContents(inner_web_contents);
DCHECK(helper);
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::ClearEmbedderFrameNodeAndEmbeddingType,
base::Unretained(helper->primary_page_node())));
}
void PerformanceManagerTabHelper::WebContentsDestroyed() {
// Remember the contents, as TearDown clears observer.
auto* contents = web_contents();
TearDown();
// Immediately remove ourselves from the WCUD. After TearDown the tab helper
// is in an inconsistent state. This will prevent other
// WCO::WebContentsDestroyed handlers from trying to access the tab helper in
// this inconsistent state.
contents->RemoveUserData(UserDataKey());
}
void PerformanceManagerTabHelper::DidUpdateFaviconURL(
content::RenderFrameHost* render_frame_host,
const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
DCHECK(primary_page_);
// This favicon change might have been initiated by a different frame some
// time ago and the main frame might have changed.
if (!render_frame_host->IsActive())
return;
// TODO(crbug.com/40894717): This logic belongs in the policy layer rather
// than here. If a page has no favicon on first load, the first change of
// favicon will be ignored no matter much later it happens.
if (!primary_page_->first_time_favicon_set) {
primary_page_->first_time_favicon_set = true;
return;
}
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::OnFaviconUpdated,
base::Unretained(primary_page_node())));
}
void PerformanceManagerTabHelper::MediaPictureInPictureChanged(
bool is_picture_in_picture) {
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetHasPictureInPicture,
base::Unretained(primary_page_node()),
is_picture_in_picture));
}
void PerformanceManagerTabHelper::OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) {
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetIsFocused,
base::Unretained(primary_page_node()),
/*is_focused=*/true));
}
void PerformanceManagerTabHelper::OnWebContentsLostFocus(
content::RenderWidgetHost* render_widget_host) {
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetIsFocused,
base::Unretained(primary_page_node()),
/*is_focused=*/false));
}
void PerformanceManagerTabHelper::AboutToBeDiscarded(
content::WebContents* new_contents) {
DCHECK(primary_page_);
base::WeakPtr<PageNode> new_page_node =
PerformanceManager::GetPrimaryPageNodeForWebContents(new_contents);
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&PageNodeImpl::OnAboutToBeDiscarded,
base::Unretained(primary_page_node()), new_page_node));
}
void PerformanceManagerTabHelper::BindDocumentCoordinationUnit(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<mojom::DocumentCoordinationUnit> receiver) {
auto it = frames_.find(render_frame_host);
DCHECK(it != frames_.end());
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(&FrameNodeImpl::Bind, base::Unretained(it->second.get()),
std::move(receiver)));
}
FrameNodeImpl* PerformanceManagerTabHelper::GetFrameNode(
content::RenderFrameHost* render_frame_host) {
auto it = frames_.find(render_frame_host);
return it != frames_.end() ? it->second.get() : nullptr;
}
void PerformanceManagerTabHelper::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PerformanceManagerTabHelper::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void PerformanceManagerTabHelper::OnMainFrameNavigation(int64_t navigation_id) {
DCHECK(primary_page_);
primary_page_->ukm_source_id =
ukm::ConvertToSourceId(navigation_id, ukm::SourceIdType::NAVIGATION_ID);
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetUkmSourceId,
base::Unretained(primary_page_node()),
primary_page_->ukm_source_id));
primary_page_->first_time_title_set = false;
primary_page_->first_time_favicon_set = false;
}
FrameNodeImpl* PerformanceManagerTabHelper::GetExistingFrameNode(
content::RenderFrameHost* render_frame_host) const {
auto it = frames_.find(render_frame_host);
CHECK(it != frames_.end());
return it->second.get();
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(PerformanceManagerTabHelper);
} // namespace performance_manager