| Index: chrome/browser/memory/tab_manager.cc
|
| diff --git a/chrome/browser/memory/tab_manager.cc b/chrome/browser/memory/tab_manager.cc
|
| index ebcbe6400fda1cba478954593af32cfbf1ce6742..5e46e790613c3fd98fb6de6aea6c224c11a4276c 100644
|
| --- a/chrome/browser/memory/tab_manager.cc
|
| +++ b/chrome/browser/memory/tab_manager.cc
|
| @@ -26,6 +26,7 @@
|
| #include "base/strings/string_number_conversions.h"
|
| #include "base/strings/string_util.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| +#include "base/thread_task_runner_handle.h"
|
| #include "base/threading/thread.h"
|
| #include "base/time/tick_clock.h"
|
| #include "build/build_config.h"
|
| @@ -48,6 +49,7 @@
|
| #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/memory_pressure_controller.h"
|
| #include "content/public/browser/navigation_controller.h"
|
| #include "content/public/browser/render_process_host.h"
|
| #include "content/public/browser/web_contents.h"
|
| @@ -107,6 +109,26 @@ int FindTabStripModelById(int64_t target_web_contents_id,
|
| return -1;
|
| }
|
|
|
| +// A wrapper around base::MemoryPressureMonitor::GetCurrentPressureLevel.
|
| +// TODO(chrisha): Move this do the default implementation of a delegate.
|
| +base::MemoryPressureListener::MemoryPressureLevel
|
| +GetCurrentPressureLevel() {
|
| + auto monitor = base::MemoryPressureMonitor::Get();
|
| + if (monitor)
|
| + return monitor->GetCurrentPressureLevel();
|
| + return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
|
| +}
|
| +
|
| +// A wrapper to content::SendPressureNotification that doesn't have overloaded
|
| +// type ambiguity. Makes use of Bind easier.
|
| +// TODO(chrisha): Move this do the default implementation of a delegate.
|
| +void NotifyRendererProcess(
|
| + const content::RenderProcessHost* render_process_host,
|
| + base::MemoryPressureListener::MemoryPressureLevel level) {
|
| + content::MemoryPressureController::SendPressureNotification(
|
| + render_process_host, level);
|
| +}
|
| +
|
| } // namespace
|
|
|
| ////////////////////////////////////////////////////////////////////////////////
|
| @@ -117,12 +139,19 @@ TabManager::TabManager()
|
| recent_tab_discard_(false),
|
| discard_once_(false),
|
| browser_tab_strip_tracker_(this, nullptr, nullptr),
|
| - test_tick_clock_(nullptr) {
|
| + test_tick_clock_(nullptr),
|
| + under_memory_pressure_(false),
|
| + weak_ptr_factory_(this) {
|
| #if defined(OS_CHROMEOS)
|
| delegate_.reset(new TabManagerDelegate);
|
| #endif
|
| browser_tab_strip_tracker_.Init(
|
| BrowserTabStripTracker::InitWith::ALL_BROWERS);
|
| +
|
| + // Set up default callbacks. These may be overridden post-construction as
|
| + // testing seams.
|
| + get_current_pressure_level_ = base::Bind(&GetCurrentPressureLevel);
|
| + notify_renderer_process_ = base::Bind(&NotifyRendererProcess);
|
| }
|
|
|
| TabManager::~TabManager() {
|
| @@ -203,10 +232,20 @@ void TabManager::Stop() {
|
| TabStatsList TabManager::GetTabStats() {
|
| DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| TabStatsList stats_list;
|
| - stats_list.reserve(32); // 99% of users have < 30 tabs open
|
| -
|
| - // Go through each window to get all the tabs.
|
| - AddTabStats(&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);
|
| + }
|
|
|
| // Sort the collected data so that least desirable to be killed is first, most
|
| // desirable is last.
|
| @@ -214,6 +253,30 @@ TabStatsList TabManager::GetTabStats() {
|
| return stats_list;
|
| }
|
|
|
| +std::vector<content::RenderProcessHost*> TabManager::GetOrderedRenderers() {
|
| + // Get the tab stats.
|
| + auto tab_stats = GetTabStats();
|
| +
|
| + std::vector<content::RenderProcessHost*> sorted_renderers;
|
| + std::set<content::RenderProcessHost*> seen_renderers;
|
| + sorted_renderers.reserve(tab_stats.size());
|
| +
|
| + // Convert the tab sort order to a process sort order. The process inherits
|
| + // the priority of its highest priority tab.
|
| + for (auto& tab : tab_stats) {
|
| + // Skip renderers that have already been encountered. This can occur when
|
| + // multiple tabs are folded into a single renderer process. In this case the
|
| + // process takes the priority of its highest priority contained tab.
|
| + auto renderer = tab.render_process_host;
|
| + if (!seen_renderers.insert(renderer).second)
|
| + continue;
|
| +
|
| + sorted_renderers.push_back(renderer);
|
| + }
|
| +
|
| + return sorted_renderers;
|
| +}
|
| +
|
| bool TabManager::IsTabDiscarded(content::WebContents* contents) const {
|
| return GetWebContentsData(contents)->IsDiscarded();
|
| }
|
| @@ -407,35 +470,42 @@ void TabManager::AddTabStats(TabStatsList* stats_list) {
|
| browser_iterator != browser_list->end_last_active();
|
| ++browser_iterator) {
|
| Browser* browser = *browser_iterator;
|
| - bool is_browser_for_app = browser->is_app();
|
| - const TabStripModel* model = browser->tab_strip_model();
|
| - for (int i = 0; i < model->count(); i++) {
|
| - WebContents* contents = model->GetWebContentsAt(i);
|
| - if (!contents->IsCrashed()) {
|
| - TabStats stats;
|
| - stats.is_app = is_browser_for_app;
|
| - stats.is_internal_page =
|
| - IsInternalPage(contents->GetLastCommittedURL());
|
| - stats.is_media = IsMediaTab(contents);
|
| - stats.is_pinned = model->IsTabPinned(i);
|
| - stats.is_selected = browser_active && 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.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
|
| - stats.child_process_host_id = contents->GetRenderProcessHost()->GetID();
|
| + AddTabStats(browser->tab_strip_model(), browser->is_app(), browser_active,
|
| + stats_list);
|
| + // The active browser window is processed in the first iteration.
|
| + browser_active = false;
|
| + }
|
| +}
|
| +
|
| +void TabManager::AddTabStats(const TabStripModel* model,
|
| + bool is_app,
|
| + bool active_model,
|
| + TabStatsList* stats_list) {
|
| +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.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_->GetOomScore(stats.child_process_host_id);
|
| + stats.oom_score = delegate_->GetOomScore(stats.child_process_host_id);
|
| #endif
|
| - stats.title = contents->GetTitle();
|
| - stats.tab_contents_id = IdFromWebContents(contents);
|
| - stats_list->push_back(stats);
|
| - }
|
| + stats.title = contents->GetTitle();
|
| + stats.tab_contents_id = IdFromWebContents(contents);
|
| + stats_list->push_back(stats);
|
| }
|
| - // The active browser window is processed in the first iteration.
|
| - browser_active = false;
|
| }
|
| }
|
|
|
| @@ -574,13 +644,26 @@ void TabManager::OnMemoryPressure(
|
| if (g_browser_process->IsShuttingDown())
|
| return;
|
|
|
| - // For the moment only do something when critical state is reached.
|
| + // If no task runner has been set, then use the same one that the memory
|
| + // pressure subsystem uses.
|
| + if (!task_runner_.get())
|
| + task_runner_ = base::ThreadTaskRunnerHandle::Get();
|
| +
|
| + // 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.
|
| +
|
| + // If this is the beginning of a period of memory pressure then kick off
|
| + // notification of child processes.
|
| + // NOTE: This mechanism relies on having a MemoryPressureMonitor
|
| + // implementation that supports "CurrentPressureLevel". This is true on all
|
| + // platforms on which TabManager is used.
|
| + if (!under_memory_pressure_)
|
| + DoChildProcessDispatch();
|
| }
|
|
|
| bool TabManager::IsMediaTab(WebContents* contents) const {
|
| @@ -654,4 +737,64 @@ TimeTicks TabManager::NowTicks() const {
|
| return test_tick_clock_->NowTicks();
|
| }
|
|
|
| +void TabManager::DoChildProcessDispatch() {
|
| + // If Chrome is shutting down, do not do anything.
|
| + if (g_browser_process->IsShuttingDown())
|
| + return;
|
| +
|
| + if (!under_memory_pressure_)
|
| + under_memory_pressure_ = true;
|
| +
|
| + // If the memory pressure condition has ended then stop dispatching messages.
|
| + auto level = get_current_pressure_level_.Run();
|
| + if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
|
| + under_memory_pressure_ = false;
|
| + notified_renderers_.clear();
|
| + return;
|
| + }
|
| +
|
| + // Get a vector of active renderers, from highest to lowest priority.
|
| + auto renderers = GetOrderedRenderers();
|
| +
|
| + // The following code requires at least one renderer to be present or it will
|
| + // busyloop. It's possible (however unlikely) for no renderers to exist, so
|
| + // bail early if that's the case.
|
| + if (renderers.empty())
|
| + return;
|
| +
|
| + // Notify a single renderer of memory pressure.
|
| + bool notified = false;
|
| + while (!notified) {
|
| + // Notify the lowest priority renderer that hasn't been notified yet.
|
| + for (auto rit = renderers.rbegin(); rit != renderers.rend(); ++rit) {
|
| + // If this renderer has already been notified then look at the next one.
|
| + if (!notified_renderers_.insert(*rit).second)
|
| + continue;
|
| +
|
| + // Notify the renderer.
|
| + notify_renderer_process_.Run(*rit, level);
|
| + notified = true;
|
| + break;
|
| + }
|
| +
|
| + // If all renderers were processed and none were notified, then all
|
| + // renderers have already been notified. Clear the list and start again.
|
| + if (!notified)
|
| + notified_renderers_.clear();
|
| +
|
| + // This loop can only run at most twice. If it doesn't exit the first time
|
| + // through, by the second time through |notified_renderers_| will be empty.
|
| + // Since |renderers| is always non-empty, the first renderer encountered
|
| + // during the second pass will be notified.
|
| + }
|
| +
|
| + // Schedule another notification. Use a weak pointer so this doesn't explode
|
| + // during tear down.
|
| + task_runner_->PostDelayedTask(
|
| + FROM_HERE,
|
| + base::Bind(&TabManager::DoChildProcessDispatch,
|
| + weak_ptr_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromSeconds(kRendererNotificationDelayInSeconds));
|
| +}
|
| +
|
| } // namespace memory
|
|
|