kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 1 | // Copyright 2025 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "content/browser/preloading/prefetch/prefetch_scheduler.h" |
| 6 | |
| 7 | #include "base/auto_reset.h" |
| 8 | #include "base/check_is_test.h" |
| 9 | #include "base/types/pass_key.h" |
| 10 | #include "content/browser/preloading/prefetch/prefetch_container.h" |
| 11 | #include "content/browser/preloading/prefetch/prefetch_document_manager.h" |
| 12 | #include "content/browser/preloading/prefetch/prefetch_features.h" |
Hiroshige Hayashizaki | f55280f6 | 2025-08-19 17:23:20 | [diff] [blame] | 13 | #include "content/browser/preloading/prefetch/prefetch_request.h" |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 14 | #include "content/browser/preloading/prefetch/prefetch_service.h" |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 15 | #include "content/browser/preloading/prerender/prerender_features.h" |
Hiroshige Hayashizaki | 583b496 | 2025-08-19 18:58:26 | [diff] [blame] | 16 | #include "content/public/browser/prefetch_priority.h" |
Taiyo Mizuhashi | d13f8ca | 2025-06-23 13:29:02 | [diff] [blame] | 17 | #include "content/public/common/content_features.h" |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 18 | |
| 19 | namespace content { |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | size_t GetActiveSetSizeLimitForBase() { |
| 24 | // TODO(crbug.com/406403063): Update the limit for base. |
| 25 | |
| 26 | if (base::FeatureList::IsEnabled(features::kPrefetchSchedulerTesting)) { |
| 27 | return features::kPrefetchSchedulerTestingActiveSetSizeLimitForBase.Get(); |
| 28 | } |
| 29 | |
Taiyo Mizuhashi | 98e3dea | 2025-06-23 18:32:58 | [diff] [blame] | 30 | if (base::FeatureList::IsEnabled( |
| 31 | features::kPrefetchMultipleActiveSetSizeLimitForBase)) { |
| 32 | return features::kPrefetchMultipleActiveSetSizeLimitForBaseValue.Get(); |
| 33 | } |
| 34 | |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 35 | return 1; |
| 36 | } |
| 37 | |
| 38 | size_t GetActiveSetSizeLimitForBurst() { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 39 | if (base::FeatureList::IsEnabled(features::kPrefetchSchedulerTesting)) { |
| 40 | return features::kPrefetchSchedulerTestingActiveSetSizeLimitForBurst.Get(); |
| 41 | } |
| 42 | |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 43 | // Before prefetch/prerender integration (i.e. |
| 44 | // `Prerender2FallbackPrefetchSpecRules` is disabled), prerender ran without |
| 45 | // prefetch So, it was not blocked by prefetch queue. Allow |
| 46 | // prefetch-ahead-of-prerender to run independently of the ordinal prefetch |
| 47 | // queue so that prerendering is not blocked by queued prefetch requests. |
| 48 | // |
| 49 | // Note that prerenders are run sequentially. So, +1 is enough. |
| 50 | if (features::kPrerender2FallbackPrefetchSchedulerPolicy.Get() == |
| 51 | features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst) { |
| 52 | return GetActiveSetSizeLimitForBase() + 1; |
| 53 | } |
| 54 | |
Taiyo Mizuhashi | d13f8ca | 2025-06-23 13:29:02 | [diff] [blame] | 55 | if (base::FeatureList::IsEnabled( |
| 56 | features::kWebViewPrefetchHighestPrefetchPriority)) { |
| 57 | return features::kWebViewPrefetchHighestPrefetchPriorityBurstLimit.Get(); |
| 58 | } |
| 59 | |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 60 | // No additional room for burst. |
| 61 | return GetActiveSetSizeLimitForBase(); |
| 62 | } |
| 63 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 64 | PrefetchSchedulerPriority CalculatePriorityImpl( |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 65 | const PrefetchContainer& prefetch_container) { |
Hiroshige Hayashizaki | 8b59d908 | 2025-08-19 18:58:04 | [diff] [blame] | 66 | if (prefetch_container.request().priority().has_value()) { |
| 67 | switch (prefetch_container.request().priority().value()) { |
Taiyo Mizuhashi | 53da92d | 2025-06-23 08:56:15 | [diff] [blame] | 68 | case PrefetchPriority::kLow: |
| 69 | case PrefetchPriority::kMedium: |
| 70 | case PrefetchPriority::kHigh: |
| 71 | return PrefetchSchedulerPriority::kBase; |
| 72 | case PrefetchPriority::kHighest: |
| 73 | return PrefetchSchedulerPriority::kBurstForPrefetchPriority; |
| 74 | } |
| 75 | } |
| 76 | |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 77 | // Burst/prioritize if ahead of prerender. |
Taiyo Mizuhashi | 53da92d | 2025-06-23 08:56:15 | [diff] [blame] | 78 | // TODO(crbug.com/426404355): Migrate to use `PrefetchPriority`. |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 79 | if (prefetch_container.IsLikelyAheadOfPrerender()) { |
| 80 | switch (features::kPrerender2FallbackPrefetchSchedulerPolicy.Get()) { |
| 81 | case features::Prerender2FallbackPrefetchSchedulerPolicy::kNotUse: |
| 82 | break; |
| 83 | case features::Prerender2FallbackPrefetchSchedulerPolicy::kPrioritize: |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 84 | return PrefetchSchedulerPriority::kHighAheadOfPrerender; |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 85 | case features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst: |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 86 | return PrefetchSchedulerPriority::kBurstAheadOfPrerender; |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 87 | } |
| 88 | } |
| 89 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 90 | return PrefetchSchedulerPriority::kBase; |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 91 | } |
| 92 | |
| 93 | bool IsReadyToStartPrefetch(const PrefetchQueue::Item& item) { |
| 94 | // Keep this method as similar as much as possible to a lambda in |
| 95 | // `PrefetchService::PopNextPrefetchContainer()`. |
| 96 | // |
| 97 | // TODO(crbug.com/406754449): Remove this comment. |
| 98 | |
| 99 | // `prefetch_container` must be valid. It will be ensured by `PrefetchService` |
| 100 | // in the future. |
| 101 | // |
| 102 | // Return true and let it handle `PrefetchScheduler::Progress()`. |
| 103 | // |
| 104 | // TODO(crbug.com/400761083): Use `CHECK`. |
| 105 | if (!item.prefetch_container) { |
| 106 | return true; |
| 107 | } |
| 108 | |
Hiroshige Hayashizaki | f55280f6 | 2025-08-19 17:23:20 | [diff] [blame] | 109 | auto* renderer_initiator_info = |
| 110 | item.prefetch_container->request().GetRendererInitiatorInfo(); |
| 111 | if (!renderer_initiator_info) { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 112 | // TODO(crbug.com/40946257): Revisit the resource limits and |
| 113 | // conditions for starting browser-initiated prefetch. |
| 114 | return true; |
| 115 | } |
| 116 | |
| 117 | auto* prefetch_document_manager = |
Hiroshige Hayashizaki | f55280f6 | 2025-08-19 17:23:20 | [diff] [blame] | 118 | renderer_initiator_info->prefetch_document_manager(); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 119 | // If there is no manager in renderer-initiated prefetch (can happen |
| 120 | // only in tests), just bypass the check. |
| 121 | if (!prefetch_document_manager) { |
| 122 | CHECK_IS_TEST(); |
| 123 | return true; |
| 124 | } |
| 125 | |
| 126 | // Eviction wil be handled in `PrefetchScheduler::ProgressOne()`. |
| 127 | return std::get<0>( |
| 128 | prefetch_document_manager->CanPrefetchNow(item.prefetch_container.get())); |
| 129 | } |
| 130 | |
| 131 | } // namespace |
| 132 | |
| 133 | PrefetchQueue::Item::Item(base::WeakPtr<PrefetchContainer> prefetch_container, |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 134 | PrefetchSchedulerPriority priority) |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 135 | : prefetch_container(std::move(prefetch_container)), priority(priority) {} |
| 136 | |
| 137 | PrefetchQueue::Item::Item(const PrefetchQueue::Item&& other) |
| 138 | : prefetch_container(std::move(other.prefetch_container)), |
| 139 | priority(other.priority) {} |
| 140 | |
| 141 | PrefetchQueue::Item& PrefetchQueue::Item::operator=( |
| 142 | const PrefetchQueue::Item&& other) { |
| 143 | prefetch_container = std::move(other.prefetch_container); |
| 144 | priority = other.priority; |
| 145 | |
| 146 | return *this; |
| 147 | } |
| 148 | |
| 149 | PrefetchQueue::Item::~Item() = default; |
| 150 | |
| 151 | PrefetchQueue::PrefetchQueue() = default; |
| 152 | |
| 153 | PrefetchQueue::~PrefetchQueue() = default; |
| 154 | |
| 155 | void PrefetchQueue::Push(base::WeakPtr<PrefetchContainer> prefetch_container, |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 156 | PrefetchSchedulerPriority priority) { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 157 | CHECK(prefetch_container); |
| 158 | // Postcondition: Pushing registered one is not allowed. |
| 159 | CHECK(!Remove(prefetch_container)); |
| 160 | |
| 161 | auto mid = std::partition_point(queue_.begin(), queue_.end(), |
| 162 | [priority](PrefetchQueue::Item& item) { |
| 163 | return item.priority >= priority; |
| 164 | }); |
| 165 | queue_.insert(mid, |
| 166 | PrefetchQueue::Item(std::move(prefetch_container), priority)); |
| 167 | } |
| 168 | |
| 169 | bool PrefetchQueue::Remove( |
| 170 | base::WeakPtr<PrefetchContainer> prefetch_container) { |
| 171 | for (auto it = queue_.cbegin(); it != queue_.cend(); ++it) { |
| 172 | if (it->prefetch_container.get() == prefetch_container.get()) { |
| 173 | queue_.erase(it); |
| 174 | return true; |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | return false; |
| 179 | } |
| 180 | |
| 181 | bool PrefetchQueue::MaybeUpdatePriority(PrefetchContainer& prefetch_container, |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 182 | PrefetchSchedulerPriority priority) { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 183 | for (auto it = queue_.cbegin(); it != queue_.cend(); ++it) { |
| 184 | if (it->prefetch_container.get() == &prefetch_container) { |
| 185 | if (it->priority != priority) { |
| 186 | queue_.erase(it); |
| 187 | Push(prefetch_container.GetWeakPtr(), priority); |
| 188 | return true; |
| 189 | } else { |
| 190 | return false; |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | return false; |
| 196 | } |
| 197 | |
| 198 | PrefetchScheduler::PrefetchScheduler(PrefetchService* prefetch_service) |
| 199 | : prefetch_service_(prefetch_service) {} |
| 200 | |
| 201 | PrefetchScheduler::~PrefetchScheduler() = default; |
| 202 | |
| 203 | bool PrefetchScheduler::IsInActiveSet( |
| 204 | const PrefetchContainer& prefetch_container) { |
| 205 | for (auto& active_prefetch_container : active_set_) { |
| 206 | if (&prefetch_container == active_prefetch_container.get()) { |
| 207 | return true; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | return false; |
| 212 | } |
| 213 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 214 | PrefetchSchedulerPriority PrefetchScheduler::CalculatePriority( |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 215 | const PrefetchContainer& prefetch_container) { |
| 216 | if (calculate_priority_for_test_) { |
| 217 | return calculate_priority_for_test_.Run(prefetch_container); |
| 218 | } |
| 219 | |
| 220 | return CalculatePriorityImpl(prefetch_container); |
| 221 | } |
| 222 | |
kenoss | 0a56736 | 2025-05-16 11:18:55 | [diff] [blame] | 223 | void PrefetchScheduler::PushAndProgress(PrefetchContainer& prefetch_container) { |
| 224 | // Precondition: Pushing already registered one is not allowed. |
| 225 | for (auto& it : active_set_) { |
| 226 | if (it.get() == &prefetch_container) { |
| 227 | NOTREACHED(); |
| 228 | } |
| 229 | } |
| 230 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 231 | PrefetchSchedulerPriority priority = CalculatePriority(prefetch_container); |
kenoss | 0a56736 | 2025-05-16 11:18:55 | [diff] [blame] | 232 | queue_.Push(prefetch_container.GetWeakPtr(), priority); |
| 233 | |
| 234 | Progress(); |
| 235 | } |
| 236 | |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 237 | void PrefetchScheduler::PushAndProgressAsync( |
| 238 | PrefetchContainer& prefetch_container) { |
| 239 | // Precondition: Pushing already registered one is not allowed. |
| 240 | for (auto& it : active_set_) { |
| 241 | if (it.get() == &prefetch_container) { |
| 242 | NOTREACHED(); |
| 243 | } |
| 244 | } |
| 245 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 246 | PrefetchSchedulerPriority priority = CalculatePriority(prefetch_container); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 247 | queue_.Push(prefetch_container.GetWeakPtr(), priority); |
| 248 | |
| 249 | ProgressAsync(); |
| 250 | } |
| 251 | |
| 252 | void PrefetchScheduler::RemoveAndProgressAsync( |
kenoss | e8d8bbb5 | 2025-05-13 12:35:04 | [diff] [blame] | 253 | PrefetchContainer& prefetch_container, |
| 254 | bool should_progress) { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 255 | [&]() { |
| 256 | for (auto it = active_set_.cbegin(); it != active_set_.cend(); ++it) { |
| 257 | if (it->get() == &prefetch_container) { |
| 258 | active_set_.erase(it); |
| 259 | return; |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | queue_.Remove(prefetch_container.GetWeakPtr()); |
| 264 | }(); |
| 265 | |
kenoss | e8d8bbb5 | 2025-05-13 12:35:04 | [diff] [blame] | 266 | if (!should_progress) { |
| 267 | return; |
| 268 | } |
| 269 | |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 270 | // This method can be called in `PrefetechService::EvictPrefetch()` called in |
| 271 | // `ProcessOne()`. Don't call `ProcessAsync()` to prevent infinite loop in |
| 272 | // that case. |
| 273 | if (!in_eviction_) { |
| 274 | ProgressAsync(); |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | void PrefetchScheduler::NotifyAttributeMightChangedAndProgressAsync( |
kenoss | e8d8bbb5 | 2025-05-13 12:35:04 | [diff] [blame] | 279 | PrefetchContainer& prefetch_container, |
| 280 | bool should_progress) { |
| 281 | if (!should_progress) { |
| 282 | return; |
| 283 | } |
| 284 | |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 285 | const bool is_changed = queue_.MaybeUpdatePriority( |
| 286 | prefetch_container, CalculatePriority(prefetch_container)); |
| 287 | if (is_changed) { |
| 288 | ProgressAsync(); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | void PrefetchScheduler::ProgressAsync() { |
| 293 | if (is_progress_scheduled_) { |
| 294 | return; |
| 295 | } |
| 296 | is_progress_scheduled_ = true; |
| 297 | |
| 298 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| 299 | FROM_HERE, base::BindOnce(&PrefetchScheduler::Progress, |
| 300 | weak_method_factory_.GetWeakPtr())); |
| 301 | } |
| 302 | |
| 303 | void PrefetchScheduler::Progress() { |
| 304 | #if DCHECK_IS_ON() |
| 305 | // Asserts that reentrancy doesn't happen. |
| 306 | CHECK(!progress_reentrancy_guard_); |
| 307 | base::AutoReset guard(&progress_reentrancy_guard_, true); |
| 308 | #endif |
| 309 | |
| 310 | // Note that this doesn't correspond to the update in `ProgressAsync()` in 1:1 |
| 311 | // and there is a case updating `false` to `false` as this method can be |
| 312 | // called from `PrefetchService` directly. |
| 313 | is_progress_scheduled_ = false; |
| 314 | |
| 315 | // #algorithm |
| 316 | // |
| 317 | // 1. Start prefetches with burst priority with limit for burst. |
| 318 | // |
| 319 | // If the size of active set is < `GetActiveSetSizeLimitForBurst()`, pop |
| 320 | // `PrefetchContainer` with priority >= `kBurstThreshold` that can be |
| 321 | // started and start it. Continue it until active set size reaches the |
| 322 | // limit or queue becomes empty. |
| 323 | // |
| 324 | // 2. Start prefetches with limit. |
| 325 | // |
| 326 | // If the size of active set is < `GetActiveSetSizeLimitForBase()`, pop |
| 327 | // `PrefetchContainer` (with no priority condition) that can be |
| 328 | // started and start it. Continue it until active set size reaches the |
| 329 | // limit or queue becomes empty. |
| 330 | // |
| 331 | // TODO(crbug.com/406403063): Consider not to limit prefetches with burst |
| 332 | // priority. See |
| 333 | // https://chromium-review.googlesource.com/c/chromium/src/+/6402914/comment/8b5c845f_0b7f6f7e/ |
| 334 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 335 | auto internal = [&](PrefetchSchedulerPriority threshold_priority, |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 336 | size_t active_limit) { |
| 337 | // Invariant: `active_set_.size() == 0 && there is a ready prefetch` is |
| 338 | // false. I.e. doesn't stuck. |
| 339 | while (active_set_.size() < active_limit) { |
| 340 | std::optional<PrefetchQueue::Item> item = |
| 341 | queue_.Pop(IsReadyToStartPrefetch, threshold_priority); |
| 342 | if (!item.has_value()) { |
| 343 | break; |
| 344 | } |
| 345 | |
| 346 | base::WeakPtr<PrefetchContainer> prefetch_container = |
| 347 | item.value().prefetch_container; |
| 348 | // `prefetch_container` must be valid. It will be ensured by |
| 349 | // `PrefetchService` in the future. |
| 350 | // |
| 351 | // TODO(crbug.com/400761083): Use `CHECK`. |
| 352 | if (!prefetch_container) { |
| 353 | continue; |
| 354 | } |
| 355 | |
| 356 | // This call calls a method of `PrefetchService` and can incur methods of |
| 357 | // `PrefetchScheduler`. It is safe as we don't hold iterators at this |
| 358 | // timing. |
| 359 | ProgressOne(std::move(prefetch_container)); |
| 360 | } |
| 361 | }; |
| 362 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 363 | internal(PrefetchSchedulerPriority::kBurstThreshold, |
| 364 | GetActiveSetSizeLimitForBurst()); |
| 365 | internal(PrefetchSchedulerPriority::kBase, GetActiveSetSizeLimitForBase()); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 366 | } |
| 367 | |
| 368 | void PrefetchScheduler::ProgressOne( |
| 369 | base::WeakPtr<PrefetchContainer> prefetch_container) { |
| 370 | CHECK(prefetch_container); |
| 371 | |
| 372 | // Evict if needed. |
| 373 | [&]() { |
Hiroshige Hayashizaki | f55280f6 | 2025-08-19 17:23:20 | [diff] [blame] | 374 | auto* renderer_initiator_info = |
| 375 | prefetch_container->request().GetRendererInitiatorInfo(); |
| 376 | if (!renderer_initiator_info) { |
| 377 | return; |
| 378 | } |
| 379 | |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 380 | auto* prefetch_document_manager = |
Hiroshige Hayashizaki | f55280f6 | 2025-08-19 17:23:20 | [diff] [blame] | 381 | renderer_initiator_info->prefetch_document_manager(); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 382 | if (!prefetch_document_manager) { |
| 383 | return; |
| 384 | } |
| 385 | |
| 386 | base::WeakPtr<PrefetchContainer> prefetch_to_evict = std::get<1>( |
| 387 | prefetch_document_manager->CanPrefetchNow(prefetch_container.get())); |
| 388 | if (!prefetch_to_evict) { |
| 389 | return; |
| 390 | } |
| 391 | |
| 392 | { |
| 393 | base::AutoReset<bool> guard{&in_eviction_, true}; |
| 394 | prefetch_service_->EvictPrefetch(base::PassKey<PrefetchScheduler>(), |
| 395 | prefetch_to_evict); |
| 396 | } |
| 397 | }(); |
| 398 | |
| 399 | const bool is_started = prefetch_service_->StartSinglePrefetch( |
| 400 | base::PassKey<PrefetchScheduler>(), prefetch_container); |
| 401 | if (is_started) { |
| 402 | active_set_.push_back(prefetch_container); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | void PrefetchScheduler::SetCalculatePriorityForTesting( |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 407 | base::RepeatingCallback<PrefetchSchedulerPriority(const PrefetchContainer&)> |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 408 | callback) { |
| 409 | calculate_priority_for_test_ = std::move(callback); |
| 410 | } |
| 411 | |
| 412 | } // namespace content |