| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/memory/tab_manager.h" | 5 #include "chrome/browser/memory/tab_manager.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 #include <set> | 10 #include <set> |
| 11 #include <vector> | 11 #include <vector> |
| 12 | 12 |
| 13 #include "base/bind.h" | 13 #include "base/bind.h" |
| 14 #include "base/bind_helpers.h" | 14 #include "base/bind_helpers.h" |
| 15 #include "base/command_line.h" | 15 #include "base/command_line.h" |
| 16 #include "base/feature_list.h" | 16 #include "base/feature_list.h" |
| 17 #include "base/macros.h" | 17 #include "base/macros.h" |
| 18 #include "base/memory/memory_pressure_monitor.h" | 18 #include "base/memory/memory_pressure_monitor.h" |
| 19 #include "base/metrics/field_trial.h" | 19 #include "base/metrics/field_trial.h" |
| 20 #include "base/metrics/histogram_macros.h" | 20 #include "base/metrics/histogram_macros.h" |
| 21 #include "base/observer_list.h" | 21 #include "base/observer_list.h" |
| 22 #include "base/process/process.h" | 22 #include "base/process/process.h" |
| 23 #include "base/rand_util.h" | 23 #include "base/rand_util.h" |
| 24 #include "base/strings/string16.h" | 24 #include "base/strings/string16.h" |
| 25 #include "base/strings/string_number_conversions.h" | 25 #include "base/strings/string_number_conversions.h" |
| 26 #include "base/strings/string_util.h" | 26 #include "base/strings/string_util.h" |
| 27 #include "base/strings/utf_string_conversions.h" | 27 #include "base/strings/utf_string_conversions.h" |
| 28 #include "base/threading/thread.h" | 28 #include "base/threading/thread.h" |
| 29 #include "base/threading/thread_task_runner_handle.h" | |
| 30 #include "base/time/tick_clock.h" | 29 #include "base/time/tick_clock.h" |
| 31 #include "build/build_config.h" | 30 #include "build/build_config.h" |
| 32 #include "chrome/browser/browser_process.h" | 31 #include "chrome/browser/browser_process.h" |
| 33 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" | 32 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| 34 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h" | 33 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h" |
| 35 #include "chrome/browser/memory/oom_memory_details.h" | 34 #include "chrome/browser/memory/oom_memory_details.h" |
| 36 #include "chrome/browser/memory/tab_manager_observer.h" | 35 #include "chrome/browser/memory/tab_manager_observer.h" |
| 37 #include "chrome/browser/memory/tab_manager_web_contents_data.h" | 36 #include "chrome/browser/memory/tab_manager_web_contents_data.h" |
| 38 #include "chrome/browser/profiles/profile.h" | 37 #include "chrome/browser/profiles/profile.h" |
| 39 #include "chrome/browser/ui/browser.h" | 38 #include "chrome/browser/ui/browser.h" |
| 40 #include "chrome/browser/ui/browser_list.h" | 39 #include "chrome/browser/ui/browser_list.h" |
| 41 #include "chrome/browser/ui/browser_window.h" | 40 #include "chrome/browser/ui/browser_window.h" |
| 42 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" | 41 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" |
| 43 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 42 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 44 #include "chrome/browser/ui/tabs/tab_utils.h" | 43 #include "chrome/browser/ui/tabs/tab_utils.h" |
| 45 #include "chrome/common/chrome_constants.h" | 44 #include "chrome/common/chrome_constants.h" |
| 46 #include "chrome/common/chrome_features.h" | 45 #include "chrome/common/chrome_features.h" |
| 47 #include "chrome/common/chrome_switches.h" | 46 #include "chrome/common/chrome_switches.h" |
| 48 #include "chrome/common/url_constants.h" | 47 #include "chrome/common/url_constants.h" |
| 49 #include "components/metrics/system_memory_stats_recorder.h" | 48 #include "components/metrics/system_memory_stats_recorder.h" |
| 50 #include "components/variations/variations_associated_data.h" | 49 #include "components/variations/variations_associated_data.h" |
| 51 #include "content/public/browser/browser_thread.h" | 50 #include "content/public/browser/browser_thread.h" |
| 52 #include "content/public/browser/memory_pressure_controller.h" | |
| 53 #include "content/public/browser/navigation_controller.h" | 51 #include "content/public/browser/navigation_controller.h" |
| 54 #include "content/public/browser/render_process_host.h" | 52 #include "content/public/browser/render_process_host.h" |
| 55 #include "content/public/browser/web_contents.h" | 53 #include "content/public/browser/web_contents.h" |
| 56 #include "content/public/common/page_importance_signals.h" | 54 #include "content/public/common/page_importance_signals.h" |
| 57 | 55 |
| 58 #if defined(OS_CHROMEOS) | 56 #if defined(OS_CHROMEOS) |
| 59 #include "ash/multi_profile_uma.h" | 57 #include "ash/multi_profile_uma.h" |
| 60 #include "ash/shell_port.h" | 58 #include "ash/shell_port.h" |
| 61 #include "chrome/browser/memory/tab_manager_delegate_chromeos.h" | 59 #include "chrome/browser/memory/tab_manager_delegate_chromeos.h" |
| 62 #include "components/user_manager/user_manager.h" | 60 #include "components/user_manager/user_manager.h" |
| (...skipping 27 matching lines...) Expand all Loading... |
| 90 for (int idx = 0; idx < model->count(); idx++) { | 88 for (int idx = 0; idx < model->count(); idx++) { |
| 91 WebContents* web_contents = model->GetWebContentsAt(idx); | 89 WebContents* web_contents = model->GetWebContentsAt(idx); |
| 92 int64_t web_contents_id = TabManager::IdFromWebContents(web_contents); | 90 int64_t web_contents_id = TabManager::IdFromWebContents(web_contents); |
| 93 if (web_contents_id == target_web_contents_id) | 91 if (web_contents_id == target_web_contents_id) |
| 94 return idx; | 92 return idx; |
| 95 } | 93 } |
| 96 | 94 |
| 97 return -1; | 95 return -1; |
| 98 } | 96 } |
| 99 | 97 |
| 100 // A wrapper around base::MemoryPressureMonitor::GetCurrentPressureLevel. | |
| 101 // TODO(chrisha): Move this do the default implementation of a delegate. | |
| 102 base::MemoryPressureListener::MemoryPressureLevel | |
| 103 GetCurrentPressureLevel() { | |
| 104 auto* monitor = base::MemoryPressureMonitor::Get(); | |
| 105 if (monitor) | |
| 106 return monitor->GetCurrentPressureLevel(); | |
| 107 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; | |
| 108 } | |
| 109 | |
| 110 // A wrapper to content::SendPressureNotification that doesn't have overloaded | |
| 111 // type ambiguity. Makes use of Bind easier. | |
| 112 // TODO(chrisha): Move this do the default implementation of a delegate. | |
| 113 void NotifyRendererProcess( | |
| 114 const content::RenderProcessHost* render_process_host, | |
| 115 base::MemoryPressureListener::MemoryPressureLevel level) { | |
| 116 content::MemoryPressureController::SendPressureNotification( | |
| 117 render_process_host, level); | |
| 118 } | |
| 119 | |
| 120 } // namespace | 98 } // namespace |
| 121 | 99 |
| 122 //////////////////////////////////////////////////////////////////////////////// | 100 //////////////////////////////////////////////////////////////////////////////// |
| 123 // TabManager | 101 // TabManager |
| 124 | 102 |
| 125 constexpr base::TimeDelta TabManager::kDefaultMinTimeToPurge; | 103 constexpr base::TimeDelta TabManager::kDefaultMinTimeToPurge; |
| 126 | 104 |
| 127 TabManager::TabManager() | 105 TabManager::TabManager() |
| 128 : discard_count_(0), | 106 : discard_count_(0), |
| 129 recent_tab_discard_(false), | 107 recent_tab_discard_(false), |
| 130 discard_once_(false), | 108 discard_once_(false), |
| 131 #if !defined(OS_CHROMEOS) | 109 #if !defined(OS_CHROMEOS) |
| 132 minimum_protection_time_(base::TimeDelta::FromMinutes(10)), | 110 minimum_protection_time_(base::TimeDelta::FromMinutes(10)), |
| 133 #endif | 111 #endif |
| 134 browser_tab_strip_tracker_(this, nullptr, nullptr), | 112 browser_tab_strip_tracker_(this, nullptr, nullptr), |
| 135 test_tick_clock_(nullptr), | 113 test_tick_clock_(nullptr), |
| 136 under_memory_pressure_(false), | |
| 137 weak_ptr_factory_(this) { | 114 weak_ptr_factory_(this) { |
| 138 #if defined(OS_CHROMEOS) | 115 #if defined(OS_CHROMEOS) |
| 139 delegate_.reset(new TabManagerDelegate(weak_ptr_factory_.GetWeakPtr())); | 116 delegate_.reset(new TabManagerDelegate(weak_ptr_factory_.GetWeakPtr())); |
| 140 #endif | 117 #endif |
| 141 browser_tab_strip_tracker_.Init(); | 118 browser_tab_strip_tracker_.Init(); |
| 142 | |
| 143 // Set up default callbacks. These may be overridden post-construction as | |
| 144 // testing seams. | |
| 145 get_current_pressure_level_ = base::Bind(&GetCurrentPressureLevel); | |
| 146 notify_renderer_process_ = base::Bind(&NotifyRendererProcess); | |
| 147 } | 119 } |
| 148 | 120 |
| 149 TabManager::~TabManager() { | 121 TabManager::~TabManager() { |
| 150 Stop(); | 122 Stop(); |
| 151 } | 123 } |
| 152 | 124 |
| 153 void TabManager::Start() { | 125 void TabManager::Start() { |
| 154 #if defined(OS_WIN) || defined(OS_MACOSX) | 126 #if defined(OS_WIN) || defined(OS_MACOSX) |
| 155 // Note that discarding is now enabled by default. This check is kept as a | 127 // Note that discarding is now enabled by default. This check is kept as a |
| 156 // kill switch. | 128 // kill switch. |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 257 TabStatsList TabManager::GetTabStats() const { | 229 TabStatsList TabManager::GetTabStats() const { |
| 258 TabStatsList stats_list(GetUnsortedTabStats()); | 230 TabStatsList stats_list(GetUnsortedTabStats()); |
| 259 | 231 |
| 260 // Sort the collected data so that least desirable to be killed is first, most | 232 // Sort the collected data so that least desirable to be killed is first, most |
| 261 // desirable is last. | 233 // desirable is last. |
| 262 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); | 234 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); |
| 263 | 235 |
| 264 return stats_list; | 236 return stats_list; |
| 265 } | 237 } |
| 266 | 238 |
| 267 std::vector<content::RenderProcessHost*> | |
| 268 TabManager::GetOrderedRenderers() const { | |
| 269 // Get the tab stats. | |
| 270 auto tab_stats = GetTabStats(); | |
| 271 | |
| 272 std::vector<content::RenderProcessHost*> sorted_renderers; | |
| 273 std::set<content::RenderProcessHost*> seen_renderers; | |
| 274 std::set<content::RenderProcessHost*> visible_renderers; | |
| 275 sorted_renderers.reserve(tab_stats.size()); | |
| 276 | |
| 277 // Convert the tab sort order to a process sort order. The process inherits | |
| 278 // the priority of its highest priority tab. | |
| 279 for (auto& tab : tab_stats) { | |
| 280 auto* renderer = tab.render_process_host; | |
| 281 | |
| 282 // Skip renderers associated with visible tabs as handling memory pressure | |
| 283 // notifications in these processes can cause jank. This code works because | |
| 284 // visible tabs always come first in |tab_stats|. | |
| 285 if (tab.is_selected) { | |
| 286 visible_renderers.insert(renderer); | |
| 287 continue; | |
| 288 } | |
| 289 if (visible_renderers.count(renderer) > 0) | |
| 290 continue; | |
| 291 | |
| 292 // Skip renderers that have already been encountered. This can occur when | |
| 293 // multiple tabs are folded into a single renderer process. In this case the | |
| 294 // process takes the priority of its highest priority contained tab. | |
| 295 if (!seen_renderers.insert(renderer).second) | |
| 296 continue; | |
| 297 | |
| 298 sorted_renderers.push_back(renderer); | |
| 299 } | |
| 300 | |
| 301 return sorted_renderers; | |
| 302 } | |
| 303 | |
| 304 bool TabManager::IsTabDiscarded(content::WebContents* contents) const { | 239 bool TabManager::IsTabDiscarded(content::WebContents* contents) const { |
| 305 return GetWebContentsData(contents)->IsDiscarded(); | 240 return GetWebContentsData(contents)->IsDiscarded(); |
| 306 } | 241 } |
| 307 | 242 |
| 308 bool TabManager::CanDiscardTab(int64_t target_web_contents_id) const { | 243 bool TabManager::CanDiscardTab(int64_t target_web_contents_id) const { |
| 309 TabStripModel* model; | 244 TabStripModel* model; |
| 310 int idx = FindTabStripModelById(target_web_contents_id, &model); | 245 int idx = FindTabStripModelById(target_web_contents_id, &model); |
| 311 | 246 |
| 312 if (idx == -1) | 247 if (idx == -1) |
| 313 return false; | 248 return false; |
| (...skipping 492 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 806 | 741 |
| 807 return null_contents; | 742 return null_contents; |
| 808 } | 743 } |
| 809 | 744 |
| 810 void TabManager::OnMemoryPressure( | 745 void TabManager::OnMemoryPressure( |
| 811 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { | 746 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { |
| 812 // If Chrome is shutting down, do not do anything. | 747 // If Chrome is shutting down, do not do anything. |
| 813 if (g_browser_process->IsShuttingDown()) | 748 if (g_browser_process->IsShuttingDown()) |
| 814 return; | 749 return; |
| 815 | 750 |
| 816 // If no task runner has been set, then use the same one that the memory | |
| 817 // pressure subsystem uses. | |
| 818 if (!task_runner_.get()) | |
| 819 task_runner_ = base::ThreadTaskRunnerHandle::Get(); | |
| 820 | |
| 821 // Under critical pressure try to discard a tab. | 751 // Under critical pressure try to discard a tab. |
| 822 if (memory_pressure_level == | 752 if (memory_pressure_level == |
| 823 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { | 753 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { |
| 824 LogMemoryAndDiscardTab(); | 754 LogMemoryAndDiscardTab(); |
| 825 } | 755 } |
| 826 // TODO(skuhne): If more memory pressure levels are introduced, consider | 756 // TODO(skuhne): If more memory pressure levels are introduced, consider |
| 827 // calling PurgeBrowserMemory() before CRITICAL is reached. | 757 // calling PurgeBrowserMemory() before CRITICAL is reached. |
| 828 | |
| 829 // If this is the beginning of a period of memory pressure then kick off | |
| 830 // notification of child processes. | |
| 831 // NOTE: This mechanism relies on having a MemoryPressureMonitor | |
| 832 // implementation that supports "CurrentPressureLevel". This is true on all | |
| 833 // platforms on which TabManager is used. | |
| 834 #if !defined(OS_CHROMEOS) | |
| 835 // Running GC under memory pressure can cause thrashing. Disable it on | |
| 836 // ChromeOS until the thrashing is fixed. crbug.com/588172. | |
| 837 if (!under_memory_pressure_) | |
| 838 DoChildProcessDispatch(); | |
| 839 #endif | |
| 840 } | 758 } |
| 841 | 759 |
| 842 void TabManager::TabChangedAt(content::WebContents* contents, | 760 void TabManager::TabChangedAt(content::WebContents* contents, |
| 843 int index, | 761 int index, |
| 844 TabChangeType change_type) { | 762 TabChangeType change_type) { |
| 845 if (change_type != TabChangeType::ALL) | 763 if (change_type != TabChangeType::ALL) |
| 846 return; | 764 return; |
| 847 auto* data = GetWebContentsData(contents); | 765 auto* data = GetWebContentsData(contents); |
| 848 bool old_state = data->IsRecentlyAudible(); | 766 bool old_state = data->IsRecentlyAudible(); |
| 849 bool current_state = contents->WasRecentlyAudible(); | 767 bool current_state = contents->WasRecentlyAudible(); |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 911 return web_contents_data; | 829 return web_contents_data; |
| 912 } | 830 } |
| 913 | 831 |
| 914 TimeTicks TabManager::NowTicks() const { | 832 TimeTicks TabManager::NowTicks() const { |
| 915 if (!test_tick_clock_) | 833 if (!test_tick_clock_) |
| 916 return TimeTicks::Now(); | 834 return TimeTicks::Now(); |
| 917 | 835 |
| 918 return test_tick_clock_->NowTicks(); | 836 return test_tick_clock_->NowTicks(); |
| 919 } | 837 } |
| 920 | 838 |
| 921 void TabManager::DoChildProcessDispatch() { | |
| 922 // If Chrome is shutting down, do not do anything. | |
| 923 if (g_browser_process->IsShuttingDown()) | |
| 924 return; | |
| 925 | |
| 926 if (!under_memory_pressure_) | |
| 927 under_memory_pressure_ = true; | |
| 928 | |
| 929 // If the memory pressure condition has ended then stop dispatching messages. | |
| 930 auto level = get_current_pressure_level_.Run(); | |
| 931 if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) { | |
| 932 under_memory_pressure_ = false; | |
| 933 notified_renderers_.clear(); | |
| 934 return; | |
| 935 } | |
| 936 | |
| 937 // Get a vector of active renderers, from highest to lowest priority. | |
| 938 auto renderers = GetOrderedRenderers(); | |
| 939 | |
| 940 // The following code requires at least one renderer to be present or it will | |
| 941 // busyloop. It's possible for no renderers to exist (we eliminate visible | |
| 942 // renderers to avoid janking them), so bail early if that's the case. | |
| 943 if (renderers.empty()) | |
| 944 return; | |
| 945 | |
| 946 // Notify a single renderer of memory pressure. | |
| 947 bool notified = false; | |
| 948 while (!notified) { | |
| 949 // Notify the lowest priority renderer that hasn't been notified yet. | |
| 950 for (auto rit = renderers.rbegin(); rit != renderers.rend(); ++rit) { | |
| 951 // If this renderer has already been notified then look at the next one. | |
| 952 if (!notified_renderers_.insert(*rit).second) | |
| 953 continue; | |
| 954 | |
| 955 // Notify the renderer. | |
| 956 notify_renderer_process_.Run(*rit, level); | |
| 957 notified = true; | |
| 958 break; | |
| 959 } | |
| 960 | |
| 961 // If all renderers were processed and none were notified, then all | |
| 962 // renderers have already been notified. Clear the list and start again. | |
| 963 if (!notified) | |
| 964 notified_renderers_.clear(); | |
| 965 | |
| 966 // This loop can only run at most twice. If it doesn't exit the first time | |
| 967 // through, by the second time through |notified_renderers_| will be empty. | |
| 968 // Since |renderers| is always non-empty, the first renderer encountered | |
| 969 // during the second pass will be notified. | |
| 970 } | |
| 971 | |
| 972 // Schedule another notification. Use a weak pointer so this doesn't explode | |
| 973 // during tear down. | |
| 974 task_runner_->PostDelayedTask( | |
| 975 FROM_HERE, | |
| 976 base::BindOnce(&TabManager::DoChildProcessDispatch, | |
| 977 weak_ptr_factory_.GetWeakPtr()), | |
| 978 base::TimeDelta::FromSeconds(kRendererNotificationDelayInSeconds)); | |
| 979 } | |
| 980 | |
| 981 // TODO(jamescook): This should consider tabs with references to other tabs, | 839 // TODO(jamescook): This should consider tabs with references to other tabs, |
| 982 // such as tabs created with JavaScript window.open(). Potentially consider | 840 // such as tabs created with JavaScript window.open(). Potentially consider |
| 983 // discarding the entire set together, or use that in the priority computation. | 841 // discarding the entire set together, or use that in the priority computation. |
| 984 content::WebContents* TabManager::DiscardTabImpl() { | 842 content::WebContents* TabManager::DiscardTabImpl() { |
| 985 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 843 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 986 TabStatsList stats = GetTabStats(); | 844 TabStatsList stats = GetTabStats(); |
| 987 | 845 |
| 988 if (stats.empty()) | 846 if (stats.empty()) |
| 989 return nullptr; | 847 return nullptr; |
| 990 // Loop until a non-discarded tab to kill is found. | 848 // Loop until a non-discarded tab to kill is found. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 1011 // platform. | 869 // platform. |
| 1012 std::string allow_multiple_discards = variations::GetVariationParamValue( | 870 std::string allow_multiple_discards = variations::GetVariationParamValue( |
| 1013 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards"); | 871 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards"); |
| 1014 return (allow_multiple_discards != "true"); | 872 return (allow_multiple_discards != "true"); |
| 1015 #else | 873 #else |
| 1016 return false; | 874 return false; |
| 1017 #endif | 875 #endif |
| 1018 } | 876 } |
| 1019 | 877 |
| 1020 } // namespace memory | 878 } // namespace memory |
| OLD | NEW |