blob: debc45d24281c4eb55583536dfeb11f44326fde5 [file] [log] [blame]
// Copyright 2019 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/performance_manager_tab_helper.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/stl_util.h"
#include "chrome/browser/performance_manager/graph/frame_node_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/performance_manager.h"
#include "chrome/browser/performance_manager/render_process_user_data.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
namespace performance_manager {
PerformanceManagerTabHelper* PerformanceManagerTabHelper::first_ = nullptr;
// static
bool PerformanceManagerTabHelper::GetCoordinationIDForWebContents(
content::WebContents* web_contents,
resource_coordinator::CoordinationUnitID* id) {
PerformanceManagerTabHelper* helper = FromWebContents(web_contents);
if (!helper)
return false;
*id = helper->page_node_->id();
return true;
}
// static
void PerformanceManagerTabHelper::DetachAndDestroyAll() {
while (first_)
first_->web_contents()->RemoveUserData(UserDataKey());
}
PerformanceManagerTabHelper::PerformanceManagerTabHelper(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
performance_manager_(PerformanceManager::GetInstance()),
page_node_(performance_manager_->CreatePageNode()) {
// Make sure to set the visibility property when we create
// |page_resource_coordinator_|.
UpdatePageNodeVisibility(web_contents->GetVisibility());
// Dispatch creation notifications for any pre-existing frames.
// This seems to occur only in tests, but dealing with this allows asserting
// a strong invariant on the |frames_| collection.
// TODO(siggi): Eliminate this once test injection is all or nothing.
std::vector<content::RenderFrameHost*> existing_frames =
web_contents->GetAllFrames();
for (content::RenderFrameHost* frame : existing_frames) {
// Only send notifications for live frames, the non-live ones will generate
// creation notifications when animated.
if (frame->IsRenderFrameLive())
RenderFrameCreated(frame);
}
// Push this instance to the list.
next_ = first_;
if (next_)
next_->prev_ = this;
prev_ = nullptr;
first_ = this;
}
PerformanceManagerTabHelper::~PerformanceManagerTabHelper() {
performance_manager_->DeleteNode(std::move(page_node_));
for (auto& kv : frames_)
performance_manager_->DeleteNode(std::move(kv.second));
if (first_ == this)
first_ = next_;
if (prev_) {
DCHECK_EQ(prev_->next_, this);
prev_->next_ = next_;
}
if (next_) {
DCHECK_EQ(next_->prev_, this);
next_->prev_ = prev_;
}
}
void PerformanceManagerTabHelper::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
DCHECK_NE(nullptr, render_frame_host);
// This must not exist in the map yet.
DCHECK(!base::ContainsKey(frames_, render_frame_host));
std::unique_ptr<FrameNodeImpl> frame =
performance_manager_->CreateFrameNode();
content::RenderFrameHost* parent = render_frame_host->GetParent();
if (parent) {
DCHECK(base::ContainsKey(frames_, parent));
auto& parent_frame_node = frames_[parent];
performance_manager_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&FrameNodeImpl::AddChildFrame,
base::Unretained(parent_frame_node.get()), frame->id()));
}
RenderProcessUserData* user_data =
RenderProcessUserData::GetForRenderProcessHost(
render_frame_host->GetProcess());
// In unittests the user data isn't populated as the relevant main parts
// is not in play.
// TODO(siggi): Figure out how to assert on this when the main parts are
// registered with the content browser client.
if (user_data) {
performance_manager_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&FrameNodeImpl::SetProcess,
base::Unretained(frame.get()),
user_data->process_node()->id()));
}
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());
performance_manager_->DeleteNode(std::move(it->second));
frames_.erase(it);
}
void PerformanceManagerTabHelper::DidStartLoading() {
performance_manager_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetIsLoading,
base::Unretained(page_node_.get()), true));
}
void PerformanceManagerTabHelper::DidStopLoading() {
performance_manager_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&PageNodeImpl::SetIsLoading,
base::Unretained(page_node_.get()), false));
}
void PerformanceManagerTabHelper::OnVisibilityChanged(
content::Visibility visibility) {
UpdatePageNodeVisibility(visibility);
}
void PerformanceManagerTabHelper::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument()) {
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();
content::RenderFrameHost* render_frame_host =
navigation_handle->GetRenderFrameHost();
// Make sure the hierarchical structure is constructed before sending signal
// to Resource Coordinator.
// 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.
auto it = frames_.find(render_frame_host);
if (it != frames_.end()) {
// TODO(siggi): See whether this can be done in RenderFrameCreated.
performance_manager_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&PageNodeImpl::AddFrame,
base::Unretained(page_node_.get()), it->second->id()));
if (navigation_handle->IsInMainFrame()) {
OnMainFrameNavigation(navigation_handle->GetNavigationId());
performance_manager_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&PageNodeImpl::OnMainFrameNavigationCommitted,
base::Unretained(page_node_.get()),
navigation_committed_time,
navigation_handle->GetNavigationId(),
navigation_handle->GetURL().spec()));
}
}
}
void PerformanceManagerTabHelper::TitleWasSet(content::NavigationEntry* entry) {
// TODO(siggi): This logic belongs in the policy layer rather than here.
if (!first_time_title_set_) {
first_time_title_set_ = true;
return;
}
performance_manager_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&PageNodeImpl::OnTitleUpdated,
base::Unretained(page_node_.get())));
}
void PerformanceManagerTabHelper::DidUpdateFaviconURL(
const std::vector<content::FaviconURL>& candidates) {
// TODO(siggi): This logic belongs in the policy layer rather than here.
if (!first_time_favicon_set_) {
first_time_favicon_set_ = true;
return;
}
performance_manager_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&PageNodeImpl::OnFaviconUpdated,
base::Unretained(page_node_.get())));
}
void PerformanceManagerTabHelper::OnInterfaceRequestFromFrame(
content::RenderFrameHost* render_frame_host,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle* interface_pipe) {
if (interface_name !=
resource_coordinator::mojom::FrameCoordinationUnit::Name_)
return;
auto it = frames_.find(render_frame_host);
DCHECK(it != frames_.end());
performance_manager_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&FrameNodeImpl::AddBinding,
base::Unretained(it->second.get()),
resource_coordinator::mojom::FrameCoordinationUnitRequest(
std::move(*interface_pipe))));
}
void PerformanceManagerTabHelper::OnMainFrameNavigation(int64_t navigation_id) {
ukm_source_id_ =
ukm::ConvertToSourceId(navigation_id, ukm::SourceIdType::NAVIGATION_ID);
performance_manager_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&PageNodeImpl::SetUKMSourceId,
base::Unretained(page_node_.get()), ukm_source_id_));
first_time_title_set_ = false;
first_time_favicon_set_ = false;
}
void PerformanceManagerTabHelper::UpdatePageNodeVisibility(
content::Visibility visibility) {
// TODO(fdoray): An OCCLUDED tab should not be considered visible.
const bool is_visible = visibility != content::Visibility::HIDDEN;
performance_manager_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&PageNodeImpl::SetVisibility,
base::Unretained(page_node_.get()), is_visible));
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(PerformanceManagerTabHelper)
} // namespace performance_manager