Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(55)

Side by Side Diff: chrome/browser/memory/tab_manager.cc

Issue 2898033002: [TabManager] Move TabManager into chrome/browser/resource_coordinator. (Closed)
Patch Set: rebase Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/memory/tab_manager.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <set>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/bind_helpers.h"
15 #include "base/command_line.h"
16 #include "base/feature_list.h"
17 #include "base/macros.h"
18 #include "base/memory/memory_pressure_monitor.h"
19 #include "base/metrics/field_trial.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/observer_list.h"
22 #include "base/process/process.h"
23 #include "base/rand_util.h"
24 #include "base/strings/string16.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/string_util.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/threading/thread.h"
29 #include "base/time/tick_clock.h"
30 #include "build/build_config.h"
31 #include "chrome/browser/browser_process.h"
32 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
33 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
34 #include "chrome/browser/memory/oom_memory_details.h"
35 #include "chrome/browser/memory/tab_manager_observer.h"
36 #include "chrome/browser/memory/tab_manager_web_contents_data.h"
37 #include "chrome/browser/profiles/profile.h"
38 #include "chrome/browser/ui/browser.h"
39 #include "chrome/browser/ui/browser_list.h"
40 #include "chrome/browser/ui/browser_window.h"
41 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
42 #include "chrome/browser/ui/tabs/tab_strip_model.h"
43 #include "chrome/browser/ui/tabs/tab_utils.h"
44 #include "chrome/common/chrome_constants.h"
45 #include "chrome/common/chrome_features.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "chrome/common/url_constants.h"
48 #include "components/metrics/system_memory_stats_recorder.h"
49 #include "components/variations/variations_associated_data.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/navigation_controller.h"
52 #include "content/public/browser/render_process_host.h"
53 #include "content/public/browser/web_contents.h"
54 #include "content/public/common/page_importance_signals.h"
55
56 #if defined(OS_CHROMEOS)
57 #include "ash/multi_profile_uma.h"
58 #include "ash/shell_port.h"
59 #include "chrome/browser/memory/tab_manager_delegate_chromeos.h"
60 #include "components/user_manager/user_manager.h"
61 #endif
62
63 using base::TimeDelta;
64 using base::TimeTicks;
65 using content::BrowserThread;
66 using content::WebContents;
67
68 namespace memory {
69 namespace {
70
71 // The default interval in seconds after which to adjust the oom_score_adj
72 // value.
73 const int kAdjustmentIntervalSeconds = 10;
74
75 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
76 // For each period of this length record a statistic to indicate whether or not
77 // the user experienced a low memory event. If this interval is changed,
78 // Tabs.Discard.DiscardInLastMinute must be replaced with a new statistic.
79 const int kRecentTabDiscardIntervalSeconds = 60;
80 #endif
81
82 // The time during which a tab is protected from discarding after it stops being
83 // audible.
84 const int kAudioProtectionTimeSeconds = 60;
85
86 int FindWebContentsById(const TabStripModel* model,
87 int64_t target_web_contents_id) {
88 for (int idx = 0; idx < model->count(); idx++) {
89 WebContents* web_contents = model->GetWebContentsAt(idx);
90 int64_t web_contents_id = TabManager::IdFromWebContents(web_contents);
91 if (web_contents_id == target_web_contents_id)
92 return idx;
93 }
94
95 return -1;
96 }
97
98 } // namespace
99
100 ////////////////////////////////////////////////////////////////////////////////
101 // TabManager
102
103 constexpr base::TimeDelta TabManager::kDefaultMinTimeToPurge;
104
105 TabManager::TabManager()
106 : discard_count_(0),
107 recent_tab_discard_(false),
108 discard_once_(false),
109 #if !defined(OS_CHROMEOS)
110 minimum_protection_time_(base::TimeDelta::FromMinutes(10)),
111 #endif
112 browser_tab_strip_tracker_(this, nullptr, nullptr),
113 test_tick_clock_(nullptr),
114 weak_ptr_factory_(this) {
115 #if defined(OS_CHROMEOS)
116 delegate_.reset(new TabManagerDelegate(weak_ptr_factory_.GetWeakPtr()));
117 #endif
118 browser_tab_strip_tracker_.Init();
119 }
120
121 TabManager::~TabManager() {
122 Stop();
123 }
124
125 void TabManager::Start() {
126 #if defined(OS_WIN) || defined(OS_MACOSX)
127 // Note that discarding is now enabled by default. This check is kept as a
128 // kill switch.
129 // TODO(georgesak): remote this when deemed not needed anymore.
130 if (!base::FeatureList::IsEnabled(features::kAutomaticTabDiscarding))
131 return;
132
133 // Check the variation parameter to see if a tab is to be protected for an
134 // amount of time after being backgrounded. The value is in seconds. Default
135 // is 10 minutes if the variation is absent.
136 std::string minimum_protection_time_string =
137 variations::GetVariationParamValue(features::kAutomaticTabDiscarding.name,
138 "MinimumProtectionTime");
139 if (!minimum_protection_time_string.empty()) {
140 unsigned int minimum_protection_time_seconds = 0;
141 if (base::StringToUint(minimum_protection_time_string,
142 &minimum_protection_time_seconds)) {
143 if (minimum_protection_time_seconds > 0)
144 minimum_protection_time_ =
145 base::TimeDelta::FromSeconds(minimum_protection_time_seconds);
146 }
147 }
148 #endif
149
150 // Check if only one discard is allowed.
151 discard_once_ = CanOnlyDiscardOnce();
152
153 if (!update_timer_.IsRunning()) {
154 update_timer_.Start(FROM_HERE,
155 TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
156 this, &TabManager::UpdateTimerCallback);
157 }
158
159 // MemoryPressureMonitor is not implemented on Linux so far and tabs are never
160 // discarded.
161 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
162 if (!recent_tab_discard_timer_.IsRunning()) {
163 recent_tab_discard_timer_.Start(
164 FROM_HERE, TimeDelta::FromSeconds(kRecentTabDiscardIntervalSeconds),
165 this, &TabManager::RecordRecentTabDiscard);
166 }
167 start_time_ = NowTicks();
168 // Create a |MemoryPressureListener| to listen for memory events.
169 base::MemoryPressureMonitor* monitor = base::MemoryPressureMonitor::Get();
170 if (monitor) {
171 memory_pressure_listener_.reset(new base::MemoryPressureListener(
172 base::Bind(&TabManager::OnMemoryPressure, base::Unretained(this))));
173 base::MemoryPressureListener::MemoryPressureLevel level =
174 monitor->GetCurrentPressureLevel();
175 if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
176 OnMemoryPressure(level);
177 }
178 }
179 #endif
180 // purge-and-suspend param is used for Purge+Suspend finch experiment
181 // in the following way:
182 // https://docs.google.com/document/d/1hPHkKtXXBTlsZx9s-9U17XC-ofEIzPo9FYbBEc7 PPbk/edit?usp=sharing
183 std::string purge_and_suspend_time = variations::GetVariationParamValue(
184 "PurgeAndSuspend", "purge-and-suspend-time");
185 unsigned int min_time_to_purge_sec = 0;
186 if (purge_and_suspend_time.empty() ||
187 !base::StringToUint(purge_and_suspend_time, &min_time_to_purge_sec))
188 min_time_to_purge_ = kDefaultMinTimeToPurge;
189 else
190 min_time_to_purge_ = base::TimeDelta::FromSeconds(min_time_to_purge_sec);
191 }
192
193 void TabManager::Stop() {
194 update_timer_.Stop();
195 recent_tab_discard_timer_.Stop();
196 memory_pressure_listener_.reset();
197 }
198
199 int TabManager::FindTabStripModelById(int64_t target_web_contents_id,
200 TabStripModel** model) const {
201 DCHECK(model);
202 // TODO(tasak): Move this code to a TabStripModel enumeration delegate!
203 if (!test_tab_strip_models_.empty()) {
204 for (size_t i = 0; i < test_tab_strip_models_.size(); ++i) {
205 TabStripModel* local_model =
206 const_cast<TabStripModel*>(test_tab_strip_models_[i].first);
207 int idx = FindWebContentsById(local_model, target_web_contents_id);
208 if (idx != -1) {
209 *model = local_model;
210 return idx;
211 }
212 }
213
214 return -1;
215 }
216
217 for (auto* browser : *BrowserList::GetInstance()) {
218 TabStripModel* local_model = browser->tab_strip_model();
219 int idx = FindWebContentsById(local_model, target_web_contents_id);
220 if (idx != -1) {
221 *model = local_model;
222 return idx;
223 }
224 }
225
226 return -1;
227 }
228
229 TabStatsList TabManager::GetTabStats() const {
230 TabStatsList stats_list(GetUnsortedTabStats());
231
232 // Sort the collected data so that least desirable to be killed is first, most
233 // desirable is last.
234 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats);
235
236 return stats_list;
237 }
238
239 bool TabManager::IsTabDiscarded(content::WebContents* contents) const {
240 return GetWebContentsData(contents)->IsDiscarded();
241 }
242
243 bool TabManager::CanDiscardTab(int64_t target_web_contents_id) const {
244 TabStripModel* model;
245 int idx = FindTabStripModelById(target_web_contents_id, &model);
246
247 if (idx == -1)
248 return false;
249
250 WebContents* web_contents = model->GetWebContentsAt(idx);
251
252 // Do not discard tabs that don't have a valid URL (most probably they have
253 // just been opened and dicarding them would lose the URL).
254 // TODO(georgesak): Look into a workaround to be able to kill the tab without
255 // losing the pending navigation.
256 if (!web_contents->GetLastCommittedURL().is_valid() ||
257 web_contents->GetLastCommittedURL().is_empty()) {
258 return false;
259 }
260
261 // Do not discard tabs in which the user has entered text in a form, lest that
262 // state gets lost.
263 if (web_contents->GetPageImportanceSignals().had_form_interaction)
264 return false;
265
266 // Do not discard tabs that are playing either playing audio or accessing the
267 // microphone or camera as it's too distruptive to the user experience. Note
268 // that tabs that have recently stopped playing audio by at least
269 // |kAudioProtectionTimeSeconds| seconds are protected as well.
270 if (IsMediaTab(web_contents))
271 return false;
272
273 // Do not discard PDFs as they might contain entry that is not saved and they
274 // don't remember their scrolling positions. See crbug.com/547286 and
275 // crbug.com/65244.
276 // TODO(georgesak): Remove this workaround when the bugs are fixed.
277 if (web_contents->GetContentsMimeType() == "application/pdf")
278 return false;
279
280 // Do not discard a previously discarded tab if that's the desired behavior.
281 if (discard_once_ && GetWebContentsData(web_contents)->DiscardCount() > 0)
282 return false;
283
284 // Do not discard a recently used tab.
285 if (minimum_protection_time_.InSeconds() > 0) {
286 auto delta =
287 NowTicks() - GetWebContentsData(web_contents)->LastInactiveTime();
288 if (delta < minimum_protection_time_)
289 return false;
290 }
291
292 // Do not discard a tab that was explicitly disallowed to.
293 if (!IsTabAutoDiscardable(web_contents))
294 return false;
295
296 return true;
297 }
298
299 void TabManager::DiscardTab() {
300 #if defined(OS_CHROMEOS)
301 // Call Chrome OS specific low memory handling process.
302 if (base::FeatureList::IsEnabled(features::kArcMemoryManagement)) {
303 delegate_->LowMemoryKill(GetUnsortedTabStats());
304 return;
305 }
306 #endif
307 DiscardTabImpl();
308 }
309
310 WebContents* TabManager::DiscardTabById(int64_t target_web_contents_id) {
311 TabStripModel* model;
312 int index = FindTabStripModelById(target_web_contents_id, &model);
313
314 if (index == -1)
315 return nullptr;
316
317 VLOG(1) << "Discarding tab " << index << " id " << target_web_contents_id;
318
319 return DiscardWebContentsAt(index, model);
320 }
321
322 WebContents* TabManager::DiscardTabByExtension(content::WebContents* contents) {
323 if (contents)
324 return DiscardTabById(IdFromWebContents(contents));
325
326 return DiscardTabImpl();
327 }
328
329 void TabManager::LogMemoryAndDiscardTab() {
330 LogMemory("Tab Discards Memory details",
331 base::Bind(&TabManager::PurgeMemoryAndDiscardTab));
332 }
333
334 void TabManager::LogMemory(const std::string& title,
335 const base::Closure& callback) {
336 DCHECK_CURRENTLY_ON(BrowserThread::UI);
337 OomMemoryDetails::Log(title, callback);
338 }
339
340 void TabManager::set_test_tick_clock(base::TickClock* test_tick_clock) {
341 test_tick_clock_ = test_tick_clock;
342 }
343
344 // Things to collect on the browser thread (because TabStripModel isn't thread
345 // safe):
346 // 1) whether or not a tab is pinned
347 // 2) last time a tab was selected
348 // 3) is the tab currently selected
349 TabStatsList TabManager::GetUnsortedTabStats() const {
350 DCHECK_CURRENTLY_ON(BrowserThread::UI);
351 TabStatsList stats_list;
352 stats_list.reserve(32); // 99% of users have < 30 tabs open.
353
354 // TODO(chrisha): Move this code to a TabStripModel enumeration delegate!
355 if (!test_tab_strip_models_.empty()) {
356 for (size_t i = 0; i < test_tab_strip_models_.size(); ++i) {
357 AddTabStats(test_tab_strip_models_[i].first, // tab_strip_model
358 test_tab_strip_models_[i].second, // is_app
359 i == 0, // is_active
360 &stats_list);
361 }
362 } else {
363 // The code here can only be tested under a full browser test.
364 AddTabStats(&stats_list);
365 }
366
367 return stats_list;
368 }
369
370 void TabManager::AddObserver(TabManagerObserver* observer) {
371 observers_.AddObserver(observer);
372 }
373
374 void TabManager::RemoveObserver(TabManagerObserver* observer) {
375 observers_.RemoveObserver(observer);
376 }
377
378 void TabManager::set_minimum_protection_time_for_tests(
379 base::TimeDelta minimum_protection_time) {
380 minimum_protection_time_ = minimum_protection_time;
381 }
382
383 bool TabManager::IsTabAutoDiscardable(content::WebContents* contents) const {
384 return GetWebContentsData(contents)->IsAutoDiscardable();
385 }
386
387 void TabManager::SetTabAutoDiscardableState(content::WebContents* contents,
388 bool state) {
389 GetWebContentsData(contents)->SetAutoDiscardableState(state);
390 }
391
392 content::WebContents* TabManager::GetWebContentsById(
393 int64_t tab_contents_id) const {
394 TabStripModel* model = nullptr;
395 int index = FindTabStripModelById(tab_contents_id, &model);
396 if (index == -1)
397 return nullptr;
398 return model->GetWebContentsAt(index);
399 }
400
401 bool TabManager::CanSuspendBackgroundedRenderer(int render_process_id) const {
402 // A renderer can be purged if it's not playing media.
403 auto tab_stats = GetUnsortedTabStats();
404 for (auto& tab : tab_stats) {
405 if (tab.child_process_host_id != render_process_id)
406 continue;
407 WebContents* web_contents = GetWebContentsById(tab.tab_contents_id);
408 if (!web_contents)
409 return false;
410 if (IsMediaTab(web_contents))
411 return false;
412 }
413 return true;
414 }
415
416 // static
417 bool TabManager::CompareTabStats(const TabStats& first,
418 const TabStats& second) {
419 // Being currently selected is most important to protect.
420 if (first.is_selected != second.is_selected)
421 return first.is_selected;
422
423 // Non auto-discardable tabs are more important to protect.
424 if (first.is_auto_discardable != second.is_auto_discardable)
425 return !first.is_auto_discardable;
426
427 // Protect tabs with pending form entries.
428 if (first.has_form_entry != second.has_form_entry)
429 return first.has_form_entry;
430
431 // Protect streaming audio and video conferencing tabs as these are similar to
432 // active tabs.
433 if (first.is_media != second.is_media)
434 return first.is_media;
435
436 // Tab with internal web UI like NTP or Settings are good choices to discard,
437 // so protect non-Web UI and let the other conditionals finish the sort.
438 if (first.is_internal_page != second.is_internal_page)
439 return !first.is_internal_page;
440
441 // Being pinned is important to protect.
442 if (first.is_pinned != second.is_pinned)
443 return first.is_pinned;
444
445 // Being an app is important too, as it's the only visible surface in the
446 // window and should not be discarded.
447 if (first.is_app != second.is_app)
448 return first.is_app;
449
450 // TODO(jamescook): Incorporate sudden_termination_allowed into the sort
451 // order. This is currently not done because pages with unload handlers set
452 // sudden_termination_allowed false, and that covers too many common pages
453 // with ad networks and statistics scripts. Ideally check for beforeUnload
454 // handlers, which are likely to present a dialog asking if the user wants to
455 // discard state. crbug.com/123049.
456
457 // Being more recently active is more important.
458 return first.last_active > second.last_active;
459 }
460
461 // static
462 int64_t TabManager::IdFromWebContents(WebContents* web_contents) {
463 return reinterpret_cast<int64_t>(web_contents);
464 }
465
466 ///////////////////////////////////////////////////////////////////////////////
467 // TabManager, private:
468
469 void TabManager::OnDiscardedStateChange(content::WebContents* contents,
470 bool is_discarded) {
471 for (TabManagerObserver& observer : observers_)
472 observer.OnDiscardedStateChange(contents, is_discarded);
473 }
474
475 void TabManager::OnAutoDiscardableStateChange(content::WebContents* contents,
476 bool is_auto_discardable) {
477 for (TabManagerObserver& observer : observers_)
478 observer.OnAutoDiscardableStateChange(contents, is_auto_discardable);
479 }
480
481 // static
482 void TabManager::PurgeMemoryAndDiscardTab() {
483 TabManager* manager = g_browser_process->GetTabManager();
484 manager->PurgeBrowserMemory();
485 manager->DiscardTab();
486 }
487
488 // static
489 bool TabManager::IsInternalPage(const GURL& url) {
490 // There are many chrome:// UI URLs, but only look for the ones that users
491 // are likely to have open. Most of the benefit is the from NTP URL.
492 const char* const kInternalPagePrefixes[] = {
493 chrome::kChromeUIDownloadsURL, chrome::kChromeUIHistoryURL,
494 chrome::kChromeUINewTabURL, chrome::kChromeUISettingsURL,
495 };
496 // Prefix-match against the table above. Use strncmp to avoid allocating
497 // memory to convert the URL prefix constants into std::strings.
498 for (size_t i = 0; i < arraysize(kInternalPagePrefixes); ++i) {
499 if (!strncmp(url.spec().c_str(), kInternalPagePrefixes[i],
500 strlen(kInternalPagePrefixes[i])))
501 return true;
502 }
503 return false;
504 }
505
506 void TabManager::RecordDiscardStatistics() {
507 discard_count_++;
508
509 // TODO(jamescook): Maybe incorporate extension count?
510 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.TabCount", GetTabCount(), 1, 100,
511 50);
512 #if defined(OS_CHROMEOS)
513 // Record the discarded tab in relation to the amount of simultaneously
514 // logged in users.
515 if (ash::ShellPort::HasInstance()) {
516 ash::MultiProfileUMA::RecordDiscardedTab(
517 user_manager::UserManager::Get()->GetLoggedInUsers().size());
518 }
519 #endif
520 // TODO(jamescook): If the time stats prove too noisy, then divide up users
521 // based on how heavily they use Chrome using tab count as a proxy.
522 // Bin into <= 1, <= 2, <= 4, <= 8, etc.
523 if (last_discard_time_.is_null()) {
524 // This is the first discard this session.
525 TimeDelta interval = NowTicks() - start_time_;
526 int interval_seconds = static_cast<int>(interval.InSeconds());
527 // Record time in seconds over an interval of approximately 1 day.
528 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.InitialTime2", interval_seconds,
529 1, 100000, 50);
530 } else {
531 // Not the first discard, so compute time since last discard.
532 TimeDelta interval = NowTicks() - last_discard_time_;
533 int interval_ms = static_cast<int>(interval.InMilliseconds());
534 // Record time in milliseconds over an interval of approximately 1 day.
535 // Start at 100 ms to get extra resolution in the target 750 ms range.
536 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.IntervalTime2", interval_ms, 100,
537 100000 * 1000, 50);
538 }
539 // TODO(georgesak): Remove this #if when RecordMemoryStats is implemented for
540 // all platforms.
541 #if defined(OS_WIN) || defined(OS_CHROMEOS)
542 // Record system memory usage at the time of the discard.
543 metrics::RecordMemoryStats(metrics::RECORD_MEMORY_STATS_TAB_DISCARDED);
544 #endif
545 // Set up to record the next interval.
546 last_discard_time_ = NowTicks();
547 }
548
549 void TabManager::RecordRecentTabDiscard() {
550 // If Chrome is shutting down, do not do anything.
551 if (g_browser_process->IsShuttingDown())
552 return;
553
554 DCHECK_CURRENTLY_ON(BrowserThread::UI);
555 // If the interval is changed, so should the histogram name.
556 UMA_HISTOGRAM_BOOLEAN("Tabs.Discard.DiscardInLastMinute",
557 recent_tab_discard_);
558 // Reset for the next interval.
559 recent_tab_discard_ = false;
560 }
561
562 void TabManager::PurgeBrowserMemory() {
563 // Based on experimental evidence, attempts to free memory from renderers
564 // have been too slow to use in OOM situations (V8 garbage collection) or
565 // do not lead to persistent decreased usage (image/bitmap caches). This
566 // function therefore only targets large blocks of memory in the browser.
567 // Note that other objects will listen to MemoryPressureListener events
568 // to release memory.
569 for (TabContentsIterator it; !it.done(); it.Next()) {
570 WebContents* web_contents = *it;
571 // Screenshots can consume ~5 MB per web contents for platforms that do
572 // touch back/forward.
573 web_contents->GetController().ClearAllScreenshots();
574 }
575 }
576
577 int TabManager::GetTabCount() const {
578 int tab_count = 0;
579 for (auto* browser : *BrowserList::GetInstance())
580 tab_count += browser->tab_strip_model()->count();
581 return tab_count;
582 }
583
584 void TabManager::AddTabStats(TabStatsList* stats_list) const {
585 BrowserList* browser_list = BrowserList::GetInstance();
586 for (BrowserList::const_reverse_iterator browser_iterator =
587 browser_list->begin_last_active();
588 browser_iterator != browser_list->end_last_active();
589 ++browser_iterator) {
590 Browser* browser = *browser_iterator;
591 // |is_active_window| tells us whether this browser window is active. It is
592 // possible that none of the browser windows is active because it's some
593 // other application window in the foreground.
594 bool is_active_window = browser->window()->IsActive();
595 AddTabStats(browser->tab_strip_model(), browser->is_app(), is_active_window,
596 stats_list);
597 }
598 }
599
600 void TabManager::AddTabStats(const TabStripModel* model,
601 bool is_app,
602 bool active_model,
603 TabStatsList* stats_list) const {
604 for (int i = 0; i < model->count(); i++) {
605 WebContents* contents = model->GetWebContentsAt(i);
606 if (!contents->IsCrashed()) {
607 TabStats stats;
608 stats.is_app = is_app;
609 stats.is_internal_page = IsInternalPage(contents->GetLastCommittedURL());
610 stats.is_media = IsMediaTab(contents);
611 stats.is_pinned = model->IsTabPinned(i);
612 stats.is_selected = active_model && model->IsTabSelected(i);
613 stats.is_discarded = GetWebContentsData(contents)->IsDiscarded();
614 stats.has_form_entry =
615 contents->GetPageImportanceSignals().had_form_interaction;
616 stats.discard_count = GetWebContentsData(contents)->DiscardCount();
617 stats.last_active = contents->GetLastActiveTime();
618 stats.last_hidden = contents->GetLastHiddenTime();
619 stats.render_process_host = contents->GetRenderProcessHost();
620 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
621 stats.child_process_host_id = contents->GetRenderProcessHost()->GetID();
622 #if defined(OS_CHROMEOS)
623 stats.oom_score = delegate_->GetCachedOomScore(stats.renderer_handle);
624 #endif
625 stats.title = contents->GetTitle();
626 stats.tab_contents_id = IdFromWebContents(contents);
627 stats_list->push_back(stats);
628 }
629 }
630 }
631
632 // This function is called when |update_timer_| fires. It will adjust the clock
633 // if needed (if it detects that the machine was asleep) and will fire the stats
634 // updating on ChromeOS via the delegate. This function also tries to purge
635 // cache memory.
636 void TabManager::UpdateTimerCallback() {
637 // If Chrome is shutting down, do not do anything.
638 if (g_browser_process->IsShuttingDown())
639 return;
640
641 if (BrowserList::GetInstance()->empty())
642 return;
643
644 last_adjust_time_ = NowTicks();
645
646 #if defined(OS_CHROMEOS)
647 TabStatsList stats_list = GetTabStats();
648 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj.
649 delegate_->AdjustOomPriorities(stats_list);
650 #endif
651
652 PurgeBackgroundedTabsIfNeeded();
653 }
654
655 base::TimeDelta TabManager::GetTimeToPurge(
656 base::TimeDelta min_time_to_purge) const {
657 return base::TimeDelta::FromSeconds(
658 base::RandInt(min_time_to_purge.InSeconds(),
659 min_time_to_purge.InSeconds() * kMinMaxTimeToPurgeRatio));
660 }
661
662 bool TabManager::ShouldPurgeNow(content::WebContents* content) const {
663 if (GetWebContentsData(content)->is_purged())
664 return false;
665
666 base::TimeDelta time_passed =
667 NowTicks() - GetWebContentsData(content)->LastInactiveTime();
668 return time_passed > GetWebContentsData(content)->time_to_purge();
669 }
670
671 void TabManager::PurgeBackgroundedTabsIfNeeded() {
672 auto tab_stats = GetUnsortedTabStats();
673 for (auto& tab : tab_stats) {
674 if (!tab.render_process_host->IsProcessBackgrounded())
675 continue;
676 if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id))
677 continue;
678
679 WebContents* content = GetWebContentsById(tab.tab_contents_id);
680 if (!content)
681 continue;
682
683 bool purge_now = ShouldPurgeNow(content);
684 if (!purge_now)
685 continue;
686
687 // Since |content|'s tab is kept inactive and background for more than
688 // time-to-purge time, its purged state changes: false => true.
689 GetWebContentsData(content)->set_is_purged(true);
690 // TODO(tasak): rename PurgeAndSuspend with a better name, e.g.
691 // RequestPurgeCache, because we don't suspend any renderers.
692 tab.render_process_host->PurgeAndSuspend();
693 }
694 }
695
696 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) {
697 // Can't discard active index.
698 if (model->active_index() == index)
699 return nullptr;
700
701 WebContents* old_contents = model->GetWebContentsAt(index);
702
703 // Can't discard tabs that are already discarded.
704 if (GetWebContentsData(old_contents)->IsDiscarded())
705 return nullptr;
706
707 // Record statistics before discarding to capture the memory state that leads
708 // to the discard.
709 RecordDiscardStatistics();
710
711 UMA_HISTOGRAM_BOOLEAN(
712 "TabManager.Discarding.DiscardedTabHasBeforeUnloadHandler",
713 old_contents->NeedToFireBeforeUnload());
714
715 WebContents* null_contents =
716 WebContents::Create(WebContents::CreateParams(model->profile()));
717 // Copy over the state from the navigation controller to preserve the
718 // back/forward history and to continue to display the correct title/favicon.
719 null_contents->GetController().CopyStateFrom(old_contents->GetController());
720
721 // Make sure to persist the last active time property.
722 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime());
723 // Copy over the discard count.
724 WebContentsData::CopyState(old_contents, null_contents);
725
726 // Replace the discarded tab with the null version.
727 model->ReplaceWebContentsAt(index, null_contents);
728 // Mark the tab so it will reload when clicked on.
729 GetWebContentsData(null_contents)->SetDiscardState(true);
730 GetWebContentsData(null_contents)->IncrementDiscardCount();
731
732 // Make the tab PURGED to avoid purging null_contents.
733 GetWebContentsData(null_contents)->set_is_purged(true);
734
735 // Discard the old tab's renderer.
736 // TODO(jamescook): This breaks script connections with other tabs.
737 // Find a different approach that doesn't do that, perhaps based on navigation
738 // to swappedout://.
739 delete old_contents;
740 recent_tab_discard_ = true;
741
742 return null_contents;
743 }
744
745 void TabManager::OnMemoryPressure(
746 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
747 // If Chrome is shutting down, do not do anything.
748 if (g_browser_process->IsShuttingDown())
749 return;
750
751 // Under critical pressure try to discard a tab.
752 if (memory_pressure_level ==
753 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
754 LogMemoryAndDiscardTab();
755 }
756 // TODO(skuhne): If more memory pressure levels are introduced, consider
757 // calling PurgeBrowserMemory() before CRITICAL is reached.
758 }
759
760 void TabManager::TabChangedAt(content::WebContents* contents,
761 int index,
762 TabChangeType change_type) {
763 if (change_type != TabChangeType::ALL)
764 return;
765 auto* data = GetWebContentsData(contents);
766 bool old_state = data->IsRecentlyAudible();
767 bool current_state = contents->WasRecentlyAudible();
768 if (old_state != current_state) {
769 data->SetRecentlyAudible(current_state);
770 data->SetLastAudioChangeTime(NowTicks());
771 }
772 }
773
774 void TabManager::ActiveTabChanged(content::WebContents* old_contents,
775 content::WebContents* new_contents,
776 int index,
777 int reason) {
778 GetWebContentsData(new_contents)->SetDiscardState(false);
779 // When ActiveTabChanged, |new_contents| purged state changes to be false.
780 GetWebContentsData(new_contents)->set_is_purged(false);
781 // If |old_contents| is set, that tab has switched from being active to
782 // inactive, so record the time of that transition.
783 if (old_contents) {
784 GetWebContentsData(old_contents)->SetLastInactiveTime(NowTicks());
785 // Re-setting time-to-purge every time a tab becomes inactive.
786 GetWebContentsData(old_contents)
787 ->set_time_to_purge(GetTimeToPurge(min_time_to_purge_));
788 }
789 }
790
791 void TabManager::TabInsertedAt(TabStripModel* tab_strip_model,
792 content::WebContents* contents,
793 int index,
794 bool foreground) {
795 // Only interested in background tabs, as foreground tabs get taken care of by
796 // ActiveTabChanged.
797 if (foreground)
798 return;
799
800 // A new background tab is similar to having a tab switch from being active to
801 // inactive.
802 GetWebContentsData(contents)->SetLastInactiveTime(NowTicks());
803 // Re-setting time-to-purge every time a tab becomes inactive.
804 GetWebContentsData(contents)->set_time_to_purge(
805 GetTimeToPurge(min_time_to_purge_));
806 }
807
808 bool TabManager::IsMediaTab(WebContents* contents) const {
809 if (contents->WasRecentlyAudible())
810 return true;
811
812 scoped_refptr<MediaStreamCaptureIndicator> media_indicator =
813 MediaCaptureDevicesDispatcher::GetInstance()
814 ->GetMediaStreamCaptureIndicator();
815 if (media_indicator->IsCapturingUserMedia(contents) ||
816 media_indicator->IsBeingMirrored(contents)) {
817 return true;
818 }
819
820 auto delta = NowTicks() - GetWebContentsData(contents)->LastAudioChangeTime();
821 return delta < TimeDelta::FromSeconds(kAudioProtectionTimeSeconds);
822 }
823
824 TabManager::WebContentsData* TabManager::GetWebContentsData(
825 content::WebContents* contents) const {
826 WebContentsData::CreateForWebContents(contents);
827 auto* web_contents_data = WebContentsData::FromWebContents(contents);
828 web_contents_data->set_test_tick_clock(test_tick_clock_);
829 return web_contents_data;
830 }
831
832 TimeTicks TabManager::NowTicks() const {
833 if (!test_tick_clock_)
834 return TimeTicks::Now();
835
836 return test_tick_clock_->NowTicks();
837 }
838
839 // TODO(jamescook): This should consider tabs with references to other tabs,
840 // such as tabs created with JavaScript window.open(). Potentially consider
841 // discarding the entire set together, or use that in the priority computation.
842 content::WebContents* TabManager::DiscardTabImpl() {
843 DCHECK_CURRENTLY_ON(BrowserThread::UI);
844 TabStatsList stats = GetTabStats();
845
846 if (stats.empty())
847 return nullptr;
848 // Loop until a non-discarded tab to kill is found.
849 for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin();
850 stats_rit != stats.rend(); ++stats_rit) {
851 int64_t least_important_tab_id = stats_rit->tab_contents_id;
852 if (CanDiscardTab(least_important_tab_id)) {
853 WebContents* new_contents = DiscardTabById(least_important_tab_id);
854 if (new_contents)
855 return new_contents;
856 }
857 }
858 return nullptr;
859 }
860
861 // Check the variation parameter to see if a tab can be discarded only once or
862 // multiple times.
863 // Default is to only discard once per tab.
864 bool TabManager::CanOnlyDiscardOnce() const {
865 #if defined(OS_WIN) || defined(OS_MACOSX)
866 // On Windows and MacOS, default to discarding only once unless otherwise
867 // specified by the variation parameter.
868 // TODO(georgesak): Add Linux when automatic discarding is enabled for that
869 // platform.
870 std::string allow_multiple_discards = variations::GetVariationParamValue(
871 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards");
872 return (allow_multiple_discards != "true");
873 #else
874 return false;
875 #endif
876 }
877
878 } // namespace memory
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698