| Index: chrome/browser/memory/tab_manager.cc
|
| diff --git a/chrome/browser/memory/tab_manager.cc b/chrome/browser/memory/tab_manager.cc
|
| index 296947636c5982a7aeaf5ebff72212798dda0123..a99e6abdc048bd79f91e73bd4fe68c6b523fc5f1 100644
|
| --- a/chrome/browser/memory/tab_manager.cc
|
| +++ b/chrome/browser/memory/tab_manager.cc
|
| @@ -271,6 +271,62 @@ 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.
|
| @@ -316,47 +372,115 @@ void TabManager::set_test_tick_clock(base::TickClock* test_tick_clock) {
|
| test_tick_clock_ = test_tick_clock;
|
| }
|
|
|
| -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());
|
| +// 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() {
|
| + 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::ActiveTabChanged(content::WebContents* old_contents,
|
| - content::WebContents* new_contents,
|
| - int index,
|
| - int reason) {
|
| - GetWebContentsData(new_contents)->SetDiscardState(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());
|
| +void TabManager::AddObserver(TabManagerObserver* observer) {
|
| + observers_.AddObserver(observer);
|
| }
|
|
|
| -void TabManager::TabInsertedAt(content::WebContents* contents,
|
| - int index,
|
| - bool foreground) {
|
| - // Only interested in background tabs, as foreground tabs get taken care of by
|
| - // ActiveTabChanged.
|
| - if (foreground)
|
| - return;
|
| +void TabManager::RemoveObserver(TabManagerObserver* observer) {
|
| + observers_.RemoveObserver(observer);
|
| +}
|
|
|
| - // A new background tab is similar to having a tab switch from being active to
|
| - // inactive.
|
| - GetWebContentsData(contents)->SetLastInactiveTime(NowTicks());
|
| +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);
|
| +}
|
| +
|
| +// 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;
|
| }
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
| // TabManager, private:
|
|
|
| +void TabManager::OnDiscardedStateChange(content::WebContents* contents,
|
| + bool is_discarded) {
|
| + FOR_EACH_OBSERVER(TabManagerObserver, observers_,
|
| + OnDiscardedStateChange(contents, is_discarded));
|
| +}
|
| +
|
| +void TabManager::OnAutoDiscardableStateChange(content::WebContents* contents,
|
| + bool is_auto_discardable) {
|
| + FOR_EACH_OBSERVER(
|
| + TabManagerObserver, observers_,
|
| + OnAutoDiscardableStateChange(contents, is_auto_discardable));
|
| +}
|
| +
|
| // static
|
| void TabManager::PurgeMemoryAndDiscardTab() {
|
| if (g_browser_process && g_browser_process->GetTabManager()) {
|
| @@ -488,8 +612,7 @@ void TabManager::AddTabStats(const TabStripModel* model,
|
| if (!contents->IsCrashed()) {
|
| TabStats stats;
|
| stats.is_app = is_app;
|
| - stats.is_internal_page =
|
| - IsInternalPage(contents->GetLastCommittedURL());
|
| + 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);
|
| @@ -575,62 +698,6 @@ void TabManager::PurgeAndSuspendBackgroundedTabs() {
|
| }
|
| }
|
|
|
| -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;
|
| -}
|
| -
|
| WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) {
|
| // Can't discard active index.
|
| if (model->active_index() == index)
|
| @@ -709,6 +776,44 @@ void TabManager::OnMemoryPressure(
|
| #endif
|
| }
|
|
|
| +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);
|
| + // 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());
|
| +}
|
| +
|
| +void TabManager::TabInsertedAt(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());
|
| +}
|
| +
|
| bool TabManager::IsMediaTab(WebContents* contents) const {
|
| if (contents->WasRecentlyAudible())
|
| return true;
|
| @@ -733,51 +838,6 @@ TabManager::WebContentsData* TabManager::GetWebContentsData(
|
| return web_contents_data;
|
| }
|
|
|
| -// 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;
|
| -}
|
| -
|
| TimeTicks TabManager::NowTicks() const {
|
| if (!test_tick_clock_)
|
| return TimeTicks::Now();
|
| @@ -884,65 +944,4 @@ bool TabManager::CanOnlyDiscardOnce() {
|
| #endif
|
| }
|
|
|
| -// 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() {
|
| - 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::OnDiscardedStateChange(content::WebContents* contents,
|
| - bool is_discarded) {
|
| - FOR_EACH_OBSERVER(TabManagerObserver, observers_,
|
| - OnDiscardedStateChange(contents, is_discarded));
|
| -}
|
| -
|
| -void TabManager::set_minimum_protection_time_for_tests(
|
| - base::TimeDelta minimum_protection_time) {
|
| - minimum_protection_time_ = minimum_protection_time;
|
| -}
|
| -
|
| -void TabManager::OnAutoDiscardableStateChange(content::WebContents* contents,
|
| - bool is_auto_discardable) {
|
| - FOR_EACH_OBSERVER(
|
| - TabManagerObserver, observers_,
|
| - OnAutoDiscardableStateChange(contents, is_auto_discardable));
|
| -}
|
| -
|
| -bool TabManager::IsTabAutoDiscardable(content::WebContents* contents) const {
|
| - return GetWebContentsData(contents)->IsAutoDiscardable();
|
| -}
|
| -
|
| -void TabManager::SetTabAutoDiscardableState(content::WebContents* contents,
|
| - bool state) {
|
| - GetWebContentsData(contents)->SetAutoDiscardableState(state);
|
| -}
|
| -
|
| } // namespace memory
|
|
|