| Index: chrome/browser/memory/tab_manager.cc
|
| diff --git a/chrome/browser/memory/tab_manager.cc b/chrome/browser/memory/tab_manager.cc
|
| deleted file mode 100644
|
| index 0d161da8de6e11d4e46b076eff610b9943aa9103..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/memory/tab_manager.cc
|
| +++ /dev/null
|
| @@ -1,878 +0,0 @@
|
| -// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include "chrome/browser/memory/tab_manager.h"
|
| -
|
| -#include <stddef.h>
|
| -
|
| -#include <algorithm>
|
| -#include <set>
|
| -#include <vector>
|
| -
|
| -#include "base/bind.h"
|
| -#include "base/bind_helpers.h"
|
| -#include "base/command_line.h"
|
| -#include "base/feature_list.h"
|
| -#include "base/macros.h"
|
| -#include "base/memory/memory_pressure_monitor.h"
|
| -#include "base/metrics/field_trial.h"
|
| -#include "base/metrics/histogram_macros.h"
|
| -#include "base/observer_list.h"
|
| -#include "base/process/process.h"
|
| -#include "base/rand_util.h"
|
| -#include "base/strings/string16.h"
|
| -#include "base/strings/string_number_conversions.h"
|
| -#include "base/strings/string_util.h"
|
| -#include "base/strings/utf_string_conversions.h"
|
| -#include "base/threading/thread.h"
|
| -#include "base/time/tick_clock.h"
|
| -#include "build/build_config.h"
|
| -#include "chrome/browser/browser_process.h"
|
| -#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
|
| -#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
|
| -#include "chrome/browser/memory/oom_memory_details.h"
|
| -#include "chrome/browser/memory/tab_manager_observer.h"
|
| -#include "chrome/browser/memory/tab_manager_web_contents_data.h"
|
| -#include "chrome/browser/profiles/profile.h"
|
| -#include "chrome/browser/ui/browser.h"
|
| -#include "chrome/browser/ui/browser_list.h"
|
| -#include "chrome/browser/ui/browser_window.h"
|
| -#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
|
| -#include "chrome/browser/ui/tabs/tab_strip_model.h"
|
| -#include "chrome/browser/ui/tabs/tab_utils.h"
|
| -#include "chrome/common/chrome_constants.h"
|
| -#include "chrome/common/chrome_features.h"
|
| -#include "chrome/common/chrome_switches.h"
|
| -#include "chrome/common/url_constants.h"
|
| -#include "components/metrics/system_memory_stats_recorder.h"
|
| -#include "components/variations/variations_associated_data.h"
|
| -#include "content/public/browser/browser_thread.h"
|
| -#include "content/public/browser/navigation_controller.h"
|
| -#include "content/public/browser/render_process_host.h"
|
| -#include "content/public/browser/web_contents.h"
|
| -#include "content/public/common/page_importance_signals.h"
|
| -
|
| -#if defined(OS_CHROMEOS)
|
| -#include "ash/multi_profile_uma.h"
|
| -#include "ash/shell_port.h"
|
| -#include "chrome/browser/memory/tab_manager_delegate_chromeos.h"
|
| -#include "components/user_manager/user_manager.h"
|
| -#endif
|
| -
|
| -using base::TimeDelta;
|
| -using base::TimeTicks;
|
| -using content::BrowserThread;
|
| -using content::WebContents;
|
| -
|
| -namespace memory {
|
| -namespace {
|
| -
|
| -// The default interval in seconds after which to adjust the oom_score_adj
|
| -// value.
|
| -const int kAdjustmentIntervalSeconds = 10;
|
| -
|
| -#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
|
| -// For each period of this length record a statistic to indicate whether or not
|
| -// the user experienced a low memory event. If this interval is changed,
|
| -// Tabs.Discard.DiscardInLastMinute must be replaced with a new statistic.
|
| -const int kRecentTabDiscardIntervalSeconds = 60;
|
| -#endif
|
| -
|
| -// The time during which a tab is protected from discarding after it stops being
|
| -// audible.
|
| -const int kAudioProtectionTimeSeconds = 60;
|
| -
|
| -int FindWebContentsById(const TabStripModel* model,
|
| - int64_t target_web_contents_id) {
|
| - for (int idx = 0; idx < model->count(); idx++) {
|
| - WebContents* web_contents = model->GetWebContentsAt(idx);
|
| - int64_t web_contents_id = TabManager::IdFromWebContents(web_contents);
|
| - if (web_contents_id == target_web_contents_id)
|
| - return idx;
|
| - }
|
| -
|
| - return -1;
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -// TabManager
|
| -
|
| -constexpr base::TimeDelta TabManager::kDefaultMinTimeToPurge;
|
| -
|
| -TabManager::TabManager()
|
| - : discard_count_(0),
|
| - recent_tab_discard_(false),
|
| - discard_once_(false),
|
| -#if !defined(OS_CHROMEOS)
|
| - minimum_protection_time_(base::TimeDelta::FromMinutes(10)),
|
| -#endif
|
| - browser_tab_strip_tracker_(this, nullptr, nullptr),
|
| - test_tick_clock_(nullptr),
|
| - weak_ptr_factory_(this) {
|
| -#if defined(OS_CHROMEOS)
|
| - delegate_.reset(new TabManagerDelegate(weak_ptr_factory_.GetWeakPtr()));
|
| -#endif
|
| - browser_tab_strip_tracker_.Init();
|
| -}
|
| -
|
| -TabManager::~TabManager() {
|
| - Stop();
|
| -}
|
| -
|
| -void TabManager::Start() {
|
| -#if defined(OS_WIN) || defined(OS_MACOSX)
|
| - // Note that discarding is now enabled by default. This check is kept as a
|
| - // kill switch.
|
| - // TODO(georgesak): remote this when deemed not needed anymore.
|
| - if (!base::FeatureList::IsEnabled(features::kAutomaticTabDiscarding))
|
| - return;
|
| -
|
| - // Check the variation parameter to see if a tab is to be protected for an
|
| - // amount of time after being backgrounded. The value is in seconds. Default
|
| - // is 10 minutes if the variation is absent.
|
| - std::string minimum_protection_time_string =
|
| - variations::GetVariationParamValue(features::kAutomaticTabDiscarding.name,
|
| - "MinimumProtectionTime");
|
| - if (!minimum_protection_time_string.empty()) {
|
| - unsigned int minimum_protection_time_seconds = 0;
|
| - if (base::StringToUint(minimum_protection_time_string,
|
| - &minimum_protection_time_seconds)) {
|
| - if (minimum_protection_time_seconds > 0)
|
| - minimum_protection_time_ =
|
| - base::TimeDelta::FromSeconds(minimum_protection_time_seconds);
|
| - }
|
| - }
|
| -#endif
|
| -
|
| - // Check if only one discard is allowed.
|
| - discard_once_ = CanOnlyDiscardOnce();
|
| -
|
| - if (!update_timer_.IsRunning()) {
|
| - update_timer_.Start(FROM_HERE,
|
| - TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
|
| - this, &TabManager::UpdateTimerCallback);
|
| - }
|
| -
|
| - // MemoryPressureMonitor is not implemented on Linux so far and tabs are never
|
| - // discarded.
|
| -#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
|
| - if (!recent_tab_discard_timer_.IsRunning()) {
|
| - recent_tab_discard_timer_.Start(
|
| - FROM_HERE, TimeDelta::FromSeconds(kRecentTabDiscardIntervalSeconds),
|
| - this, &TabManager::RecordRecentTabDiscard);
|
| - }
|
| - start_time_ = NowTicks();
|
| - // Create a |MemoryPressureListener| to listen for memory events.
|
| - base::MemoryPressureMonitor* monitor = base::MemoryPressureMonitor::Get();
|
| - if (monitor) {
|
| - memory_pressure_listener_.reset(new base::MemoryPressureListener(
|
| - base::Bind(&TabManager::OnMemoryPressure, base::Unretained(this))));
|
| - base::MemoryPressureListener::MemoryPressureLevel level =
|
| - monitor->GetCurrentPressureLevel();
|
| - if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
|
| - OnMemoryPressure(level);
|
| - }
|
| - }
|
| -#endif
|
| - // purge-and-suspend param is used for Purge+Suspend finch experiment
|
| - // in the following way:
|
| - // https://docs.google.com/document/d/1hPHkKtXXBTlsZx9s-9U17XC-ofEIzPo9FYbBEc7PPbk/edit?usp=sharing
|
| - std::string purge_and_suspend_time = variations::GetVariationParamValue(
|
| - "PurgeAndSuspend", "purge-and-suspend-time");
|
| - unsigned int min_time_to_purge_sec = 0;
|
| - if (purge_and_suspend_time.empty() ||
|
| - !base::StringToUint(purge_and_suspend_time, &min_time_to_purge_sec))
|
| - min_time_to_purge_ = kDefaultMinTimeToPurge;
|
| - else
|
| - min_time_to_purge_ = base::TimeDelta::FromSeconds(min_time_to_purge_sec);
|
| -}
|
| -
|
| -void TabManager::Stop() {
|
| - update_timer_.Stop();
|
| - recent_tab_discard_timer_.Stop();
|
| - memory_pressure_listener_.reset();
|
| -}
|
| -
|
| -int TabManager::FindTabStripModelById(int64_t target_web_contents_id,
|
| - TabStripModel** model) const {
|
| - DCHECK(model);
|
| - // TODO(tasak): Move this code to a TabStripModel enumeration delegate!
|
| - if (!test_tab_strip_models_.empty()) {
|
| - for (size_t i = 0; i < test_tab_strip_models_.size(); ++i) {
|
| - TabStripModel* local_model =
|
| - const_cast<TabStripModel*>(test_tab_strip_models_[i].first);
|
| - int idx = FindWebContentsById(local_model, target_web_contents_id);
|
| - if (idx != -1) {
|
| - *model = local_model;
|
| - return idx;
|
| - }
|
| - }
|
| -
|
| - return -1;
|
| - }
|
| -
|
| - for (auto* browser : *BrowserList::GetInstance()) {
|
| - TabStripModel* local_model = browser->tab_strip_model();
|
| - int idx = FindWebContentsById(local_model, target_web_contents_id);
|
| - if (idx != -1) {
|
| - *model = local_model;
|
| - return idx;
|
| - }
|
| - }
|
| -
|
| - return -1;
|
| -}
|
| -
|
| -TabStatsList TabManager::GetTabStats() const {
|
| - TabStatsList stats_list(GetUnsortedTabStats());
|
| -
|
| - // Sort the collected data so that least desirable to be killed is first, most
|
| - // desirable is last.
|
| - std::sort(stats_list.begin(), stats_list.end(), CompareTabStats);
|
| -
|
| - return stats_list;
|
| -}
|
| -
|
| -bool TabManager::IsTabDiscarded(content::WebContents* contents) const {
|
| - return GetWebContentsData(contents)->IsDiscarded();
|
| -}
|
| -
|
| -bool TabManager::CanDiscardTab(int64_t target_web_contents_id) const {
|
| - TabStripModel* model;
|
| - int idx = FindTabStripModelById(target_web_contents_id, &model);
|
| -
|
| - if (idx == -1)
|
| - return false;
|
| -
|
| - WebContents* web_contents = model->GetWebContentsAt(idx);
|
| -
|
| - // Do not discard tabs that don't have a valid URL (most probably they have
|
| - // just been opened and dicarding them would lose the URL).
|
| - // TODO(georgesak): Look into a workaround to be able to kill the tab without
|
| - // losing the pending navigation.
|
| - if (!web_contents->GetLastCommittedURL().is_valid() ||
|
| - web_contents->GetLastCommittedURL().is_empty()) {
|
| - return false;
|
| - }
|
| -
|
| - // Do not discard tabs in which the user has entered text in a form, lest that
|
| - // state gets lost.
|
| - if (web_contents->GetPageImportanceSignals().had_form_interaction)
|
| - return false;
|
| -
|
| - // Do not discard tabs that are playing either playing audio or accessing the
|
| - // microphone or camera as it's too distruptive to the user experience. Note
|
| - // that tabs that have recently stopped playing audio by at least
|
| - // |kAudioProtectionTimeSeconds| seconds are protected as well.
|
| - if (IsMediaTab(web_contents))
|
| - return false;
|
| -
|
| - // Do not discard PDFs as they might contain entry that is not saved and they
|
| - // don't remember their scrolling positions. See crbug.com/547286 and
|
| - // crbug.com/65244.
|
| - // TODO(georgesak): Remove this workaround when the bugs are fixed.
|
| - if (web_contents->GetContentsMimeType() == "application/pdf")
|
| - return false;
|
| -
|
| - // Do not discard a previously discarded tab if that's the desired behavior.
|
| - if (discard_once_ && GetWebContentsData(web_contents)->DiscardCount() > 0)
|
| - return false;
|
| -
|
| - // Do not discard a recently used tab.
|
| - if (minimum_protection_time_.InSeconds() > 0) {
|
| - auto delta =
|
| - NowTicks() - GetWebContentsData(web_contents)->LastInactiveTime();
|
| - if (delta < minimum_protection_time_)
|
| - return false;
|
| - }
|
| -
|
| - // Do not discard a tab that was explicitly disallowed to.
|
| - if (!IsTabAutoDiscardable(web_contents))
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| -void TabManager::DiscardTab() {
|
| -#if defined(OS_CHROMEOS)
|
| - // Call Chrome OS specific low memory handling process.
|
| - if (base::FeatureList::IsEnabled(features::kArcMemoryManagement)) {
|
| - delegate_->LowMemoryKill(GetUnsortedTabStats());
|
| - return;
|
| - }
|
| -#endif
|
| - DiscardTabImpl();
|
| -}
|
| -
|
| -WebContents* TabManager::DiscardTabById(int64_t target_web_contents_id) {
|
| - TabStripModel* model;
|
| - int index = FindTabStripModelById(target_web_contents_id, &model);
|
| -
|
| - if (index == -1)
|
| - return nullptr;
|
| -
|
| - VLOG(1) << "Discarding tab " << index << " id " << target_web_contents_id;
|
| -
|
| - return DiscardWebContentsAt(index, model);
|
| -}
|
| -
|
| -WebContents* TabManager::DiscardTabByExtension(content::WebContents* contents) {
|
| - if (contents)
|
| - return DiscardTabById(IdFromWebContents(contents));
|
| -
|
| - return DiscardTabImpl();
|
| -}
|
| -
|
| -void TabManager::LogMemoryAndDiscardTab() {
|
| - LogMemory("Tab Discards Memory details",
|
| - base::Bind(&TabManager::PurgeMemoryAndDiscardTab));
|
| -}
|
| -
|
| -void TabManager::LogMemory(const std::string& title,
|
| - const base::Closure& callback) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - OomMemoryDetails::Log(title, callback);
|
| -}
|
| -
|
| -void TabManager::set_test_tick_clock(base::TickClock* test_tick_clock) {
|
| - test_tick_clock_ = test_tick_clock;
|
| -}
|
| -
|
| -// Things to collect on the browser thread (because TabStripModel isn't thread
|
| -// safe):
|
| -// 1) whether or not a tab is pinned
|
| -// 2) last time a tab was selected
|
| -// 3) is the tab currently selected
|
| -TabStatsList TabManager::GetUnsortedTabStats() const {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - TabStatsList stats_list;
|
| - stats_list.reserve(32); // 99% of users have < 30 tabs open.
|
| -
|
| - // TODO(chrisha): Move this code to a TabStripModel enumeration delegate!
|
| - if (!test_tab_strip_models_.empty()) {
|
| - for (size_t i = 0; i < test_tab_strip_models_.size(); ++i) {
|
| - AddTabStats(test_tab_strip_models_[i].first, // tab_strip_model
|
| - test_tab_strip_models_[i].second, // is_app
|
| - i == 0, // is_active
|
| - &stats_list);
|
| - }
|
| - } else {
|
| - // The code here can only be tested under a full browser test.
|
| - AddTabStats(&stats_list);
|
| - }
|
| -
|
| - return stats_list;
|
| -}
|
| -
|
| -void TabManager::AddObserver(TabManagerObserver* observer) {
|
| - observers_.AddObserver(observer);
|
| -}
|
| -
|
| -void TabManager::RemoveObserver(TabManagerObserver* observer) {
|
| - observers_.RemoveObserver(observer);
|
| -}
|
| -
|
| -void TabManager::set_minimum_protection_time_for_tests(
|
| - base::TimeDelta minimum_protection_time) {
|
| - minimum_protection_time_ = minimum_protection_time;
|
| -}
|
| -
|
| -bool TabManager::IsTabAutoDiscardable(content::WebContents* contents) const {
|
| - return GetWebContentsData(contents)->IsAutoDiscardable();
|
| -}
|
| -
|
| -void TabManager::SetTabAutoDiscardableState(content::WebContents* contents,
|
| - bool state) {
|
| - GetWebContentsData(contents)->SetAutoDiscardableState(state);
|
| -}
|
| -
|
| -content::WebContents* TabManager::GetWebContentsById(
|
| - int64_t tab_contents_id) const {
|
| - TabStripModel* model = nullptr;
|
| - int index = FindTabStripModelById(tab_contents_id, &model);
|
| - if (index == -1)
|
| - return nullptr;
|
| - return model->GetWebContentsAt(index);
|
| -}
|
| -
|
| -bool TabManager::CanSuspendBackgroundedRenderer(int render_process_id) const {
|
| - // A renderer can be purged if it's not playing media.
|
| - auto tab_stats = GetUnsortedTabStats();
|
| - for (auto& tab : tab_stats) {
|
| - if (tab.child_process_host_id != render_process_id)
|
| - continue;
|
| - WebContents* web_contents = GetWebContentsById(tab.tab_contents_id);
|
| - if (!web_contents)
|
| - return false;
|
| - if (IsMediaTab(web_contents))
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -// static
|
| -bool TabManager::CompareTabStats(const TabStats& first,
|
| - const TabStats& second) {
|
| - // Being currently selected is most important to protect.
|
| - if (first.is_selected != second.is_selected)
|
| - return first.is_selected;
|
| -
|
| - // Non auto-discardable tabs are more important to protect.
|
| - if (first.is_auto_discardable != second.is_auto_discardable)
|
| - return !first.is_auto_discardable;
|
| -
|
| - // Protect tabs with pending form entries.
|
| - if (first.has_form_entry != second.has_form_entry)
|
| - return first.has_form_entry;
|
| -
|
| - // Protect streaming audio and video conferencing tabs as these are similar to
|
| - // active tabs.
|
| - if (first.is_media != second.is_media)
|
| - return first.is_media;
|
| -
|
| - // Tab with internal web UI like NTP or Settings are good choices to discard,
|
| - // so protect non-Web UI and let the other conditionals finish the sort.
|
| - if (first.is_internal_page != second.is_internal_page)
|
| - return !first.is_internal_page;
|
| -
|
| - // Being pinned is important to protect.
|
| - if (first.is_pinned != second.is_pinned)
|
| - return first.is_pinned;
|
| -
|
| - // Being an app is important too, as it's the only visible surface in the
|
| - // window and should not be discarded.
|
| - if (first.is_app != second.is_app)
|
| - return first.is_app;
|
| -
|
| - // TODO(jamescook): Incorporate sudden_termination_allowed into the sort
|
| - // order. This is currently not done because pages with unload handlers set
|
| - // sudden_termination_allowed false, and that covers too many common pages
|
| - // with ad networks and statistics scripts. Ideally check for beforeUnload
|
| - // handlers, which are likely to present a dialog asking if the user wants to
|
| - // discard state. crbug.com/123049.
|
| -
|
| - // Being more recently active is more important.
|
| - return first.last_active > second.last_active;
|
| -}
|
| -
|
| -// static
|
| -int64_t TabManager::IdFromWebContents(WebContents* web_contents) {
|
| - return reinterpret_cast<int64_t>(web_contents);
|
| -}
|
| -
|
| -///////////////////////////////////////////////////////////////////////////////
|
| -// TabManager, private:
|
| -
|
| -void TabManager::OnDiscardedStateChange(content::WebContents* contents,
|
| - bool is_discarded) {
|
| - for (TabManagerObserver& observer : observers_)
|
| - observer.OnDiscardedStateChange(contents, is_discarded);
|
| -}
|
| -
|
| -void TabManager::OnAutoDiscardableStateChange(content::WebContents* contents,
|
| - bool is_auto_discardable) {
|
| - for (TabManagerObserver& observer : observers_)
|
| - observer.OnAutoDiscardableStateChange(contents, is_auto_discardable);
|
| -}
|
| -
|
| -// static
|
| -void TabManager::PurgeMemoryAndDiscardTab() {
|
| - TabManager* manager = g_browser_process->GetTabManager();
|
| - manager->PurgeBrowserMemory();
|
| - manager->DiscardTab();
|
| -}
|
| -
|
| -// static
|
| -bool TabManager::IsInternalPage(const GURL& url) {
|
| - // There are many chrome:// UI URLs, but only look for the ones that users
|
| - // are likely to have open. Most of the benefit is the from NTP URL.
|
| - const char* const kInternalPagePrefixes[] = {
|
| - chrome::kChromeUIDownloadsURL, chrome::kChromeUIHistoryURL,
|
| - chrome::kChromeUINewTabURL, chrome::kChromeUISettingsURL,
|
| - };
|
| - // Prefix-match against the table above. Use strncmp to avoid allocating
|
| - // memory to convert the URL prefix constants into std::strings.
|
| - for (size_t i = 0; i < arraysize(kInternalPagePrefixes); ++i) {
|
| - if (!strncmp(url.spec().c_str(), kInternalPagePrefixes[i],
|
| - strlen(kInternalPagePrefixes[i])))
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -void TabManager::RecordDiscardStatistics() {
|
| - discard_count_++;
|
| -
|
| - // TODO(jamescook): Maybe incorporate extension count?
|
| - UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.TabCount", GetTabCount(), 1, 100,
|
| - 50);
|
| -#if defined(OS_CHROMEOS)
|
| - // Record the discarded tab in relation to the amount of simultaneously
|
| - // logged in users.
|
| - if (ash::ShellPort::HasInstance()) {
|
| - ash::MultiProfileUMA::RecordDiscardedTab(
|
| - user_manager::UserManager::Get()->GetLoggedInUsers().size());
|
| - }
|
| -#endif
|
| - // TODO(jamescook): If the time stats prove too noisy, then divide up users
|
| - // based on how heavily they use Chrome using tab count as a proxy.
|
| - // Bin into <= 1, <= 2, <= 4, <= 8, etc.
|
| - if (last_discard_time_.is_null()) {
|
| - // This is the first discard this session.
|
| - TimeDelta interval = NowTicks() - start_time_;
|
| - int interval_seconds = static_cast<int>(interval.InSeconds());
|
| - // Record time in seconds over an interval of approximately 1 day.
|
| - UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.InitialTime2", interval_seconds,
|
| - 1, 100000, 50);
|
| - } else {
|
| - // Not the first discard, so compute time since last discard.
|
| - TimeDelta interval = NowTicks() - last_discard_time_;
|
| - int interval_ms = static_cast<int>(interval.InMilliseconds());
|
| - // Record time in milliseconds over an interval of approximately 1 day.
|
| - // Start at 100 ms to get extra resolution in the target 750 ms range.
|
| - UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.IntervalTime2", interval_ms, 100,
|
| - 100000 * 1000, 50);
|
| - }
|
| -// TODO(georgesak): Remove this #if when RecordMemoryStats is implemented for
|
| -// all platforms.
|
| -#if defined(OS_WIN) || defined(OS_CHROMEOS)
|
| - // Record system memory usage at the time of the discard.
|
| - metrics::RecordMemoryStats(metrics::RECORD_MEMORY_STATS_TAB_DISCARDED);
|
| -#endif
|
| - // Set up to record the next interval.
|
| - last_discard_time_ = NowTicks();
|
| -}
|
| -
|
| -void TabManager::RecordRecentTabDiscard() {
|
| - // If Chrome is shutting down, do not do anything.
|
| - if (g_browser_process->IsShuttingDown())
|
| - return;
|
| -
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - // If the interval is changed, so should the histogram name.
|
| - UMA_HISTOGRAM_BOOLEAN("Tabs.Discard.DiscardInLastMinute",
|
| - recent_tab_discard_);
|
| - // Reset for the next interval.
|
| - recent_tab_discard_ = false;
|
| -}
|
| -
|
| -void TabManager::PurgeBrowserMemory() {
|
| - // Based on experimental evidence, attempts to free memory from renderers
|
| - // have been too slow to use in OOM situations (V8 garbage collection) or
|
| - // do not lead to persistent decreased usage (image/bitmap caches). This
|
| - // function therefore only targets large blocks of memory in the browser.
|
| - // Note that other objects will listen to MemoryPressureListener events
|
| - // to release memory.
|
| - for (TabContentsIterator it; !it.done(); it.Next()) {
|
| - WebContents* web_contents = *it;
|
| - // Screenshots can consume ~5 MB per web contents for platforms that do
|
| - // touch back/forward.
|
| - web_contents->GetController().ClearAllScreenshots();
|
| - }
|
| -}
|
| -
|
| -int TabManager::GetTabCount() const {
|
| - int tab_count = 0;
|
| - for (auto* browser : *BrowserList::GetInstance())
|
| - tab_count += browser->tab_strip_model()->count();
|
| - return tab_count;
|
| -}
|
| -
|
| -void TabManager::AddTabStats(TabStatsList* stats_list) const {
|
| - BrowserList* browser_list = BrowserList::GetInstance();
|
| - for (BrowserList::const_reverse_iterator browser_iterator =
|
| - browser_list->begin_last_active();
|
| - browser_iterator != browser_list->end_last_active();
|
| - ++browser_iterator) {
|
| - Browser* browser = *browser_iterator;
|
| - // |is_active_window| tells us whether this browser window is active. It is
|
| - // possible that none of the browser windows is active because it's some
|
| - // other application window in the foreground.
|
| - bool is_active_window = browser->window()->IsActive();
|
| - AddTabStats(browser->tab_strip_model(), browser->is_app(), is_active_window,
|
| - stats_list);
|
| - }
|
| -}
|
| -
|
| -void TabManager::AddTabStats(const TabStripModel* model,
|
| - bool is_app,
|
| - bool active_model,
|
| - TabStatsList* stats_list) const {
|
| - for (int i = 0; i < model->count(); i++) {
|
| - WebContents* contents = model->GetWebContentsAt(i);
|
| - if (!contents->IsCrashed()) {
|
| - TabStats stats;
|
| - stats.is_app = is_app;
|
| - stats.is_internal_page = IsInternalPage(contents->GetLastCommittedURL());
|
| - stats.is_media = IsMediaTab(contents);
|
| - stats.is_pinned = model->IsTabPinned(i);
|
| - stats.is_selected = active_model && model->IsTabSelected(i);
|
| - stats.is_discarded = GetWebContentsData(contents)->IsDiscarded();
|
| - stats.has_form_entry =
|
| - contents->GetPageImportanceSignals().had_form_interaction;
|
| - stats.discard_count = GetWebContentsData(contents)->DiscardCount();
|
| - stats.last_active = contents->GetLastActiveTime();
|
| - stats.last_hidden = contents->GetLastHiddenTime();
|
| - stats.render_process_host = contents->GetRenderProcessHost();
|
| - stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
|
| - stats.child_process_host_id = contents->GetRenderProcessHost()->GetID();
|
| -#if defined(OS_CHROMEOS)
|
| - stats.oom_score = delegate_->GetCachedOomScore(stats.renderer_handle);
|
| -#endif
|
| - stats.title = contents->GetTitle();
|
| - stats.tab_contents_id = IdFromWebContents(contents);
|
| - stats_list->push_back(stats);
|
| - }
|
| - }
|
| -}
|
| -
|
| -// This function is called when |update_timer_| fires. It will adjust the clock
|
| -// if needed (if it detects that the machine was asleep) and will fire the stats
|
| -// updating on ChromeOS via the delegate. This function also tries to purge
|
| -// cache memory.
|
| -void TabManager::UpdateTimerCallback() {
|
| - // If Chrome is shutting down, do not do anything.
|
| - if (g_browser_process->IsShuttingDown())
|
| - return;
|
| -
|
| - if (BrowserList::GetInstance()->empty())
|
| - return;
|
| -
|
| - last_adjust_time_ = NowTicks();
|
| -
|
| -#if defined(OS_CHROMEOS)
|
| - TabStatsList stats_list = GetTabStats();
|
| - // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj.
|
| - delegate_->AdjustOomPriorities(stats_list);
|
| -#endif
|
| -
|
| - PurgeBackgroundedTabsIfNeeded();
|
| -}
|
| -
|
| -base::TimeDelta TabManager::GetTimeToPurge(
|
| - base::TimeDelta min_time_to_purge) const {
|
| - return base::TimeDelta::FromSeconds(
|
| - base::RandInt(min_time_to_purge.InSeconds(),
|
| - min_time_to_purge.InSeconds() * kMinMaxTimeToPurgeRatio));
|
| -}
|
| -
|
| -bool TabManager::ShouldPurgeNow(content::WebContents* content) const {
|
| - if (GetWebContentsData(content)->is_purged())
|
| - return false;
|
| -
|
| - base::TimeDelta time_passed =
|
| - NowTicks() - GetWebContentsData(content)->LastInactiveTime();
|
| - return time_passed > GetWebContentsData(content)->time_to_purge();
|
| -}
|
| -
|
| -void TabManager::PurgeBackgroundedTabsIfNeeded() {
|
| - auto tab_stats = GetUnsortedTabStats();
|
| - for (auto& tab : tab_stats) {
|
| - if (!tab.render_process_host->IsProcessBackgrounded())
|
| - continue;
|
| - if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id))
|
| - continue;
|
| -
|
| - WebContents* content = GetWebContentsById(tab.tab_contents_id);
|
| - if (!content)
|
| - continue;
|
| -
|
| - bool purge_now = ShouldPurgeNow(content);
|
| - if (!purge_now)
|
| - continue;
|
| -
|
| - // Since |content|'s tab is kept inactive and background for more than
|
| - // time-to-purge time, its purged state changes: false => true.
|
| - GetWebContentsData(content)->set_is_purged(true);
|
| - // TODO(tasak): rename PurgeAndSuspend with a better name, e.g.
|
| - // RequestPurgeCache, because we don't suspend any renderers.
|
| - tab.render_process_host->PurgeAndSuspend();
|
| - }
|
| -}
|
| -
|
| -WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) {
|
| - // Can't discard active index.
|
| - if (model->active_index() == index)
|
| - return nullptr;
|
| -
|
| - WebContents* old_contents = model->GetWebContentsAt(index);
|
| -
|
| - // Can't discard tabs that are already discarded.
|
| - if (GetWebContentsData(old_contents)->IsDiscarded())
|
| - return nullptr;
|
| -
|
| - // Record statistics before discarding to capture the memory state that leads
|
| - // to the discard.
|
| - RecordDiscardStatistics();
|
| -
|
| - UMA_HISTOGRAM_BOOLEAN(
|
| - "TabManager.Discarding.DiscardedTabHasBeforeUnloadHandler",
|
| - old_contents->NeedToFireBeforeUnload());
|
| -
|
| - WebContents* null_contents =
|
| - WebContents::Create(WebContents::CreateParams(model->profile()));
|
| - // Copy over the state from the navigation controller to preserve the
|
| - // back/forward history and to continue to display the correct title/favicon.
|
| - null_contents->GetController().CopyStateFrom(old_contents->GetController());
|
| -
|
| - // Make sure to persist the last active time property.
|
| - null_contents->SetLastActiveTime(old_contents->GetLastActiveTime());
|
| - // Copy over the discard count.
|
| - WebContentsData::CopyState(old_contents, null_contents);
|
| -
|
| - // Replace the discarded tab with the null version.
|
| - model->ReplaceWebContentsAt(index, null_contents);
|
| - // Mark the tab so it will reload when clicked on.
|
| - GetWebContentsData(null_contents)->SetDiscardState(true);
|
| - GetWebContentsData(null_contents)->IncrementDiscardCount();
|
| -
|
| - // Make the tab PURGED to avoid purging null_contents.
|
| - GetWebContentsData(null_contents)->set_is_purged(true);
|
| -
|
| - // Discard the old tab's renderer.
|
| - // TODO(jamescook): This breaks script connections with other tabs.
|
| - // Find a different approach that doesn't do that, perhaps based on navigation
|
| - // to swappedout://.
|
| - delete old_contents;
|
| - recent_tab_discard_ = true;
|
| -
|
| - return null_contents;
|
| -}
|
| -
|
| -void TabManager::OnMemoryPressure(
|
| - base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
|
| - // If Chrome is shutting down, do not do anything.
|
| - if (g_browser_process->IsShuttingDown())
|
| - return;
|
| -
|
| - // Under critical pressure try to discard a tab.
|
| - if (memory_pressure_level ==
|
| - base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
|
| - LogMemoryAndDiscardTab();
|
| - }
|
| - // TODO(skuhne): If more memory pressure levels are introduced, consider
|
| - // calling PurgeBrowserMemory() before CRITICAL is reached.
|
| -}
|
| -
|
| -void TabManager::TabChangedAt(content::WebContents* contents,
|
| - int index,
|
| - TabChangeType change_type) {
|
| - if (change_type != TabChangeType::ALL)
|
| - return;
|
| - auto* data = GetWebContentsData(contents);
|
| - bool old_state = data->IsRecentlyAudible();
|
| - bool current_state = contents->WasRecentlyAudible();
|
| - if (old_state != current_state) {
|
| - data->SetRecentlyAudible(current_state);
|
| - data->SetLastAudioChangeTime(NowTicks());
|
| - }
|
| -}
|
| -
|
| -void TabManager::ActiveTabChanged(content::WebContents* old_contents,
|
| - content::WebContents* new_contents,
|
| - int index,
|
| - int reason) {
|
| - GetWebContentsData(new_contents)->SetDiscardState(false);
|
| - // When ActiveTabChanged, |new_contents| purged state changes to be false.
|
| - GetWebContentsData(new_contents)->set_is_purged(false);
|
| - // If |old_contents| is set, that tab has switched from being active to
|
| - // inactive, so record the time of that transition.
|
| - if (old_contents) {
|
| - GetWebContentsData(old_contents)->SetLastInactiveTime(NowTicks());
|
| - // Re-setting time-to-purge every time a tab becomes inactive.
|
| - GetWebContentsData(old_contents)
|
| - ->set_time_to_purge(GetTimeToPurge(min_time_to_purge_));
|
| - }
|
| -}
|
| -
|
| -void TabManager::TabInsertedAt(TabStripModel* tab_strip_model,
|
| - content::WebContents* contents,
|
| - int index,
|
| - bool foreground) {
|
| - // Only interested in background tabs, as foreground tabs get taken care of by
|
| - // ActiveTabChanged.
|
| - if (foreground)
|
| - return;
|
| -
|
| - // A new background tab is similar to having a tab switch from being active to
|
| - // inactive.
|
| - GetWebContentsData(contents)->SetLastInactiveTime(NowTicks());
|
| - // Re-setting time-to-purge every time a tab becomes inactive.
|
| - GetWebContentsData(contents)->set_time_to_purge(
|
| - GetTimeToPurge(min_time_to_purge_));
|
| -}
|
| -
|
| -bool TabManager::IsMediaTab(WebContents* contents) const {
|
| - if (contents->WasRecentlyAudible())
|
| - return true;
|
| -
|
| - scoped_refptr<MediaStreamCaptureIndicator> media_indicator =
|
| - MediaCaptureDevicesDispatcher::GetInstance()
|
| - ->GetMediaStreamCaptureIndicator();
|
| - if (media_indicator->IsCapturingUserMedia(contents) ||
|
| - media_indicator->IsBeingMirrored(contents)) {
|
| - return true;
|
| - }
|
| -
|
| - auto delta = NowTicks() - GetWebContentsData(contents)->LastAudioChangeTime();
|
| - return delta < TimeDelta::FromSeconds(kAudioProtectionTimeSeconds);
|
| -}
|
| -
|
| -TabManager::WebContentsData* TabManager::GetWebContentsData(
|
| - content::WebContents* contents) const {
|
| - WebContentsData::CreateForWebContents(contents);
|
| - auto* web_contents_data = WebContentsData::FromWebContents(contents);
|
| - web_contents_data->set_test_tick_clock(test_tick_clock_);
|
| - return web_contents_data;
|
| -}
|
| -
|
| -TimeTicks TabManager::NowTicks() const {
|
| - if (!test_tick_clock_)
|
| - return TimeTicks::Now();
|
| -
|
| - return test_tick_clock_->NowTicks();
|
| -}
|
| -
|
| -// TODO(jamescook): This should consider tabs with references to other tabs,
|
| -// such as tabs created with JavaScript window.open(). Potentially consider
|
| -// discarding the entire set together, or use that in the priority computation.
|
| -content::WebContents* TabManager::DiscardTabImpl() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - TabStatsList stats = GetTabStats();
|
| -
|
| - if (stats.empty())
|
| - return nullptr;
|
| - // Loop until a non-discarded tab to kill is found.
|
| - for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin();
|
| - stats_rit != stats.rend(); ++stats_rit) {
|
| - int64_t least_important_tab_id = stats_rit->tab_contents_id;
|
| - if (CanDiscardTab(least_important_tab_id)) {
|
| - WebContents* new_contents = DiscardTabById(least_important_tab_id);
|
| - if (new_contents)
|
| - return new_contents;
|
| - }
|
| - }
|
| - return nullptr;
|
| -}
|
| -
|
| -// Check the variation parameter to see if a tab can be discarded only once or
|
| -// multiple times.
|
| -// Default is to only discard once per tab.
|
| -bool TabManager::CanOnlyDiscardOnce() const {
|
| -#if defined(OS_WIN) || defined(OS_MACOSX)
|
| - // On Windows and MacOS, default to discarding only once unless otherwise
|
| - // specified by the variation parameter.
|
| - // TODO(georgesak): Add Linux when automatic discarding is enabled for that
|
| - // platform.
|
| - std::string allow_multiple_discards = variations::GetVariationParamValue(
|
| - features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards");
|
| - return (allow_multiple_discards != "true");
|
| -#else
|
| - return false;
|
| -#endif
|
| -}
|
| -
|
| -} // namespace memory
|
|
|