1 // Copyright 2017 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/resource_coordinator/tab_activity_watcher.h"
6 
7 #include <limits>
8 
9 #include "base/metrics/histogram_macros.h"
10 #include "base/no_destructor.h"
11 #include "base/rand_util.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/resource_coordinator/lifecycle_unit.h"
14 #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
15 #include "chrome/browser/resource_coordinator/tab_manager_features.h"
16 #include "chrome/browser/resource_coordinator/tab_metrics_logger.h"
17 #include "chrome/browser/resource_coordinator/tab_ranker/mru_features.h"
18 #include "chrome/browser/resource_coordinator/tab_ranker/tab_features.h"
19 #include "chrome/browser/resource_coordinator/time.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/browser_list.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "components/ukm/content/source_url_recorder.h"
26 #include "content/public/browser/browser_context.h"
27 #include "content/public/browser/navigation_handle.h"
28 #include "content/public/browser/render_view_host.h"
29 #include "content/public/browser/render_widget_host.h"
30 #include "content/public/browser/web_contents.h"
31 #include "content/public/browser/web_contents_observer.h"
32 #include "content/public/browser/web_contents_user_data.h"
33 #include "services/metrics/public/cpp/ukm_source_id.h"
34 #include "third_party/blink/public/common/input/web_input_event.h"
35 
36 namespace resource_coordinator {
37 namespace {
38 using tab_ranker::TabFeatures;
39 
40 // Used for decay Frecency scores.
41 constexpr float kFrecencyScoreDecay = 0.8f;
42 // Records how many tab reactivations till now.
43 static int32_t reactivation_index = 0;
44 // Used for generating label_ids and query_ids.
45 int64_t internal_id_for_logging = 0;
46 // Returns an int64_t number as label_id or query_id.
NewInt64ForLabelIdOrQueryId()47 int64_t NewInt64ForLabelIdOrQueryId() {
48   // The id is shifted 16 bits so that the lower bits are reserved for counting
49   // multiple queries.
50   // We choose 16 so that the lower bits for counting multiple queries and
51   // higher bits for labeling queries are both unlikely to overflow. (lower bits
52   // only overflows when we have more than 65536 queries without labeling
53   // events; higher bits only overflow when we have more than 100 billion
54   // discards.
55   constexpr int kIdShiftBits = 16;
56   return (++internal_id_for_logging) << kIdShiftBits;
57 }
58 
59 }  // namespace
60 
61 // Per-WebContents helper class that observes its WebContents, notifying
62 // TabActivityWatcher when interesting events occur. Also provides
63 // per-WebContents data that TabActivityWatcher uses to log the tab.
64 class TabActivityWatcher::WebContentsData
65     : public content::WebContentsObserver,
66       public content::WebContentsUserData<WebContentsData>,
67       public content::RenderWidgetHost::InputEventObserver {
68  public:
69   ~WebContentsData() override = default;
70 
71   // Calculates the tab reactivation score for a background tab. Returns nullopt
72   // if the score could not be calculated, e.g. because the tab is in the
73   // foreground.
CalculateReactivationScore()74   base::Optional<float> CalculateReactivationScore() {
75     if (web_contents()->IsBeingDestroyed() || backgrounded_time_.is_null())
76       return base::nullopt;
77 
78     // No log for CalculateReactivationScore.
79     base::Optional<TabFeatures> tab = GetTabFeatures();
80     if (!tab.has_value())
81       return base::nullopt;
82 
83     float score = 0.0f;
84     const tab_ranker::TabRankerResult result =
85         TabActivityWatcher::GetInstance()->predictor_->ScoreTab(tab.value(),
86                                                                 &score);
87     if (result == tab_ranker::TabRankerResult::kSuccess)
88       return score;
89     return base::nullopt;
90   }
91 
92   // Call when the associated WebContents has been replaced.
WasReplaced()93   void WasReplaced() { was_replaced_ = true; }
94 
95   // Call when the associated WebContents has replaced the WebContents of
96   // another tab. Copies info from the other WebContentsData so future events
97   // can be logged consistently.
DidReplace(const WebContentsData & replaced_tab)98   void DidReplace(const WebContentsData& replaced_tab) {
99     // Copy creation and foregrounded times to retain the replaced tab's MRU
100     // position.
101     creation_time_ = replaced_tab.creation_time_;
102     foregrounded_time_ = replaced_tab.foregrounded_time_;
103 
104     // Copy background status so ForegroundOrClosed can potentially be logged.
105     backgrounded_time_ = replaced_tab.backgrounded_time_;
106 
107     // Copy the replaced tab's stats.
108     page_metrics_ = replaced_tab.page_metrics_;
109 
110     // Recover the ukm_source_id from the |replaced_tab|.
111     ukm_source_id_ = replaced_tab.ukm_source_id_;
112 
113     // Copy the replaced label_id_.
114     label_id_ = replaced_tab.label_id_;
115 
116     // Copy the frecency score.
117     frecency_score_ = replaced_tab.frecency_score_;
118   }
119 
120   // Call when the WebContents is detached from its tab. If the tab is later
121   // re-inserted elsewhere, we use the state it had before being detached.
TabDetached()122   void TabDetached() { is_detached_ = true; }
123 
124   // Call when the tab is inserted into a tab strip to update state.
TabInserted(bool foreground)125   void TabInserted(bool foreground) {
126     if (is_detached_) {
127       is_detached_ = false;
128 
129       // Dragged tabs are normally inserted into their new tab strip in the
130       // "background", then "activated", even though the user perceives the tab
131       // staying active the whole time. So don't update |background_time_| here.
132       //
133       // TODO(michaelpg): If a background tab is dragged (as part of a group)
134       // and inserted, it may be treated as being foregrounded (depending on tab
135       // order). This is a small edge case, but can be fixed by the plan to
136       // merge the ForegroundedOrClosed and TabMetrics events.
137       return;
138     }
139 
140     if (foreground) {
141       foregrounded_time_ = NowTicks();
142       UpdateFrecencyScoreOnReactivation();
143     } else {
144       // This is a new tab that was opened in the background.
145       backgrounded_time_ = NowTicks();
146     }
147   }
148 
149   // Logs TabMetrics for the tab if it is considered to be backgrounded.
LogTabIfBackgrounded()150   void LogTabIfBackgrounded() {
151     if (backgrounded_time_.is_null() || DisableBackgroundLogWithTabRanker())
152       return;
153 
154     base::Optional<TabFeatures> tab = GetTabFeatures();
155     if (tab.has_value()) {
156       // Background time logging always logged with label_id == 0, since we
157       // only use label_id for query time logging for now.
158       TabActivityWatcher::GetInstance()->tab_metrics_logger_->LogTabMetrics(
159           ukm_source_id_, tab.value(), web_contents(), 0);
160     }
161   }
162 
163   // Logs current TabFeatures; skips if current tab is null.
LogCurrentTabFeatures(const base::Optional<TabFeatures> & tab)164   void LogCurrentTabFeatures(const base::Optional<TabFeatures>& tab) {
165     if (!tab.has_value())
166       return;
167     // Update label_id_: a new label_id is generated for this query if the
168     // label_id_ is 0; otherwise the old label_id_ is incremented. This allows
169     // us to better pairing TabMetrics with ForegroundedOrClosed events offline.
170     // The same label_id_ will be logged with ForegroundedOrClosed event later
171     // on so that TabFeatures can be paired with ForegroundedOrClosed.
172     label_id_ = label_id_ ? label_id_ + 1 : NewInt64ForLabelIdOrQueryId();
173 
174     TabActivityWatcher::GetInstance()->tab_metrics_logger_->LogTabMetrics(
175         ukm_source_id_, tab.value(), web_contents(), label_id_);
176   }
177 
178   // Sets foregrounded_time_ to NowTicks() so this becomes the
179   // most-recently-used tab.
TabWindowActivated()180   void TabWindowActivated() { foregrounded_time_ = NowTicks(); }
181 
182  private:
183   friend class content::WebContentsUserData<WebContentsData>;
184   friend class TabActivityWatcher;
185 
186   // A FrecencyScore is used as a measurement of both frequency and recency.
187   // (1) The score is decayed by kFrecencyScoreDecay every time any tab is
188   // reactivated.
189   // (2) The score is incremented by 1.0 - kFrecencyScoreDecay when this tab is
190   // reactivated.
191   struct FrecencyScore {
192     int32_t update_index = 0;
193     float score = 0.0f;
194   };
195 
WebContentsData(content::WebContents * web_contents)196   explicit WebContentsData(content::WebContents* web_contents)
197       : WebContentsObserver(web_contents) {
198     DCHECK(!web_contents->GetBrowserContext()->IsOffTheRecord());
199     web_contents->GetMainFrame()
200         ->GetRenderViewHost()
201         ->GetWidget()
202         ->AddInputEventObserver(this);
203 
204     creation_time_ = NowTicks();
205 
206     // A navigation may already have completed if this is a replacement tab.
207     ukm_source_id_ = ukm::GetSourceIdForWebContentsDocument(web_contents);
208 
209     // When a tab is discarded, a new null_web_contents will be created (with
210     // WasDiscarded set as true) applied as a replacement of the discarded tab.
211     // We want to record this discarded state for later logging.
212     discarded_since_backgrounded_ = web_contents->WasDiscarded();
213   }
214 
WasHidden()215   void WasHidden() {
216     // The tab may not be in the tabstrip if it's being moved or replaced.
217     Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
218     if (!browser)
219       return;
220 
221     DCHECK(!browser->tab_strip_model()->closing_all());
222 
223     if (browser->tab_strip_model()->GetActiveWebContents() == web_contents() &&
224         !browser->window()->IsMinimized()) {
225       // The active tab is considered to be in the foreground unless its window
226       // is minimized. It might still get hidden, e.g. when the browser is about
227       // to close, but that shouldn't count as a backgrounded event.
228       //
229       // TODO(michaelpg): On Mac, hiding the application (e.g. via Cmd+H) should
230       // log tabs as backgrounded. Check NSApplication's isHidden property.
231       return;
232     }
233 
234     backgrounded_time_ = NowTicks();
235     discarded_since_backgrounded_ = false;
236     LogTabIfBackgrounded();
237   }
238 
WasShown()239   void WasShown() {
240     UpdateFrecencyScoreOnReactivation();
241 
242     if (backgrounded_time_.is_null())
243       return;
244 
245     Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
246     if (browser && browser->tab_strip_model()->closing_all())
247       return;
248 
249     // Log the event before updating times.
250     LogForegroundedOrClosedMetrics(true /* is_foregrounded */);
251 
252     backgrounded_time_ = base::TimeTicks();
253     foregrounded_time_ = NowTicks();
254 
255     page_metrics_.num_reactivations++;
256   }
257 
258   // content::WebContentsObserver:
RenderViewHostChanged(content::RenderViewHost * old_host,content::RenderViewHost * new_host)259   void RenderViewHostChanged(content::RenderViewHost* old_host,
260                              content::RenderViewHost* new_host) override {
261     if (old_host != nullptr)
262       old_host->GetWidget()->RemoveInputEventObserver(this);
263     new_host->GetWidget()->AddInputEventObserver(this);
264   }
265 
DidFinishNavigation(content::NavigationHandle * navigation_handle)266   void DidFinishNavigation(
267       content::NavigationHandle* navigation_handle) override {
268     if (!navigation_handle->HasCommitted() ||
269         !navigation_handle->IsInMainFrame() ||
270         navigation_handle->IsSameDocument()) {
271       return;
272     }
273 
274     // Use the same SourceId that SourceUrlRecorderWebContentsObserver populates
275     // and updates.
276     ukm::SourceId new_source_id = ukm::ConvertToSourceId(
277         navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
278     DCHECK_NE(new_source_id, ukm_source_id_)
279         << "Expected a unique Source ID for the navigation";
280     ukm_source_id_ = new_source_id;
281 
282     // Reset the per-page data.
283     page_metrics_ = {};
284 
285     // Update navigation info.
286     page_metrics_.page_transition = navigation_handle->GetPageTransition();
287   }
288 
289   // Logs metrics for the tab when it stops loading instead of immediately
290   // after a navigation commits, so we can have some idea of its status and
291   // contents.
DidStopLoading()292   void DidStopLoading() override {
293     // Ignore load events in foreground tabs. The tab state of a foreground tab
294     // will be logged if/when it is backgrounded.
295     LogTabIfBackgrounded();
296   }
297 
OnVisibilityChanged(content::Visibility visibility)298   void OnVisibilityChanged(content::Visibility visibility) override {
299     // Record background tab UKMs and do associated bookkepping.
300     if (!web_contents()->IsBeingDestroyed()) {
301       // TODO(michaelpg): Consider treating occluded tabs as hidden.
302       if (visibility == content::Visibility::HIDDEN) {
303         WasHidden();
304       } else {
305         WasShown();
306       }
307     }
308   }
309 
WebContentsDestroyed()310   void WebContentsDestroyed() override {
311     if (was_replaced_)
312       return;
313 
314     // Log necessary metrics.
315     TabActivityWatcher::GetInstance()->OnTabClosed(this);
316   }
317 
318   // content::RenderWidgetHost::InputEventObserver:
OnInputEvent(const blink::WebInputEvent & event)319   void OnInputEvent(const blink::WebInputEvent& event) override {
320     if (blink::WebInputEvent::IsMouseEventType(event.GetType()))
321       page_metrics_.mouse_event_count++;
322     else if (blink::WebInputEvent::IsKeyboardEventType(event.GetType()))
323       page_metrics_.key_event_count++;
324     else if (blink::WebInputEvent::IsTouchEventType(event.GetType()))
325       page_metrics_.touch_event_count++;
326   }
327 
328   // Iterates through tabstrips to determine the index of |contents| in
329   // most-recently-used order out of all non-incognito tabs.
330   // Linear in the number of tabs (most users have <10 tabs open).
GetMRUFeatures()331   tab_ranker::MRUFeatures GetMRUFeatures() {
332     // If not in closing_all mode, calculate |mru_features_|.
333     mru_features_.index = 0;
334     mru_features_.total = 0;
335     for (Browser* browser : *BrowserList::GetInstance()) {
336       // Ignore incognito browsers.
337       if (browser->profile()->IsOffTheRecord())
338         continue;
339 
340       int count = browser->tab_strip_model()->count();
341       mru_features_.total += count;
342 
343       // Increment the MRU index for each WebContents that was foregrounded more
344       // recently than this one.
345       for (int i = 0; i < count; i++) {
346         auto* other = WebContentsData::FromWebContents(
347             browser->tab_strip_model()->GetWebContentsAt(i));
348         if (!other || this == other)
349           continue;
350 
351         if (!MoreRecentlyUsed(this, other))
352           mru_features_.index++;
353       }
354     }
355     return mru_features_;
356   }
357 
358   // Returns whether |webcontents_a| is more recently used than |webcontents_b|.
359   // A webcontents is more recently used iff it has larger (later)
360   // |foregrounded_time_|; or |creation_time_| if they were never foregrounded.
MoreRecentlyUsed(TabActivityWatcher::WebContentsData * webcontents_a,TabActivityWatcher::WebContentsData * const webcontents_b)361   static bool MoreRecentlyUsed(
362       TabActivityWatcher::WebContentsData* webcontents_a,
363       TabActivityWatcher::WebContentsData* const webcontents_b) {
364     return webcontents_a->foregrounded_time_ >
365                webcontents_b->foregrounded_time_ ||
366            (webcontents_a->foregrounded_time_ ==
367                 webcontents_b->foregrounded_time_ &&
368             webcontents_a->creation_time_ > webcontents_b->creation_time_);
369   }
370 
371   // Returns the tabfeatures of current tab by combining TabMetrics,
372   // WindowFeatures and MRUFeatures.
373   // TODO(charleszhao): refactor TabMetricsLogger::GetTabFeatures to return a
374   // full TabFeatures instead of a partial TabFeatures.
GetTabFeatures()375   base::Optional<TabFeatures> GetTabFeatures() {
376     if (web_contents()->IsBeingDestroyed() || backgrounded_time_.is_null())
377       return base::nullopt;
378     // For tab features.
379     base::Optional<TabFeatures> tab =
380         TabMetricsLogger::GetTabFeatures(page_metrics_, web_contents());
381     if (!tab.has_value())
382       return tab;
383 
384     tab->time_from_backgrounded =
385         backgrounded_time_.is_null()
386             ? 0
387             : (NowTicks() - backgrounded_time_).InMilliseconds();
388 
389     // For mru features.
390     const tab_ranker::MRUFeatures& mru = GetMRUFeatures();
391     tab->mru_index = mru.index;
392     tab->total_tab_count = mru.total;
393 
394     // For frecency_score;
395     tab->frecency_score = GetFrecencyScore();
396 
397     return tab;
398   }
399 
400   // Collect current ForegroundedOrClosedMetrics and send to ukm.
LogForegroundedOrClosedMetrics(bool is_foregrounded)401   void LogForegroundedOrClosedMetrics(bool is_foregrounded) {
402     // If background time logging is disabled, then we only log the case where
403     // the label_id_ != 0 (a feature is logged and a label has not been logged).
404     if (DisableBackgroundLogWithTabRanker() && label_id_ == 0)
405       return;
406 
407     TabMetricsLogger::ForegroundedOrClosedMetrics metrics;
408     metrics.is_foregrounded = is_foregrounded;
409     metrics.is_discarded = discarded_since_backgrounded_;
410     metrics.time_from_backgrounded =
411         (NowTicks() - backgrounded_time_).InMilliseconds();
412     metrics.label_id = label_id_;
413 
414     TabActivityWatcher::GetInstance()
415         ->tab_metrics_logger_->LogForegroundedOrClosedMetrics(ukm_source_id_,
416                                                               metrics);
417     // label_id_ is reset whenever a label is logged.
418     // A new label_id_ is generated when a query happens inside
419     // CalculateReactivationScore, after that this ForegroundedOrClosed logging
420     // can happen many times (tabs may get backgrounded and reactivated several
421     // times). In such cases, we only count the first time as the true label,
422     // the rest are considered to be query time logging irrelevant, for which we
423     // log with label_id == 0.
424     label_id_ = 0;
425   }
426 
427   // Returns frecency score of this tab.
428   // NOTE: we don't apply decay for all reactivations, instead we accumulate
429   // them as reactivations_since_last_update and applied all together when the
430   // score is queried.
GetFrecencyScore()431   float GetFrecencyScore() {
432     const int reactivations_since_last_update =
433         reactivation_index - frecency_score_.update_index;
434     if (reactivations_since_last_update > 0) {
435       frecency_score_.score *=
436           std::pow(kFrecencyScoreDecay, reactivations_since_last_update);
437       frecency_score_.update_index = reactivation_index;
438     }
439     return frecency_score_.score;
440   }
441 
442   // Updates frecency score of current tab when it is reactivated.
UpdateFrecencyScoreOnReactivation()443   void UpdateFrecencyScoreOnReactivation() {
444     ++reactivation_index;
445     // Updates the current score.
446     frecency_score_.score = GetFrecencyScore() + 1.0f - kFrecencyScoreDecay;
447   }
448 
449   // Updated when a navigation is finished.
450   ukm::SourceId ukm_source_id_ = 0;
451 
452   // When the tab was created.
453   base::TimeTicks creation_time_;
454 
455   // The most recent time the tab became backgrounded. This happens when a
456   // different tab in the tabstrip is activated or the tab's window is hidden.
457   base::TimeTicks backgrounded_time_;
458 
459   // The most recent time the tab became foregrounded. This happens when the
460   // tab becomes the active tab in the tabstrip or when the active tab's window
461   // is activated.
462   base::TimeTicks foregrounded_time_;
463 
464   // Stores current page stats for the tab.
465   TabMetricsLogger::PageMetrics page_metrics_;
466 
467   // Set to true when the WebContents has been detached from its tab.
468   bool is_detached_ = false;
469 
470   // If true, future events such as the tab being destroyed won't be logged.
471   bool was_replaced_ = false;
472 
473   // MRUFeatures of this WebContents, updated only before ForegroundedOrClosed
474   // event is logged.
475   tab_ranker::MRUFeatures mru_features_;
476 
477   // Whether this tab is currently in discarded state.
478   bool discarded_since_backgrounded_ = false;
479 
480   // An int64 random label to pair TabFeatures with ForegroundedOrClosed event.
481   int64_t label_id_ = 0;
482 
483   // Fecency score of this tab.
484   FrecencyScore frecency_score_;
485 
486   WEB_CONTENTS_USER_DATA_KEY_DECL();
487 
488   DISALLOW_COPY_AND_ASSIGN(WebContentsData);
489 };
490 
WEB_CONTENTS_USER_DATA_KEY_IMPL(TabActivityWatcher::WebContentsData)491 WEB_CONTENTS_USER_DATA_KEY_IMPL(TabActivityWatcher::WebContentsData)
492 
493 TabActivityWatcher::TabActivityWatcher()
494     : tab_metrics_logger_(std::make_unique<TabMetricsLogger>()),
495       browser_tab_strip_tracker_(this, this),
496       predictor_(std::make_unique<tab_ranker::TabScorePredictor>()) {
497   BrowserList::AddObserver(this);
498   browser_tab_strip_tracker_.Init();
499 }
500 
~TabActivityWatcher()501 TabActivityWatcher::~TabActivityWatcher() {
502   BrowserList::RemoveObserver(this);
503 }
504 
CalculateReactivationScore(content::WebContents * web_contents)505 base::Optional<float> TabActivityWatcher::CalculateReactivationScore(
506     content::WebContents* web_contents) {
507   WebContentsData* web_contents_data =
508       WebContentsData::FromWebContents(web_contents);
509   if (!web_contents_data)
510     return base::nullopt;
511   return web_contents_data->CalculateReactivationScore();
512 }
513 
LogAndMaybeSortLifecycleUnitWithTabRanker(std::vector<LifecycleUnit * > * tabs)514 void TabActivityWatcher::LogAndMaybeSortLifecycleUnitWithTabRanker(
515     std::vector<LifecycleUnit*>* tabs) {
516   // Set query_id so that all TabFeatures logged in this query can be joined.
517   tab_metrics_logger_->set_query_id(NewInt64ForLabelIdOrQueryId());
518 
519   const bool should_sort_tabs =
520       base::FeatureList::IsEnabled(features::kTabRanker);
521 
522   std::map<int32_t, base::Optional<TabFeatures>> tab_features;
523   for (auto* lifecycle_unit : *tabs) {
524     auto* lifecycle_unit_external =
525         lifecycle_unit->AsTabLifecycleUnitExternal();
526     // the lifecycle_unit_external is nullptr in the unit test
527     // TabManagerDelegateTest::KillMultipleProcesses.
528     if (!lifecycle_unit_external) {
529       tab_features[lifecycle_unit->GetID()] = base::nullopt;
530       continue;
531     }
532     WebContentsData* web_contents_data = WebContentsData::FromWebContents(
533         lifecycle_unit_external->GetWebContents());
534 
535     // The web_contents_data can be nullptr in some cases.
536     // TODO(crbug.com/1019482): move the creation of WebContentsData to
537     // TabHelpers::AttachTabHelpers.
538     if (!web_contents_data) {
539       tab_features[lifecycle_unit->GetID()] = base::nullopt;
540       continue;
541     }
542 
543     const base::Optional<TabFeatures> tab = web_contents_data->GetTabFeatures();
544     web_contents_data->LogCurrentTabFeatures(tab);
545 
546     // No reason to store TabFeatures if TabRanker is disabled.
547     if (should_sort_tabs) {
548       tab_features[lifecycle_unit->GetID()] = tab;
549     }
550   }
551 
552   // Directly return if TabRanker is disabled.
553   if (!should_sort_tabs)
554     return;
555 
556   const std::map<int32_t, float> reactivation_scores =
557       predictor_->ScoreTabs(tab_features);
558   // Sort with larger reactivation_score first (desending importance).
559   std::sort(tabs->begin(), tabs->end(),
560             [&reactivation_scores](const LifecycleUnit* const a,
561                                    const LifecycleUnit* const b) {
562               return reactivation_scores.at(a->GetID()) >
563                      reactivation_scores.at(b->GetID());
564             });
565 }
566 
OnBrowserSetLastActive(Browser * browser)567 void TabActivityWatcher::OnBrowserSetLastActive(Browser* browser) {
568   if (browser->tab_strip_model()->closing_all())
569     return;
570 
571   content::WebContents* active_contents =
572       browser->tab_strip_model()->GetActiveWebContents();
573   if (!active_contents)
574     return;
575 
576   // Don't assume the WebContentsData already exists in case activation happens
577   // before the tabstrip is fully updated.
578   WebContentsData* web_contents_data =
579       WebContentsData::FromWebContents(active_contents);
580   if (web_contents_data)
581     web_contents_data->TabWindowActivated();
582 }
583 
OnTabStripModelChanged(TabStripModel * tab_strip_model,const TabStripModelChange & change,const TabStripSelectionChange & selection)584 void TabActivityWatcher::OnTabStripModelChanged(
585     TabStripModel* tab_strip_model,
586     const TabStripModelChange& change,
587     const TabStripSelectionChange& selection) {
588   switch (change.type()) {
589     case TabStripModelChange::kInserted: {
590       for (const auto& contents : change.GetInsert()->contents) {
591         // Ensure the WebContentsData is created to observe this WebContents
592         // since it may represent a newly created tab.
593         WebContentsData::CreateForWebContents(contents.contents);
594         WebContentsData::FromWebContents(contents.contents)
595             ->TabInserted(selection.new_contents == contents.contents);
596       }
597       break;
598     }
599     case TabStripModelChange::kRemoved: {
600       for (const auto& contents : change.GetRemove()->contents)
601         WebContentsData::FromWebContents(contents.contents)->TabDetached();
602       break;
603     }
604     case TabStripModelChange::kReplaced: {
605       auto* replace = change.GetReplace();
606       WebContentsData* old_web_contents_data =
607           WebContentsData::FromWebContents(replace->old_contents);
608       old_web_contents_data->WasReplaced();
609 
610       // Ensure the WebContentsData is created to observe this WebContents
611       // since it likely hasn't been inserted into a tabstrip before.
612       WebContentsData::CreateForWebContents(replace->new_contents);
613 
614       WebContentsData::FromWebContents(replace->new_contents)
615           ->DidReplace(*old_web_contents_data);
616       break;
617     }
618     case TabStripModelChange::kMoved:
619     case TabStripModelChange::kSelectionOnly:
620       break;
621   }
622 }
623 
TabPinnedStateChanged(TabStripModel * tab_strip_model,content::WebContents * contents,int index)624 void TabActivityWatcher::TabPinnedStateChanged(TabStripModel* tab_strip_model,
625                                                content::WebContents* contents,
626                                                int index) {
627   WebContentsData::FromWebContents(contents)->LogTabIfBackgrounded();
628 }
629 
ShouldTrackBrowser(Browser * browser)630 bool TabActivityWatcher::ShouldTrackBrowser(Browser* browser) {
631   // Don't track incognito browsers. This is also enforced by UKM.
632   // TODO(michaelpg): Keep counters for incognito browsers so we can score them
633   // using the TabScorePredictor. We should be able to do this without logging
634   // these values.
635   return !browser->profile()->IsOffTheRecord();
636 }
637 
ResetForTesting()638 void TabActivityWatcher::ResetForTesting() {
639   tab_metrics_logger_ = std::make_unique<TabMetricsLogger>();
640   predictor_ = std::make_unique<tab_ranker::TabScorePredictor>();
641   internal_id_for_logging = 0;
642 }
643 
644 // static
GetInstance()645 TabActivityWatcher* TabActivityWatcher::GetInstance() {
646   static base::NoDestructor<TabActivityWatcher> instance;
647   return instance.get();
648 }
649 
OnTabClosed(WebContentsData * web_contents_data)650 void TabActivityWatcher::OnTabClosed(WebContentsData* web_contents_data) {
651   // Log ForegroundedOrClosed event.
652   if (!web_contents_data->backgrounded_time_.is_null()) {
653     web_contents_data->LogForegroundedOrClosedMetrics(
654         false /*is_foregrounded */);
655   }
656 }
657 
658 }  // namespace resource_coordinator
659