blob: 881737833dfaf8e7b209f17e635e419bcf927dc7 [file] [log] [blame]
// Copyright 2015 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/ui/tabs/tab_strip_model_stats_recorder.h"
#include <algorithm>
#include <utility>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/supports_user_data.h"
#include "base/time/time.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
TabStripModelStatsRecorder::TabStripModelStatsRecorder()
: browser_tab_strip_tracker_(this, nullptr, nullptr) {
browser_tab_strip_tracker_.Init();
}
TabStripModelStatsRecorder::~TabStripModelStatsRecorder() {
}
class TabStripModelStatsRecorder::TabInfo
: public base::SupportsUserData::Data {
public:
~TabInfo() override;
void UpdateState(TabState new_state);
TabState state() const { return current_state_; }
static TabInfo* Get(content::WebContents* contents) {
TabInfo* info = static_cast<TabStripModelStatsRecorder::TabInfo*>(
contents->GetUserData(kKey));
if (!info) {
info = new TabInfo();
contents->SetUserData(kKey, base::WrapUnique(info));
}
return info;
}
base::TimeTicks creation_time() const { return creation_time_; }
private:
TabState current_state_ = TabState::INITIAL;
base::TimeTicks creation_time_ = base::TimeTicks::Now();
static const char kKey[];
};
const char TabStripModelStatsRecorder::TabInfo::kKey[] = "WebContents TabInfo";
TabStripModelStatsRecorder::TabInfo::~TabInfo() {}
void TabStripModelStatsRecorder::TabInfo::UpdateState(TabState new_state) {
if (new_state == current_state_)
return;
// Avoid state transition from CLOSED.
// When tab is closed, we receive TabStripModelObserver::TabClosingAt and then
// TabStripModelStatsRecorder::ActiveTabChanged.
// Here we ignore CLOSED -> INACTIVE state transition from last
// ActiveTabChanged.
if (current_state_ == TabState::CLOSED)
return;
switch (current_state_) {
case TabState::INITIAL:
break;
case TabState::ACTIVE:
UMA_HISTOGRAM_ENUMERATION("Tabs.StateTransfer.Target_Active",
static_cast<int>(new_state),
static_cast<int>(TabState::MAX));
break;
case TabState::INACTIVE:
UMA_HISTOGRAM_ENUMERATION("Tabs.StateTransfer.Target_Inactive",
static_cast<int>(new_state),
static_cast<int>(TabState::MAX));
break;
case TabState::CLOSED:
case TabState::MAX:
NOTREACHED();
break;
}
if (new_state == TabState::CLOSED) {
UMA_HISTOGRAM_MEDIUM_TIMES(
"Tabs.FineTiming.TimeBetweenTabCreatedAndSameTabClosed",
base::TimeTicks::Now() - creation_time_);
}
current_state_ = new_state;
}
void TabStripModelStatsRecorder::OnTabClosing(content::WebContents* contents) {
TabInfo::Get(contents)->UpdateState(TabState::CLOSED);
last_close_time_ = base::TimeTicks::Now();
// Avoid having stale pointer in active_tab_history_
std::replace(active_tab_history_.begin(), active_tab_history_.end(), contents,
static_cast<content::WebContents*>(nullptr));
}
void TabStripModelStatsRecorder::OnActiveTabChanged(
content::WebContents* old_contents,
content::WebContents* new_contents,
int reason) {
if (reason & TabStripModelObserver::CHANGE_REASON_REPLACED) {
// We already handled tab clobber at TabReplacedAt notification.
return;
}
if (old_contents)
TabInfo::Get(old_contents)->UpdateState(TabState::INACTIVE);
DCHECK(new_contents);
TabInfo* tab_info = TabInfo::Get(new_contents);
if (tab_info->state() == TabState::INITIAL) {
// A new tab has been created: log the time since the last one was created.
if (!last_creation_time_.is_null()) {
UMA_HISTOGRAM_MEDIUM_TIMES(
"Tabs.FineTiming.TimeBetweenTabCreatedAndNextTabCreated",
tab_info->creation_time() - last_creation_time_);
}
last_creation_time_ = tab_info->creation_time();
// Also log the time since a tab was closed, but only if this is the first
// tab that was opened since the closing.
if (!last_close_time_.is_null()) {
UMA_HISTOGRAM_MEDIUM_TIMES(
"Tabs.FineTiming.TimeBetweenTabClosedAndNextTabCreated",
tab_info->creation_time() - last_close_time_);
last_close_time_ = base::TimeTicks();
}
}
bool was_inactive = tab_info->state() == TabState::INACTIVE;
tab_info->UpdateState(TabState::ACTIVE);
// A UMA Histogram must be bounded by some number.
// We chose 64 as our bound as 99.5% of the users open <64 tabs.
const int kMaxTabHistory = 64;
auto it = std::find(active_tab_history_.cbegin(), active_tab_history_.cend(),
new_contents);
int age = (it != active_tab_history_.cend()) ?
(it - active_tab_history_.cbegin()) : (kMaxTabHistory - 1);
if (was_inactive) {
UMA_HISTOGRAM_ENUMERATION(
"Tabs.StateTransfer.NumberOfOtherTabsActivatedBeforeMadeActive",
std::min(age, kMaxTabHistory - 1), kMaxTabHistory);
}
active_tab_history_.insert(active_tab_history_.begin(), new_contents);
if (active_tab_history_.size() > kMaxTabHistory)
active_tab_history_.resize(kMaxTabHistory);
}
void TabStripModelStatsRecorder::OnTabReplaced(
content::WebContents* old_contents,
content::WebContents* new_contents) {
DCHECK(old_contents != new_contents);
*TabInfo::Get(new_contents) = *TabInfo::Get(old_contents);
std::replace(active_tab_history_.begin(), active_tab_history_.end(),
old_contents, new_contents);
}
void TabStripModelStatsRecorder::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (change.type() == TabStripModelChange::kRemoved) {
for (const auto& delta : change.deltas()) {
if (!delta.remove.will_be_deleted)
continue;
OnTabClosing(delta.remove.contents);
}
} else if (change.type() == TabStripModelChange::kReplaced) {
for (const auto& delta : change.deltas())
OnTabReplaced(delta.replace.old_contents, delta.replace.new_contents);
}
if (!selection.active_tab_changed() || tab_strip_model->empty())
return;
OnActiveTabChanged(selection.old_contents, selection.new_contents,
selection.reason);
}