blob: 9ff6188d959d61f7a298dea8bb7b662fdb62f81f [file] [log] [blame]
// 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/resource_coordinator/tab_helper.h"
#include <string>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/feature_list.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/performance_manager/page_resource_coordinator.h"
#include "chrome/browser/performance_manager/performance_manager.h"
#include "chrome/browser/performance_manager/render_process_user_data.h"
#include "chrome/browser/resource_coordinator/page_signal_receiver.h"
#include "chrome/browser/resource_coordinator/resource_coordinator_parts.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "chrome/browser/resource_coordinator/tab_memory_metrics_reporter.h"
#include "chrome/browser/resource_coordinator/utils.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#if !defined(OS_ANDROID)
#include "chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.h"
#include "chrome/browser/resource_coordinator/tab_manager.h"
#endif
namespace resource_coordinator {
ResourceCoordinatorTabHelper::ResourceCoordinatorTabHelper(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
performance_manager_(
performance_manager::PerformanceManager::GetInstance()) {
TabLoadTracker::Get()->StartTracking(web_contents);
if (performance_manager_) {
page_resource_coordinator_ =
std::make_unique<performance_manager::PageResourceCoordinator>(
performance_manager_);
// Make sure to set the visibility property when we create
// |page_resource_coordinator_|.
const bool is_visible =
web_contents->GetVisibility() != content::Visibility::HIDDEN;
page_resource_coordinator_->SetVisibility(is_visible);
if (auto* page_signal_receiver = GetPageSignalReceiver()) {
// Gets CoordinationUnitID for this WebContents and adds it to
// PageSignalReceiver.
page_signal_receiver->AssociateCoordinationUnitIDWithWebContents(
page_resource_coordinator_->id(), web_contents);
}
if (memory_instrumentation::MemoryInstrumentation::GetInstance()) {
auto* rc_parts = g_browser_process->resource_coordinator_parts();
DCHECK(rc_parts);
rc_parts->tab_memory_metrics_reporter()->StartReporting(
TabLoadTracker::Get());
}
}
#if !defined(OS_ANDROID)
// Don't create the LocalSiteCharacteristicsWebContentsObserver for this tab
// we don't have a page signal receiver as the data that this observer
// records depend on it.
if (base::FeatureList::IsEnabled(features::kSiteCharacteristicsDatabase) &&
GetPageSignalReceiver()) {
local_site_characteristics_wc_observer_ =
std::make_unique<LocalSiteCharacteristicsWebContentsObserver>(
web_contents);
}
#endif
// 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.
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);
}
}
ResourceCoordinatorTabHelper::~ResourceCoordinatorTabHelper() = default;
void ResourceCoordinatorTabHelper::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));
if (!performance_manager_)
return;
std::unique_ptr<performance_manager::FrameResourceCoordinator> frame =
std::make_unique<performance_manager::FrameResourceCoordinator>(
performance_manager_);
content::RenderFrameHost* parent = render_frame_host->GetParent();
if (parent) {
DCHECK(base::ContainsKey(frames_, parent));
auto& parent_frame_node = frames_[parent];
parent_frame_node->AddChildFrame(*frame.get());
}
performance_manager::RenderProcessUserData* user_data =
performance_manager::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)
frame->SetProcess(*user_data->process_resource_coordinator());
frames_[render_frame_host] = std::move(frame);
}
void ResourceCoordinatorTabHelper::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (!performance_manager_)
return;
// TODO(siggi): Ideally this would DCHECK that the deleted render frame host
// is known, e.g. that there was a creation notification for it. This is
// however not always the case. Notably these two unit_tests:
// - TabsApiUnitTest.TabsGoForwardAndBack
// - TabsApiUnitTest.TabsGoForwardAndBackWithoutTabId
// end up issuing deletion notifications for render frame hosts never seen
// before. It appears that the RenderFrameHostManager keeps a queue of
// pending deletions. If a frame is already in this queue at the time
// this tab helper is attached to a WebContents, the eventual deletion
// notification will be singular.
// DCHECK(base::ContainsKey(frames_, render_frame_host));
frames_.erase(render_frame_host);
}
void ResourceCoordinatorTabHelper::DidStartLoading() {
if (page_resource_coordinator_)
page_resource_coordinator_->SetIsLoading(true);
TabLoadTracker::Get()->DidStartLoading(web_contents());
}
void ResourceCoordinatorTabHelper::DidReceiveResponse() {
TabLoadTracker::Get()->DidReceiveResponse(web_contents());
}
void ResourceCoordinatorTabHelper::DidStopLoading() {
if (page_resource_coordinator_)
page_resource_coordinator_->SetIsLoading(false);
TabLoadTracker::Get()->DidStopLoading(web_contents());
}
void ResourceCoordinatorTabHelper::DidFailLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) {
TabLoadTracker::Get()->DidFailLoad(web_contents());
}
void ResourceCoordinatorTabHelper::RenderProcessGone(
base::TerminationStatus status) {
// TODO(siggi): Looks like this can be acquired in a more timely manner from
// the RenderProcessHostObserver.
TabLoadTracker::Get()->RenderProcessGone(web_contents(), status);
}
void ResourceCoordinatorTabHelper::OnVisibilityChanged(
content::Visibility visibility) {
if (page_resource_coordinator_) {
// TODO(fdoray): An OCCLUDED tab should not be considered visible.
const bool is_visible = visibility != content::Visibility::HIDDEN;
page_resource_coordinator_->SetVisibility(is_visible);
}
}
void ResourceCoordinatorTabHelper::WebContentsDestroyed() {
if (page_resource_coordinator_) {
if (auto* page_signal_receiver = GetPageSignalReceiver()) {
// Gets CoordinationUnitID for this WebContents and removes it from
// PageSignalReceiver.
page_signal_receiver->RemoveCoordinationUnitID(
page_resource_coordinator_->id());
}
}
TabLoadTracker::Get()->StopTracking(web_contents());
}
void ResourceCoordinatorTabHelper::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument()) {
return;
}
if (page_resource_coordinator_) {
// 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 when
// 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.
page_resource_coordinator_->AddFrame(*(it->second));
}
if (navigation_handle->IsInMainFrame()) {
if (auto* page_signal_receiver = GetPageSignalReceiver()) {
// Update the last observed navigation ID for this WebContents.
page_signal_receiver->SetNavigationID(
web_contents(), navigation_handle->GetNavigationId());
}
UpdateUkmRecorder(navigation_handle->GetNavigationId());
ResetFlag();
page_resource_coordinator_->OnMainFrameNavigationCommitted(
navigation_committed_time, navigation_handle->GetNavigationId(),
navigation_handle->GetURL().spec());
}
}
}
void ResourceCoordinatorTabHelper::TitleWasSet(
content::NavigationEntry* entry) {
if (!first_time_title_set_) {
first_time_title_set_ = true;
return;
}
if (page_resource_coordinator_)
page_resource_coordinator_->OnTitleUpdated();
}
void ResourceCoordinatorTabHelper::DidUpdateFaviconURL(
const std::vector<content::FaviconURL>& candidates) {
if (!first_time_favicon_set_) {
first_time_favicon_set_ = true;
return;
}
if (page_resource_coordinator_)
page_resource_coordinator_->OnFaviconUpdated();
}
void ResourceCoordinatorTabHelper::OnInterfaceRequestFromFrame(
content::RenderFrameHost* render_frame_host,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle* interface_pipe) {
if (interface_name != mojom::FrameCoordinationUnit::Name_)
return;
auto it = frames_.find(render_frame_host);
DCHECK(it != frames_.end());
it->second->AddBinding(
mojom::FrameCoordinationUnitRequest(std::move(*interface_pipe)));
}
void ResourceCoordinatorTabHelper::UpdateUkmRecorder(int64_t navigation_id) {
ukm_source_id_ =
ukm::ConvertToSourceId(navigation_id, ukm::SourceIdType::NAVIGATION_ID);
if (page_resource_coordinator_)
page_resource_coordinator_->SetUKMSourceId(ukm_source_id_);
}
void ResourceCoordinatorTabHelper::ResetFlag() {
first_time_title_set_ = false;
first_time_favicon_set_ = false;
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(ResourceCoordinatorTabHelper)
} // namespace resource_coordinator