| Index: chrome/browser/memory/tab_manager.cc
|
| diff --git a/chrome/browser/memory/tab_manager.cc b/chrome/browser/memory/tab_manager.cc
|
| index e761e81a7c43a1d31ef1ebd15c33c984e6faa98b..9ca996cca91c15d5f50135808abf1da639e15dbe 100644
|
| --- a/chrome/browser/memory/tab_manager.cc
|
| +++ b/chrome/browser/memory/tab_manager.cc
|
| @@ -84,6 +84,16 @@ const int kRecentTabDiscardIntervalSeconds = 60;
|
| // machine was suspended and correct the timing statistics.
|
| const int kSuspendThresholdSeconds = kAdjustmentIntervalSeconds * 4;
|
|
|
| +// The backgrounded renderer is allowed to be suspended for this duration
|
| +// before resume.
|
| +constexpr base::TimeDelta kMaxTimeRendererAllowedToBeSuspendedBeforeResume =
|
| + base::TimeDelta::FromSeconds(120);
|
| +
|
| +// When the suspended backgrounded renderer is resumed, the renderer runs
|
| +// for this duration.
|
| +constexpr base::TimeDelta kSuspendedRendererLengthOfResumption =
|
| + base::TimeDelta::FromSeconds(10);
|
| +
|
| // The time during which a tab is protected from discarding after it stops being
|
| // audible.
|
| const int kAudioProtectionTimeSeconds = 60;
|
| @@ -418,17 +428,24 @@ void TabManager::SetTabAutoDiscardableState(content::WebContents* contents,
|
| GetWebContentsData(contents)->SetAutoDiscardableState(state);
|
| }
|
|
|
| +content::WebContents* TabManager::GetWebContentsByContentsId(
|
| + int64_t tab_contents_id) {
|
| + TabStripModel* model;
|
| + int index = FindTabStripModelById(tab_contents_id, &model);
|
| + if (index == -1)
|
| + return nullptr;
|
| + return model->GetWebContentsAt(index);
|
| +}
|
| +
|
| bool TabManager::CanSuspendBackgroundedRenderer(int render_process_id) {
|
| // A renderer can be suspended 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;
|
| - TabStripModel* model;
|
| - int index = FindTabStripModelById(tab.tab_contents_id, &model);
|
| - if (index == -1)
|
| + WebContents* web_contents = GetWebContentsByContentsId(tab.tab_contents_id);
|
| + if (!web_contents)
|
| return false;
|
| - WebContents* web_contents = model->GetWebContentsAt(index);
|
| if (IsMediaTab(web_contents))
|
| return false;
|
| }
|
| @@ -687,6 +704,49 @@ void TabManager::UpdateTimerCallback() {
|
| PurgeAndSuspendBackgroundedTabs();
|
| }
|
|
|
| +bool TabManager::ShouldUpdatePurgeAndSuspendState(
|
| + const base::TimeTicks& current_time,
|
| + const TabStats& tab,
|
| + const base::TimeDelta& purge_and_suspend_threshold,
|
| + PurgeAndSuspendState* next_state) {
|
| + DCHECK(next_state);
|
| + content::WebContents* content =
|
| + GetWebContentsByContentsId(tab.tab_contents_id);
|
| + if (!content)
|
| + return false;
|
| +
|
| + base::TimeTicks last_modified_time =
|
| + GetWebContentsData(content)->LastPurgeAndSuspendModifiedTime();
|
| + PurgeAndSuspendState state =
|
| + GetWebContentsData(content)->GetPurgeAndSuspendState();
|
| + // If the tab was foregrounded, should ignore the returned state.
|
| + if (tab.last_active > last_modified_time)
|
| + state = RUNNING;
|
| +
|
| + auto time_passed = current_time - last_modified_time;
|
| + switch (state) {
|
| + case RUNNING:
|
| + if (time_passed > purge_and_suspend_threshold) {
|
| + *next_state = SUSPENDED;
|
| + return true;
|
| + }
|
| + break;
|
| + case RESUMED:
|
| + if (time_passed > kSuspendedRendererLengthOfResumption) {
|
| + *next_state = SUSPENDED;
|
| + return true;
|
| + }
|
| + break;
|
| + case SUSPENDED:
|
| + if (time_passed > kMaxTimeRendererAllowedToBeSuspendedBeforeResume) {
|
| + *next_state = RESUMED;
|
| + return true;
|
| + }
|
| + break;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| void TabManager::PurgeAndSuspendBackgroundedTabs() {
|
| const base::CommandLine& command_line =
|
| *base::CommandLine::ForCurrentProcess();
|
| @@ -700,21 +760,36 @@ void TabManager::PurgeAndSuspendBackgroundedTabs() {
|
| }
|
| if (purge_and_suspend_time <= 0)
|
| return;
|
| - auto purge_and_suspend_time_threshold =
|
| - NowTicks() - base::TimeDelta::FromSeconds(purge_and_suspend_time);
|
| + base::TimeTicks current_time = NowTicks();
|
| + base::TimeDelta purge_and_suspend_threshold =
|
| + base::TimeDelta::FromSeconds(purge_and_suspend_time);
|
| auto tab_stats = GetUnsortedTabStats();
|
| for (auto& tab : tab_stats) {
|
| if (!tab.render_process_host->IsProcessBackgrounded())
|
| continue;
|
| + if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id))
|
| + continue;
|
| +
|
| // TODO(hajimehoshi): Now calling PurgeAndSuspend is implemented without
|
| // timers for simplicity, so PurgeAndSuspend is called even after the
|
| // renderer is purged and suspended once. This should be replaced with
|
| // timers if we want necessary and sufficient signals.
|
| - if (tab.last_active > purge_and_suspend_time_threshold)
|
| + PurgeAndSuspendState state = RUNNING;
|
| + if (!ShouldUpdatePurgeAndSuspendState(current_time, tab,
|
| + purge_and_suspend_threshold, &state))
|
| continue;
|
| - if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id))
|
| - continue;
|
| - tab.render_process_host->PurgeAndSuspend();
|
| + WebContents* content = GetWebContentsByContentsId(tab.tab_contents_id);
|
| + GetWebContentsData(content)->SetPurgeAndSuspendState(state);
|
| + switch (state) {
|
| + case SUSPENDED:
|
| + tab.render_process_host->PurgeAndSuspend();
|
| + break;
|
| + case RESUMED:
|
| + tab.render_process_host->Resume();
|
| + break;
|
| + case RUNNING:
|
| + NOTREACHED();
|
| + }
|
| }
|
| }
|
|
|
|
|