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