1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/sessions/session_restore_stats_collector.h"
6
7 #include <string>
8
9 #include "base/metrics/histogram_macros.h"
10 #include "base/strings/stringprintf.h"
11 #include "content/public/browser/navigation_controller.h"
12 #include "content/public/browser/navigation_entry.h"
13 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_types.h"
15 #include "content/public/browser/render_widget_host_view.h"
16 #include "content/public/browser/web_contents.h"
17
18 namespace {
19
20 using content::NavigationController;
21 using content::RenderWidgetHost;
22 using content::RenderWidgetHostView;
23 using content::Source;
24 using content::WebContents;
25
26 SessionRestoreStatsCollector* g_instance = nullptr;
27
28 // Returns the RenderWidgetHostView associated with a NavigationController.
GetRenderWidgetHostView(NavigationController * tab)29 RenderWidgetHostView* GetRenderWidgetHostView(
30 NavigationController* tab) {
31 WebContents* web_contents = tab->GetWebContents();
32 if (web_contents)
33 return web_contents->GetRenderWidgetHostView();
34 return nullptr;
35 }
36
37 // Returns the RenderWidgetHost associated with a NavigationController.
GetRenderWidgetHost(NavigationController * tab)38 RenderWidgetHost* GetRenderWidgetHost(
39 NavigationController* tab) {
40 content::RenderWidgetHostView* render_widget_host_view =
41 GetRenderWidgetHostView(tab);
42 if (render_widget_host_view)
43 return render_widget_host_view->GetRenderWidgetHost();
44 return nullptr;
45 }
46
47 } // namespace
48
TabLoaderStats()49 SessionRestoreStatsCollector::TabLoaderStats::TabLoaderStats()
50 : tab_count(0u),
51 tab_first_paint_reason(PAINT_FINISHED_UMA_MAX) {}
52
TabState(NavigationController * controller)53 SessionRestoreStatsCollector::TabState::TabState(
54 NavigationController* controller)
55 : controller(controller),
56 was_hidden_or_occluded(false),
57 observed_host(nullptr) {}
58
SessionRestoreStatsCollector(const base::TimeTicks & restore_started,std::unique_ptr<StatsReportingDelegate> reporting_delegate)59 SessionRestoreStatsCollector::SessionRestoreStatsCollector(
60 const base::TimeTicks& restore_started,
61 std::unique_ptr<StatsReportingDelegate> reporting_delegate)
62 : non_restored_tab_painted_first_(false),
63 hidden_or_occluded_tab_ignored_(false),
64 restore_started_(restore_started),
65 reporting_delegate_(std::move(reporting_delegate)) {
66 DCHECK(!g_instance);
67 g_instance = this;
68
69 registrar_.Add(
70 this,
71 content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_VISUAL_PROPERTIES,
72 content::NotificationService::AllSources());
73 }
74
~SessionRestoreStatsCollector()75 SessionRestoreStatsCollector::~SessionRestoreStatsCollector() {
76 DCHECK_EQ(g_instance, this);
77 g_instance = nullptr;
78 }
79
80 // static
GetOrCreateInstance(base::TimeTicks restore_started,std::unique_ptr<StatsReportingDelegate> reporting_delegate)81 SessionRestoreStatsCollector* SessionRestoreStatsCollector::GetOrCreateInstance(
82 base::TimeTicks restore_started,
83 std::unique_ptr<StatsReportingDelegate> reporting_delegate) {
84 if (g_instance)
85 return g_instance;
86 return new SessionRestoreStatsCollector(restore_started,
87 std::move(reporting_delegate));
88 }
89
TrackTabs(const std::vector<SessionRestoreDelegate::RestoredTab> & tabs)90 void SessionRestoreStatsCollector::TrackTabs(
91 const std::vector<SessionRestoreDelegate::RestoredTab>& tabs) {
92 const base::TimeTicks now = base::TimeTicks::Now();
93 tab_loader_stats_.tab_count += tabs.size();
94 for (const auto& tab : tabs) {
95 // Report the time since the tab was active. If the tab is visible the
96 // last active time is right now, so report zero.
97 base::TimeDelta time_since_active;
98 if (tab.contents()->GetVisibility() != content::Visibility::VISIBLE)
99 time_since_active = now - tab.contents()->GetLastActiveTime();
100 reporting_delegate_->ReportTabTimeSinceActive(time_since_active);
101
102 // Get the active navigation entry. Restored tabs should always have one.
103 auto* controller = &tab.contents()->GetController();
104 auto* nav_entry =
105 controller->GetEntryAtIndex(controller->GetCurrentEntryIndex());
106 DCHECK(nav_entry);
107
108 RegisterForNotifications(controller);
109 }
110 }
111
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)112 void SessionRestoreStatsCollector::Observe(
113 int type,
114 const content::NotificationSource& source,
115 const content::NotificationDetails& details) {
116 switch (type) {
117 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
118 // This happens when a tab has been closed. A tab can be in any state
119 // when this occurs. Simply stop tracking the tab.
120 WebContents* web_contents = Source<WebContents>(source).ptr();
121 NavigationController* tab = &web_contents->GetController();
122 RemoveTab(tab);
123 break;
124 }
125 case content::
126 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_VISUAL_PROPERTIES: {
127 // This notification is across all tabs in the browser so notifications
128 // will arrive for tabs that the collector is not explicitly tracking.
129
130 // Only process this event if first paint hasn't been seen and this is a
131 // paint of a tab that has not been hidden or occluded.
132 RenderWidgetHost* render_widget_host =
133 Source<RenderWidgetHost>(source).ptr();
134 if (render_widget_host->GetView() &&
135 render_widget_host->GetView()->IsShowing()) {
136 TabState* tab_state = GetTabState(render_widget_host);
137 if (tab_state) {
138 // Ignore first paint of a restored tab that was hidden or occluded
139 // before first paint. If another restored tab is painted, its paint
140 // time will be recorded.
141 if (tab_state->was_hidden_or_occluded) {
142 hidden_or_occluded_tab_ignored_ = true;
143 break;
144 }
145 // This is a paint for a tab that is explicitly being tracked so
146 // update the statistics. Otherwise the host is for a tab that's not
147 // being tracked. Thus some other tab has visibility and has rendered
148 // and there's no point in tracking the time to first paint. This can
149 // happen because the user opened a different tab or restored tabs
150 // to an already existing browser and an existing tab was in the
151 // foreground.
152 base::TimeDelta time_to_paint =
153 base::TimeTicks::Now() - restore_started_;
154 tab_loader_stats_.foreground_tab_first_paint = time_to_paint;
155 } else {
156 non_restored_tab_painted_first_ = true;
157 }
158
159 ReportStatsAndSelfDestroy();
160 }
161 break;
162 }
163 default:
164 NOTREACHED() << "Unknown notification received:" << type;
165 break;
166 }
167 }
168
RenderWidgetHostVisibilityChanged(content::RenderWidgetHost * widget_host,bool became_visible)169 void SessionRestoreStatsCollector::RenderWidgetHostVisibilityChanged(
170 content::RenderWidgetHost* widget_host,
171 bool became_visible) {
172 auto* tab_state = GetTabState(widget_host);
173 if (tab_state && !became_visible)
174 tab_state->was_hidden_or_occluded = true;
175 }
176
RenderWidgetHostDestroyed(content::RenderWidgetHost * widget_host)177 void SessionRestoreStatsCollector::RenderWidgetHostDestroyed(
178 content::RenderWidgetHost* widget_host) {
179 auto* tab_state = GetTabState(widget_host);
180 if (tab_state) {
181 observer_.Remove(tab_state->observed_host);
182 tab_state->observed_host = nullptr;
183 }
184 }
185
RemoveTab(NavigationController * tab)186 void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) {
187 // Stop observing this tab.
188 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
189 Source<WebContents>(tab->GetWebContents()));
190 auto tab_it = tabs_tracked_.find(tab);
191 DCHECK(tab_it != tabs_tracked_.end());
192 TabState& tab_state = tab_it->second;
193 if (tab_state.observed_host)
194 observer_.Remove(tab_state.observed_host);
195
196 // Remove the tab from the |tabs_tracked_| map.
197 tabs_tracked_.erase(tab_it);
198
199 // It is possible for all restored contents to be destroyed before a first
200 // paint has arrived.
201 if (tabs_tracked_.empty())
202 ReportStatsAndSelfDestroy();
203 }
204
205 SessionRestoreStatsCollector::TabState*
RegisterForNotifications(NavigationController * tab)206 SessionRestoreStatsCollector::RegisterForNotifications(
207 NavigationController* tab) {
208 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
209 Source<WebContents>(tab->GetWebContents()));
210 auto result = tabs_tracked_.insert(std::make_pair(tab, TabState(tab)));
211 DCHECK(result.second);
212 TabState* tab_state = &result.first->second;
213 // Register for RenderWidgetHostVisibilityChanged notifications for this tab.
214 tab_state->observed_host = GetRenderWidgetHost(tab);
215 if (tab_state->observed_host)
216 observer_.Add(tab_state->observed_host);
217 return tab_state;
218 }
219
220 SessionRestoreStatsCollector::TabState*
GetTabState(NavigationController * tab)221 SessionRestoreStatsCollector::GetTabState(NavigationController* tab) {
222 // This lookup can fail because the call can arrive for tabs that have
223 // already loaded (user forced) and are no longer tracked.
224 auto it = tabs_tracked_.find(tab);
225 if (it == tabs_tracked_.end())
226 return nullptr;
227 return &it->second;
228 }
229
230 SessionRestoreStatsCollector::TabState*
GetTabState(RenderWidgetHost * tab)231 SessionRestoreStatsCollector::GetTabState(RenderWidgetHost* tab) {
232 for (auto& pair : tabs_tracked_) {
233 if (pair.second.observed_host == tab)
234 return &pair.second;
235 }
236 // It's possible for this lookup to fail as paint events can be received for
237 // tabs that aren't being tracked.
238 return nullptr;
239 }
240
ReportStatsAndSelfDestroy()241 void SessionRestoreStatsCollector::ReportStatsAndSelfDestroy() {
242 if (!tab_loader_stats_.foreground_tab_first_paint.is_zero()) {
243 tab_loader_stats_.tab_first_paint_reason = PAINT_FINISHED_UMA_DONE;
244 } else if (non_restored_tab_painted_first_) {
245 tab_loader_stats_.tab_first_paint_reason =
246 PAINT_FINISHED_NON_RESTORED_TAB_PAINTED_FIRST;
247 } else if (hidden_or_occluded_tab_ignored_) {
248 tab_loader_stats_.tab_first_paint_reason =
249 PAINT_FINISHED_UMA_NO_COMPLETELY_VISIBLE_TABS;
250 } else {
251 tab_loader_stats_.tab_first_paint_reason = PAINT_FINISHED_UMA_NO_PAINT;
252 }
253 reporting_delegate_->ReportTabLoaderStats(tab_loader_stats_);
254
255 delete this;
256 }
257
258 SessionRestoreStatsCollector::UmaStatsReportingDelegate::
259 UmaStatsReportingDelegate() = default;
260
261 void SessionRestoreStatsCollector::UmaStatsReportingDelegate::
ReportTabLoaderStats(const TabLoaderStats & tab_loader_stats)262 ReportTabLoaderStats(const TabLoaderStats& tab_loader_stats) {
263 if (!tab_loader_stats.foreground_tab_first_paint.is_zero()) {
264 UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstPaint4",
265 tab_loader_stats.foreground_tab_first_paint,
266 base::TimeDelta::FromMilliseconds(100),
267 base::TimeDelta::FromMinutes(16), 50);
268
269 std::string time_for_count = base::StringPrintf(
270 "SessionRestore.ForegroundTabFirstPaint4_%u",
271 static_cast<unsigned int>(tab_loader_stats.tab_count));
272 base::HistogramBase* counter_for_count = base::Histogram::FactoryTimeGet(
273 time_for_count, base::TimeDelta::FromMilliseconds(100),
274 base::TimeDelta::FromMinutes(16), 50,
275 base::Histogram::kUmaTargetedHistogramFlag);
276 counter_for_count->AddTime(tab_loader_stats.foreground_tab_first_paint);
277 }
278 UMA_HISTOGRAM_ENUMERATION(
279 "SessionRestore.ForegroundTabFirstPaint4.FinishReason",
280 tab_loader_stats.tab_first_paint_reason, PAINT_FINISHED_UMA_MAX);
281 }
282
283 void SessionRestoreStatsCollector::UmaStatsReportingDelegate::
ReportTabTimeSinceActive(base::TimeDelta elapsed)284 ReportTabTimeSinceActive(base::TimeDelta elapsed) {
285 UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.RestoredTab.TimeSinceActive",
286 elapsed, base::TimeDelta::FromSeconds(10),
287 base::TimeDelta::FromDays(7), 100);
288 }
289