1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_manager.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/system/sys_info.h"
#include "base/task/thread_pool.h"
#include "base/time/default_tick_clock.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_config.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.h"
#include "ui/display/display_observer.h"
#include "ui/display/screen.h"
namespace content {
NavigationEntryScreenshotManager::NavigationEntryScreenshotManager()
: // `NO_AUTO_EVICT` since we want to manually limit the global cache size
// by the number of bytes of the thumbnails, rather than the number of
// entries in the cache.
managed_caches_(base::LRUCacheSet<int>::NO_AUTO_EVICT),
tick_clock_(base::DefaultTickClock::GetInstance()),
cleanup_delay_(
NavigationTransitionConfig::GetCleanupDelayForInvisibleCaches()) {
CHECK(NavigationTransitionConfig::AreBackForwardTransitionsEnabled());
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
max_cache_size_in_bytes_ =
NavigationTransitionConfig::ComputeCacheSizeInBytes();
listener_ = std::make_unique<base::MemoryPressureListener>(
FROM_HERE,
base::BindRepeating(&NavigationEntryScreenshotManager::OnMemoryPressure,
base::Unretained(this)));
if (auto* screen = display::Screen::GetScreen()) {
screen->AddObserver(this);
}
// Start recording memory usage.
RecordScreenshotCacheSizeAfterDelay();
}
NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() {
if (auto* screen = display::Screen::GetScreen()) {
screen->RemoveObserver(this);
}
}
void NavigationEntryScreenshotManager::OnDisplayAdded(const display::Display&) {
RecalculateCacheSize();
}
void NavigationEntryScreenshotManager::OnDisplaysRemoved(
const display::Displays&) {
RecalculateCacheSize();
}
void NavigationEntryScreenshotManager::OnDisplayMetricsChanged(
const display::Display&,
uint32_t metrics_changed) {
if (metrics_changed &
(display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR)) {
RecalculateCacheSize();
}
}
void NavigationEntryScreenshotManager::OnScreenshotCached(
NavigationEntryScreenshotCacheEvictor* cache,
size_t size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (managed_caches_.Get(cache) == managed_caches_.end()) {
Register(cache);
}
// We shouldn't be able to capture anything greater than the budget.
CHECK_LE(size, max_cache_size_in_bytes_);
current_cache_size_in_bytes_ += size;
EvictIfOutOfMemoryBudget();
}
void NavigationEntryScreenshotManager::OnScreenshotRemoved(
NavigationEntryScreenshotCacheEvictor* cache,
size_t size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!IsEmpty());
auto it = managed_caches_.Get(cache);
CHECK(it != managed_caches_.end());
CHECK_GE(current_cache_size_in_bytes_, size);
current_cache_size_in_bytes_ -= size;
if (cache->IsEmpty()) {
Unregister(cache);
}
}
void NavigationEntryScreenshotManager::OnScreenshotCompressed(
NavigationEntryScreenshotCacheEvictor* cache,
size_t old_size,
size_t new_size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!IsEmpty());
CHECK(managed_caches_.Peek(cache) != managed_caches_.end());
CHECK_GE(current_cache_size_in_bytes_, old_size);
current_cache_size_in_bytes_ -= old_size;
current_cache_size_in_bytes_ += new_size;
}
void NavigationEntryScreenshotManager::OnVisibilityChanged(
NavigationEntryScreenshotCacheEvictor* cache) {
if (cache->IsEmpty()) {
CHECK(managed_caches_.Peek(cache) == managed_caches_.end());
return;
}
auto last_visible_time = cache->GetLastVisibleTime();
if (last_visible_time && !cleanup_task_.IsRunning()) {
ScheduleCleanup(*last_visible_time);
}
}
bool NavigationEntryScreenshotManager::IsEmpty() const {
CHECK(managed_caches_.empty() == (current_cache_size_in_bytes_ == 0U));
return managed_caches_.empty();
}
void NavigationEntryScreenshotManager::RecalculateCacheSize() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Recalculate the max cache size based on the size of the new primary screen.
// Keep the maximum size calculated from all screens that were ever added.
max_cache_size_in_bytes_ =
std::max(max_cache_size_in_bytes_,
NavigationTransitionConfig::ComputeCacheSizeInBytes());
}
void NavigationEntryScreenshotManager::Register(
NavigationEntryScreenshotCacheEvictor* cache) {
CHECK(managed_caches_.Peek(cache) == managed_caches_.end());
managed_caches_.Put(std::move(cache));
OnVisibilityChanged(cache);
}
void NavigationEntryScreenshotManager::Unregister(
NavigationEntryScreenshotCacheEvictor* cache) {
auto it = managed_caches_.Peek(cache);
CHECK(it != managed_caches_.end());
managed_caches_.Erase(it);
}
base::TimeTicks NavigationEntryScreenshotManager::Now() const {
return tick_clock_->NowTicks();
}
void NavigationEntryScreenshotManager::ScheduleCleanup(
base::TimeTicks last_visible_time) {
const auto delay = cleanup_delay_ - (Now() - last_visible_time);
auto callback = base::BindOnce(&NavigationEntryScreenshotManager::RunCleanup,
weak_factory_.GetWeakPtr());
cleanup_task_.Start(FROM_HERE, delay, std::move(callback));
}
void NavigationEntryScreenshotManager::RunCleanup() {
std::optional<base::TimeTicks> earliest_last_visible_time;
const base::TimeTicks now = Now();
auto it = managed_caches_.begin();
while (it != managed_caches_.end()) {
auto* cache = *it;
it++;
const auto last_visible_time = cache->GetLastVisibleTime();
if (!last_visible_time) {
continue;
}
CHECK_LE(*last_visible_time, now);
if (now - *last_visible_time >= cleanup_delay_) {
cache->Purge(
NavigationEntryScreenshotCacheEvictor::PurgeReason::kInvisible);
CHECK(cache->IsEmpty());
CHECK(managed_caches_.Peek(cache) == managed_caches_.end());
} else if (!earliest_last_visible_time) {
earliest_last_visible_time = *last_visible_time;
} else {
earliest_last_visible_time =
std::min(*earliest_last_visible_time, *last_visible_time);
}
}
if (earliest_last_visible_time) {
ScheduleCleanup(*earliest_last_visible_time);
}
}
// The current implementation iterates through the tabs in the LRU order and
// evict screenshots from each tab, until either the global size satisfies the
// budget, or the cache of the current tab is empty.
//
// One alternative is to always evict the navigation entries in LRU order,
// regardless of which tab the entry is from. The pro of this alternative is to
// have all the eviction logic inside the global manager.
void NavigationEntryScreenshotManager::EvictIfOutOfMemoryBudget() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!IsEmpty());
// Start with the least recently used.
// TODO(khushalsagar): Evict from invisible caches first?
auto it = managed_caches_.rbegin();
while (current_cache_size_in_bytes_ > max_cache_size_in_bytes_) {
CHECK(it != managed_caches_.rend());
auto* cache = *it;
cache->EvictScreenshotsUntilUnderBudgetOrEmpty();
CHECK(cache->IsEmpty() ||
current_cache_size_in_bytes_ <= max_cache_size_in_bytes_);
if (cache->IsEmpty()) {
// No need to unregister this cache -
// `EvictScreenshotsUntilUnderBudgetOrEmpty` takes care of that.
CHECK(managed_caches_.Peek(cache) == managed_caches_.end());
it = managed_caches_.rbegin();
}
}
}
void NavigationEntryScreenshotManager::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (memory_pressure_level !=
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
return;
}
// Using a while loop because `Purge` erases the iterator.
auto it = managed_caches_.begin();
while (it != managed_caches_.end()) {
auto* cache = *it;
cache->Purge(
NavigationEntryScreenshotCacheEvictor::PurgeReason::kMemoryPressure);
CHECK(cache->IsEmpty());
it = managed_caches_.begin();
}
CHECK(IsEmpty());
}
void NavigationEntryScreenshotManager::RecordScreenshotCacheSizeAfterDelay() {
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&NavigationEntryScreenshotManager::RecordScreenshotCacheSize,
weak_factory_.GetWeakPtr()),
memory_instrumentation::GetDelayForNextMemoryLog());
}
void NavigationEntryScreenshotManager::RecordScreenshotCacheSize() {
MEMORY_METRICS_HISTOGRAM_MB(
"Navigation.GestureTransition.ScreenshotCacheSize",
current_cache_size_in_bytes_ / (1024 * 1024));
RecordScreenshotCacheSizeAfterDelay();
}
} // namespace content
|