blob: ef540e05449b6c3468dbd5e904347c8139b54914 [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_activity_watcher.h"
#include "base/metrics/field_trial_params.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_metrics_logger_impl.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/window_activity_watcher.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
DEFINE_WEB_CONTENTS_USER_DATA_KEY(
resource_coordinator::TabActivityWatcher::WebContentsData);
namespace resource_coordinator {
namespace {
// Default time before a tab with the same SourceId can be logged again.
constexpr base::TimeDelta kDefaultPerSourceLogTimeout =
base::TimeDelta::FromSeconds(10);
// Fieldtrial param to override the default per-source log timeout above.
constexpr char kPerSourceLogTimeoutMsecParamName[] =
"per_source_log_timeout_msec";
} // namespace
// Per-WebContents helper class that observes its WebContents, notifying
// TabActivityWatcher when interesting events occur. Also provides
// per-WebContents data that TabActivityWatcher uses to log the tab.
class TabActivityWatcher::WebContentsData
: public content::WebContentsObserver,
public content::WebContentsUserData<WebContentsData>,
public content::RenderWidgetHost::InputEventObserver {
public:
~WebContentsData() override = default;
// Call after logging to update |last_log_time_for_source_|.
void DidLog(base::TimeTicks log_time) {
last_log_time_for_source_ = log_time;
}
ukm::SourceId ukm_source_id() const { return ukm_source_id_; }
const TabMetricsLogger::TabMetrics& tab_metrics() const {
return tab_metrics_;
}
base::TimeTicks last_log_time_for_source() const {
return last_log_time_for_source_;
}
private:
friend class content::WebContentsUserData<WebContentsData>;
explicit WebContentsData(content::WebContents* web_contents)
: WebContentsObserver(web_contents) {
tab_metrics_.web_contents = web_contents;
web_contents->GetRenderViewHost()->GetWidget()->AddInputEventObserver(this);
}
// content::WebContentsObserver:
void RenderViewHostChanged(content::RenderViewHost* old_host,
content::RenderViewHost* new_host) override {
if (old_host != nullptr)
old_host->GetWidget()->RemoveInputEventObserver(this);
new_host->GetWidget()->AddInputEventObserver(this);
}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (!navigation_handle->HasCommitted() ||
!navigation_handle->IsInMainFrame() ||
navigation_handle->IsSameDocument()) {
return;
}
// Use the same SourceId that SourceUrlRecorderWebContentsObserver populates
// and updates.
ukm::SourceId new_source_id = ukm::ConvertToSourceId(
navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
DCHECK_NE(new_source_id, ukm_source_id_)
<< "Expected a unique Source ID for the navigation";
ukm_source_id_ = new_source_id;
// Clear the per-SourceId last log time.
last_log_time_for_source_ = base::TimeTicks();
// Reset the per-page data.
tab_metrics_.page_metrics = {};
// Update navigation info.
tab_metrics_.page_transition = navigation_handle->GetPageTransition();
}
void DidStopLoading() override {
// Log metrics for the tab when it stops loading instead of immediately
// after a navigation commits, so we can have some idea of its status and
// contents.
TabActivityWatcher::GetInstance()->OnDidStopLoading(web_contents());
}
void WasHidden() override {
TabActivityWatcher::GetInstance()->OnWasHidden(web_contents());
}
// content::RenderWidgetHost::InputEventObserver:
void OnInputEvent(const blink::WebInputEvent& event) override {
if (blink::WebInputEvent::IsMouseEventType(event.GetType()))
tab_metrics_.page_metrics.mouse_event_count++;
else if (blink::WebInputEvent::IsKeyboardEventType(event.GetType()))
tab_metrics_.page_metrics.key_event_count++;
else if (blink::WebInputEvent::IsTouchEventType(event.GetType()))
tab_metrics_.page_metrics.touch_event_count++;
}
// Updated when a navigation is finished.
ukm::SourceId ukm_source_id_ = 0;
// Used to throttle event logging per SourceId.
base::TimeTicks last_log_time_for_source_;
// Stores current stats for the tab.
TabMetricsLogger::TabMetrics tab_metrics_;
DISALLOW_COPY_AND_ASSIGN(WebContentsData);
};
TabActivityWatcher::TabActivityWatcher()
: tab_metrics_logger_(std::make_unique<TabMetricsLoggerImpl>()),
browser_tab_strip_tracker_(this, this, nullptr) {
per_source_log_timeout_ =
base::TimeDelta::FromMilliseconds(base::GetFieldTrialParamByFeatureAsInt(
features::kTabMetricsLogging, kPerSourceLogTimeoutMsecParamName,
kDefaultPerSourceLogTimeout.InMilliseconds()));
browser_tab_strip_tracker_.Init();
// TabMetrics UKMs reference WindowMetrics UKM entries, so ensure the
// WindowActivityWatcher is initialized.
WindowActivityWatcher::GetInstance();
}
TabActivityWatcher::~TabActivityWatcher() = default;
void TabActivityWatcher::TabPinnedStateChanged(TabStripModel* tab_strip_model,
content::WebContents* contents,
int index) {
// We only want UKMs for background tabs.
if (tab_strip_model->active_index() == index)
return;
MaybeLogTab(contents);
}
bool TabActivityWatcher::ShouldTrackBrowser(Browser* browser) {
// Don't track incognito browsers. This is also enforced by UKM.
return !browser->profile()->IsOffTheRecord();
}
void TabActivityWatcher::OnWasHidden(content::WebContents* web_contents) {
DCHECK(web_contents);
MaybeLogTab(web_contents);
}
void TabActivityWatcher::OnDidStopLoading(content::WebContents* web_contents) {
// Ignore load events in foreground tabs. The tab state of a foreground tab
// will be logged if/when it is backgrounded.
if (web_contents->IsVisible())
return;
MaybeLogTab(web_contents);
}
void TabActivityWatcher::MaybeLogTab(content::WebContents* web_contents) {
// Don't log when the WebContents is being closed or replaced.
if (web_contents->IsBeingDestroyed())
return;
DCHECK(!web_contents->GetBrowserContext()->IsOffTheRecord());
TabActivityWatcher::WebContentsData* web_contents_data =
TabActivityWatcher::WebContentsData::FromWebContents(web_contents);
DCHECK(web_contents_data);
// TODO(michaelpg): Convert to resource_coordinator::NowTicks().
base::TimeTicks now = base::TimeTicks::Now();
if (now - web_contents_data->last_log_time_for_source() <
per_source_log_timeout_) {
return;
}
ukm::SourceId ukm_source_id = web_contents_data->ukm_source_id();
tab_metrics_logger_->LogBackgroundTab(ukm_source_id,
web_contents_data->tab_metrics());
web_contents_data->DidLog(now);
}
void TabActivityWatcher::DisableLogTimeoutForTesting() {
per_source_log_timeout_ = base::TimeDelta();
}
void TabActivityWatcher::ResetForTesting() {
tab_metrics_logger_ = std::make_unique<TabMetricsLoggerImpl>();
}
// static
TabActivityWatcher* TabActivityWatcher::GetInstance() {
CR_DEFINE_STATIC_LOCAL(TabActivityWatcher, instance, ());
return &instance;
}
// static
void TabActivityWatcher::WatchWebContents(content::WebContents* web_contents) {
// In incognito, the UKM service won't log anything.
if (!web_contents->GetBrowserContext()->IsOffTheRecord())
TabActivityWatcher::WebContentsData::CreateForWebContents(web_contents);
}
} // namespace resource_coordinator