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