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/metrics/tab_stats_tracker.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/power_monitor/power_monitor.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/time/time.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/resource_coordinator/lifecycle_unit.h"
21 #include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h"
22 #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
23 #include "chrome/browser/resource_coordinator/tab_manager.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_list.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/tabs/tab_strip_model.h"
28 #include "chrome/common/buildflags.h"
29 #include "chrome/common/pref_names.h"
30 #include "components/metrics/daily_event.h"
31 #include "components/prefs/pref_registry_simple.h"
32 #include "components/prefs/pref_service.h"
33 #include "components/ukm/content/source_url_recorder.h"
34 #include "services/metrics/public/cpp/ukm_builders.h"
35 #include "services/metrics/public/cpp/ukm_source_id.h"
36 #include "ui/gfx/geometry/rect.h"
37 #include "ui/gfx/geometry/size.h"
38
39 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
40 #include "chrome/browser/background/background_mode_manager.h"
41 #endif // BUILDFLAG(ENABLE_BACKGROUND_MODE)
42
43 namespace metrics {
44
45 namespace {
46
47 // The interval at which the DailyEvent::CheckInterval function should be
48 // called.
49 constexpr base::TimeDelta kDailyEventIntervalTimeDelta =
50 base::TimeDelta::FromMinutes(30);
51
52 // The intervals at which we report the number of unused tabs. This is used for
53 // all the tab usage histograms listed below.
54 //
55 // The 'Tabs.TabUsageIntervalLength' histogram suffixes entry in histograms.xml
56 // should be kept in sync with these values.
57 constexpr base::TimeDelta kTabUsageReportingIntervals[] = {
58 base::TimeDelta::FromSeconds(30), base::TimeDelta::FromMinutes(1),
59 base::TimeDelta::FromMinutes(10), base::TimeDelta::FromHours(1),
60 base::TimeDelta::FromHours(5), base::TimeDelta::FromHours(12)};
61
62 #if defined(OS_WIN)
63 const base::TimeDelta kNativeWindowOcclusionCalculationInterval =
64 base::TimeDelta::FromMinutes(10);
65 #endif
66
67 // The interval at which the heartbeat tab metrics should be reported.
68 const base::TimeDelta kTabsHeartbeatReportingInterval =
69 base::TimeDelta::FromMinutes(5);
70
71 // The global TabStatsTracker instance.
72 TabStatsTracker* g_tab_stats_tracker_instance = nullptr;
73
74 // Ensure that an interval is a valid one (i.e. listed in
75 // |kTabUsageReportingIntervals|).
IsValidInterval(base::TimeDelta interval)76 bool IsValidInterval(base::TimeDelta interval) {
77 return base::Contains(kTabUsageReportingIntervals, interval);
78 }
79
80 } // namespace
81
82 // static
83 const char TabStatsTracker::kTabStatsDailyEventHistogramName[] =
84 "Tabs.TabsStatsDailyEventInterval";
85 const char TabStatsTracker::UmaStatsReportingDelegate::
86 kNumberOfTabsOnResumeHistogramName[] = "Tabs.NumberOfTabsOnResume";
87 const char
88 TabStatsTracker::UmaStatsReportingDelegate::kMaxTabsInADayHistogramName[] =
89 "Tabs.MaxTabsInADay";
90 const char TabStatsTracker::UmaStatsReportingDelegate::
91 kMaxTabsPerWindowInADayHistogramName[] = "Tabs.MaxTabsPerWindowInADay";
92 const char TabStatsTracker::UmaStatsReportingDelegate::
93 kMaxWindowsInADayHistogramName[] = "Tabs.MaxWindowsInADay";
94
95 // Tab usage histograms.
96 const char TabStatsTracker::UmaStatsReportingDelegate::
97 kUnusedAndClosedInIntervalHistogramNameBase[] =
98 "Tabs.UnusedAndClosedInInterval.Count";
99 const char TabStatsTracker::UmaStatsReportingDelegate::
100 kUnusedTabsInIntervalHistogramNameBase[] = "Tabs.UnusedInInterval.Count";
101 const char TabStatsTracker::UmaStatsReportingDelegate::
102 kUsedAndClosedInIntervalHistogramNameBase[] =
103 "Tabs.UsedAndClosedInInterval.Count";
104 const char TabStatsTracker::UmaStatsReportingDelegate::
105 kUsedTabsInIntervalHistogramNameBase[] = "Tabs.UsedInInterval.Count";
106
107 const char
108 TabStatsTracker::UmaStatsReportingDelegate::kTabCountHistogramName[] =
109 "Tabs.TabCount";
110 const char
111 TabStatsTracker::UmaStatsReportingDelegate::kWindowCountHistogramName[] =
112 "Tabs.WindowCount";
113
114 const char
115 TabStatsTracker::UmaStatsReportingDelegate::kWindowWidthHistogramName[] =
116 "Tabs.WindowWidth";
117
tab_stats() const118 const TabStatsDataStore::TabsStats& TabStatsTracker::tab_stats() const {
119 return tab_stats_data_store_->tab_stats();
120 }
121
TabStatsTracker(PrefService * pref_service)122 TabStatsTracker::TabStatsTracker(PrefService* pref_service)
123 : reporting_delegate_(std::make_unique<UmaStatsReportingDelegate>()),
124 delegate_(std::make_unique<TabStatsTrackerDelegate>()),
125 tab_stats_data_store_(std::make_unique<TabStatsDataStore>(pref_service)),
126 daily_event_(
127 std::make_unique<DailyEvent>(pref_service,
128 ::prefs::kTabStatsDailySample,
129 kTabStatsDailyEventHistogramName)) {
130 DCHECK(pref_service);
131 // Get the list of existing windows/tabs. There shouldn't be any if this is
132 // initialized at startup but this will ensure that the counts stay accurate
133 // if the initialization gets moved to after the creation of the first tab.
134 BrowserList* browser_list = BrowserList::GetInstance();
135 for (Browser* browser : *browser_list) {
136 OnBrowserAdded(browser);
137 for (int i = 0; i < browser->tab_strip_model()->count(); ++i)
138 OnInitialOrInsertedTab(browser->tab_strip_model()->GetWebContentsAt(i));
139 tab_stats_data_store_->UpdateMaxTabsPerWindowIfNeeded(
140 static_cast<size_t>(browser->tab_strip_model()->count()));
141 }
142
143 browser_list->AddObserver(this);
144 base::PowerMonitor::AddObserver(this);
145
146 daily_event_->AddObserver(std::make_unique<TabStatsDailyObserver>(
147 reporting_delegate_.get(), tab_stats_data_store_.get()));
148 // Call the CheckInterval method to see if the data need to be immediately
149 // reported.
150 daily_event_->CheckInterval();
151 daily_event_timer_.Start(FROM_HERE, kDailyEventIntervalTimeDelta,
152 daily_event_.get(), &DailyEvent::CheckInterval);
153
154 // Initialize the interval maps and timers associated with them.
155 for (base::TimeDelta interval : kTabUsageReportingIntervals) {
156 TabStatsDataStore::TabsStateDuringIntervalMap* interval_map =
157 tab_stats_data_store_->AddInterval();
158 // Setup the timer associated with this interval.
159 std::unique_ptr<base::RepeatingTimer> timer =
160 std::make_unique<base::RepeatingTimer>();
161 timer->Start(
162 FROM_HERE, interval,
163 base::BindRepeating(&TabStatsTracker::OnInterval,
164 base::Unretained(this), interval, interval_map));
165 usage_interval_timers_.push_back(std::move(timer));
166 }
167
168 // The native window occlusion calculation is specific to Windows.
169 #if defined(OS_WIN)
170 native_window_occlusion_timer_.Start(
171 FROM_HERE, kNativeWindowOcclusionCalculationInterval,
172 base::BindRepeating(
173 &TabStatsTracker::CalculateAndRecordNativeWindowVisibilities,
174 base::Unretained(this)));
175 #endif
176
177 heartbeat_timer_.Start(FROM_HERE, kTabsHeartbeatReportingInterval,
178 base::BindRepeating(&TabStatsTracker::OnHeartbeatEvent,
179 base::Unretained(this)));
180 }
181
~TabStatsTracker()182 TabStatsTracker::~TabStatsTracker() {
183 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
184 BrowserList::GetInstance()->RemoveObserver(this);
185 }
186
187 // static
SetInstance(std::unique_ptr<TabStatsTracker> instance)188 void TabStatsTracker::SetInstance(std::unique_ptr<TabStatsTracker> instance) {
189 DCHECK_EQ(nullptr, g_tab_stats_tracker_instance);
190 g_tab_stats_tracker_instance = instance.release();
191 }
192
GetInstance()193 TabStatsTracker* TabStatsTracker::GetInstance() {
194 return g_tab_stats_tracker_instance;
195 }
196
RegisterPrefs(PrefRegistrySimple * registry)197 void TabStatsTracker::RegisterPrefs(PrefRegistrySimple* registry) {
198 registry->RegisterIntegerPref(::prefs::kTabStatsTotalTabCountMax, 0);
199 registry->RegisterIntegerPref(::prefs::kTabStatsMaxTabsPerWindow, 0);
200 registry->RegisterIntegerPref(::prefs::kTabStatsWindowCountMax, 0);
201 DailyEvent::RegisterPref(registry, ::prefs::kTabStatsDailySample);
202 }
203
SetDelegateForTesting(std::unique_ptr<TabStatsTrackerDelegate> new_delegate)204 void TabStatsTracker::SetDelegateForTesting(
205 std::unique_ptr<TabStatsTrackerDelegate> new_delegate) {
206 delegate_ = std::move(new_delegate);
207 }
208
OnDailyEvent(DailyEvent::IntervalType type)209 void TabStatsTracker::TabStatsDailyObserver::OnDailyEvent(
210 DailyEvent::IntervalType type) {
211 reporting_delegate_->ReportDailyMetrics(data_store_->tab_stats());
212 data_store_->ResetMaximumsToCurrentState();
213 }
214
215 class TabStatsTracker::WebContentsUsageObserver
216 : public content::WebContentsObserver {
217 public:
WebContentsUsageObserver(content::WebContents * web_contents,TabStatsTracker * tab_stats_tracker)218 WebContentsUsageObserver(content::WebContents* web_contents,
219 TabStatsTracker* tab_stats_tracker)
220 : content::WebContentsObserver(web_contents),
221 tab_stats_tracker_(tab_stats_tracker),
222 ukm_source_id_(ukm::GetSourceIdForWebContentsDocument(web_contents)) {}
223
224 // content::WebContentsObserver:
DidStartNavigation(content::NavigationHandle * navigation_handle)225 void DidStartNavigation(
226 content::NavigationHandle* navigation_handle) override {
227 // Treat browser-initiated navigations as user interactions.
228 if (!navigation_handle->IsRendererInitiated()) {
229 tab_stats_tracker_->tab_stats_data_store()->OnTabInteraction(
230 web_contents());
231 }
232 }
233
DidFinishNavigation(content::NavigationHandle * navigation_handle)234 void DidFinishNavigation(
235 content::NavigationHandle* navigation_handle) override {
236 if (!navigation_handle->HasCommitted() ||
237 !navigation_handle->IsInMainFrame() ||
238 navigation_handle->IsSameDocument()) {
239 return;
240 }
241 // Update navigation time for UKM reporting.
242 navigation_time_ = navigation_handle->NavigationStart();
243 ukm_source_id_ = ukm::ConvertToSourceId(
244 navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
245 }
246
DidGetUserInteraction(const blink::WebInputEvent & event)247 void DidGetUserInteraction(const blink::WebInputEvent& event) override {
248 tab_stats_tracker_->tab_stats_data_store()->OnTabInteraction(
249 web_contents());
250 }
251
OnVisibilityChanged(content::Visibility visibility)252 void OnVisibilityChanged(content::Visibility visibility) override {
253 if (visibility == content::Visibility::VISIBLE)
254 tab_stats_tracker_->tab_stats_data_store()->OnTabVisible(web_contents());
255 }
256
WebContentsDestroyed()257 void WebContentsDestroyed() override {
258 if (ukm_source_id_) {
259 ukm::builders::TabManager_TabLifetime(ukm_source_id_)
260 .SetTimeSinceNavigation(
261 (base::TimeTicks::Now() - navigation_time_).InMilliseconds())
262 .Record(ukm::UkmRecorder::Get());
263 }
264
265 tab_stats_tracker_->OnWebContentsDestroyed(web_contents());
266 // The call above will free |this| and so nothing should be done on this
267 // object starting from here.
268 }
269
270 private:
271 TabStatsTracker* tab_stats_tracker_;
272 // The last navigation time associated with this tab.
273 base::TimeTicks navigation_time_ = base::TimeTicks::Now();
274 // Updated when a navigation is finished.
275 ukm::SourceId ukm_source_id_ = 0;
276
277 DISALLOW_COPY_AND_ASSIGN(WebContentsUsageObserver);
278 };
279
OnBrowserAdded(Browser * browser)280 void TabStatsTracker::OnBrowserAdded(Browser* browser) {
281 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
282 tab_stats_data_store_->OnWindowAdded();
283 browser->tab_strip_model()->AddObserver(this);
284 }
285
OnBrowserRemoved(Browser * browser)286 void TabStatsTracker::OnBrowserRemoved(Browser* browser) {
287 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
288 tab_stats_data_store_->OnWindowRemoved();
289 browser->tab_strip_model()->RemoveObserver(this);
290 }
291
OnTabStripModelChanged(TabStripModel * tab_strip_model,const TabStripModelChange & change,const TabStripSelectionChange & selection)292 void TabStatsTracker::OnTabStripModelChanged(
293 TabStripModel* tab_strip_model,
294 const TabStripModelChange& change,
295 const TabStripSelectionChange& selection) {
296 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
297 if (change.type() == TabStripModelChange::kInserted) {
298 for (const auto& contents : change.GetInsert()->contents)
299 OnInitialOrInsertedTab(contents.contents);
300
301 tab_stats_data_store_->UpdateMaxTabsPerWindowIfNeeded(
302 static_cast<size_t>(tab_strip_model->count()));
303
304 return;
305 }
306
307 if (change.type() == TabStripModelChange::kReplaced) {
308 auto* replace = change.GetReplace();
309 tab_stats_data_store_->OnTabReplaced(replace->old_contents,
310 replace->new_contents);
311 web_contents_usage_observers_.insert(std::make_pair(
312 replace->new_contents, std::make_unique<WebContentsUsageObserver>(
313 replace->new_contents, this)));
314 web_contents_usage_observers_.erase(replace->old_contents);
315 }
316 }
317
TabChangedAt(content::WebContents * web_contents,int index,TabChangeType change_type)318 void TabStatsTracker::TabChangedAt(content::WebContents* web_contents,
319 int index,
320 TabChangeType change_type) {
321 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
322 // Ignore 'loading' and 'title' changes, we're only interested in audio here.
323 if (change_type != TabChangeType::kAll)
324 return;
325 if (web_contents->IsCurrentlyAudible())
326 tab_stats_data_store_->OnTabAudible(web_contents);
327 }
328
OnResume()329 void TabStatsTracker::OnResume() {
330 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
331 reporting_delegate_->ReportTabCountOnResume(
332 tab_stats_data_store_->tab_stats().total_tab_count);
333 }
334
OnInterval(base::TimeDelta interval,TabStatsDataStore::TabsStateDuringIntervalMap * interval_map)335 void TabStatsTracker::OnInterval(
336 base::TimeDelta interval,
337 TabStatsDataStore::TabsStateDuringIntervalMap* interval_map) {
338 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
339 DCHECK(interval_map);
340 reporting_delegate_->ReportUsageDuringInterval(*interval_map, interval);
341 // Reset the interval data.
342 tab_stats_data_store_->ResetIntervalData(interval_map);
343 }
344
OnInitialOrInsertedTab(content::WebContents * web_contents)345 void TabStatsTracker::OnInitialOrInsertedTab(
346 content::WebContents* web_contents) {
347 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
348 // If we already have a WebContentsObserver for this tab then it means that
349 // it's already tracked and it's being dragged into a new window, there's
350 // nothing to do here.
351 if (!base::Contains(web_contents_usage_observers_, web_contents)) {
352 tab_stats_data_store_->OnTabAdded(web_contents);
353 web_contents_usage_observers_.insert(std::make_pair(
354 web_contents,
355 std::make_unique<WebContentsUsageObserver>(web_contents, this)));
356 }
357 }
358
OnWebContentsDestroyed(content::WebContents * web_contents)359 void TabStatsTracker::OnWebContentsDestroyed(
360 content::WebContents* web_contents) {
361 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
362 DCHECK(base::Contains(web_contents_usage_observers_, web_contents));
363 web_contents_usage_observers_.erase(
364 web_contents_usage_observers_.find(web_contents));
365 tab_stats_data_store_->OnTabRemoved(web_contents);
366 }
367
OnHeartbeatEvent()368 void TabStatsTracker::OnHeartbeatEvent() {
369 reporting_delegate_->ReportHeartbeatMetrics(
370 tab_stats_data_store_->tab_stats());
371 }
372
ReportTabCountOnResume(size_t tab_count)373 void TabStatsTracker::UmaStatsReportingDelegate::ReportTabCountOnResume(
374 size_t tab_count) {
375 // Don't report the number of tabs on resume if Chrome is running in
376 // background with no visible window.
377 if (IsChromeBackgroundedWithoutWindows())
378 return;
379 UMA_HISTOGRAM_COUNTS_10000(kNumberOfTabsOnResumeHistogramName, tab_count);
380 }
381
ReportDailyMetrics(const TabStatsDataStore::TabsStats & tab_stats)382 void TabStatsTracker::UmaStatsReportingDelegate::ReportDailyMetrics(
383 const TabStatsDataStore::TabsStats& tab_stats) {
384 // Don't report the counts if they're equal to 0, this means that Chrome has
385 // only been running in the background since the last time the metrics have
386 // been reported.
387 if (tab_stats.total_tab_count_max == 0)
388 return;
389 UMA_HISTOGRAM_COUNTS_10000(kMaxTabsInADayHistogramName,
390 tab_stats.total_tab_count_max);
391 UMA_HISTOGRAM_COUNTS_10000(kMaxTabsPerWindowInADayHistogramName,
392 tab_stats.max_tab_per_window);
393 UMA_HISTOGRAM_COUNTS_10000(kMaxWindowsInADayHistogramName,
394 tab_stats.window_count_max);
395 }
396
ReportHeartbeatMetrics(const TabStatsDataStore::TabsStats & tab_stats)397 void TabStatsTracker::UmaStatsReportingDelegate::ReportHeartbeatMetrics(
398 const TabStatsDataStore::TabsStats& tab_stats) {
399 // Don't report anything if Chrome is running in background with no visible
400 // window.
401 if (IsChromeBackgroundedWithoutWindows())
402 return;
403
404 UMA_HISTOGRAM_COUNTS_10000(kTabCountHistogramName, tab_stats.total_tab_count);
405 UMA_HISTOGRAM_COUNTS_10000(kWindowCountHistogramName, tab_stats.window_count);
406
407 // Record the width of all open browser windows with tabs.
408 for (Browser* browser : *BrowserList::GetInstance()) {
409 if (browser->type() != Browser::TYPE_NORMAL)
410 continue;
411
412 const BrowserWindow* window = browser->window();
413
414 // Only consider visible windows.
415 if (!window->IsVisible() || window->IsMinimized())
416 continue;
417
418 // Get the window's size (in DIPs).
419 const gfx::Size window_size = browser->window()->GetBounds().size();
420
421 // If the size is for some reason 0 in either dimension, skip it.
422 if (window_size.IsEmpty())
423 continue;
424
425 // A 4K screen is 4096 pixels wide. Doubling this and rounding up to
426 // 10000 should give a reasonable upper bound on DIPs. For the
427 // minimum width, pick an arbitrary value of 100. Most screens are
428 // unlikely to be this small, and likewise a browser window's min
429 // width is around this size.
430 UMA_HISTOGRAM_CUSTOM_COUNTS(kWindowWidthHistogramName, window_size.width(),
431 100, 10000, 50);
432 }
433 }
434
ReportUsageDuringInterval(const TabStatsDataStore::TabsStateDuringIntervalMap & interval_map,base::TimeDelta interval)435 void TabStatsTracker::UmaStatsReportingDelegate::ReportUsageDuringInterval(
436 const TabStatsDataStore::TabsStateDuringIntervalMap& interval_map,
437 base::TimeDelta interval) {
438 // Counts the number of used/unused tabs during this interval, a tabs counts
439 // as unused if it hasn't been interacted with or visible during the duration
440 // of the interval.
441 size_t used_tabs = 0;
442 size_t used_and_closed_tabs = 0;
443 size_t unused_tabs = 0;
444 size_t unused_and_closed_tabs = 0;
445 for (const auto& iter : interval_map) {
446 // There's currently no distinction between a visible/audible tab and one
447 // that has been interacted with in these metrics.
448 // TODO(sebmarchand): Add a metric that track the number of tab that have
449 // been visible/audible but not interacted with during an interval,
450 // https://crbug.com/800828.
451 if (iter.second.interacted_during_interval ||
452 iter.second.visible_or_audible_during_interval) {
453 if (iter.second.exists_currently)
454 ++used_tabs;
455 else
456 ++used_and_closed_tabs;
457 } else {
458 if (iter.second.exists_currently)
459 ++unused_tabs;
460 else
461 ++unused_and_closed_tabs;
462 }
463 }
464
465 std::string used_and_closed_histogram_name = GetIntervalHistogramName(
466 UmaStatsReportingDelegate::kUsedAndClosedInIntervalHistogramNameBase,
467 interval);
468 std::string used_histogram_name = GetIntervalHistogramName(
469 UmaStatsReportingDelegate::kUsedTabsInIntervalHistogramNameBase,
470 interval);
471 std::string unused_and_closed_histogram_name = GetIntervalHistogramName(
472 UmaStatsReportingDelegate::kUnusedAndClosedInIntervalHistogramNameBase,
473 interval);
474 std::string unused_histogram_name = GetIntervalHistogramName(
475 UmaStatsReportingDelegate::kUnusedTabsInIntervalHistogramNameBase,
476 interval);
477
478 base::UmaHistogramCounts10000(used_and_closed_histogram_name,
479 used_and_closed_tabs);
480 base::UmaHistogramCounts10000(used_histogram_name, used_tabs);
481 base::UmaHistogramCounts10000(unused_and_closed_histogram_name,
482 unused_and_closed_tabs);
483 base::UmaHistogramCounts10000(unused_histogram_name, unused_tabs);
484 }
485
486 // static
487 std::string
GetIntervalHistogramName(const char * base_name,base::TimeDelta interval)488 TabStatsTracker::UmaStatsReportingDelegate::GetIntervalHistogramName(
489 const char* base_name,
490 base::TimeDelta interval) {
491 DCHECK(IsValidInterval(interval));
492 return base::StringPrintf("%s_%zu", base_name,
493 static_cast<size_t>(interval.InSeconds()));
494 }
495
496 bool TabStatsTracker::UmaStatsReportingDelegate::
IsChromeBackgroundedWithoutWindows()497 IsChromeBackgroundedWithoutWindows() {
498 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
499 if (g_browser_process && g_browser_process->background_mode_manager()
500 ->IsBackgroundWithoutWindows()) {
501 return true;
502 }
503 #endif // BUILDFLAG(ENABLE_BACKGROUND_MODE)
504 return false;
505 }
506
507 } // namespace metrics
508