no_state_prefetch_link_manager.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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
  3. // found in the LICENSE file.
  4. #include "components/no_state_prefetch/browser/no_state_prefetch_link_manager.h"
  5. #include <functional>
  6. #include <limits>
  7. #include <memory>
  8. #include <set>
  9. #include <string>
  10. #include <utility>
  11. #include "base/metrics/field_trial.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "build/build_config.h"
  14. #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
  15. #include "components/no_state_prefetch/browser/no_state_prefetch_handle.h"
  16. #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
  17. #include "content/public/browser/render_process_host.h"
  18. #include "content/public/browser/render_view_host.h"
  19. #include "content/public/browser/session_storage_namespace.h"
  20. #include "content/public/common/referrer.h"
  21. #include "mojo/public/cpp/bindings/associated_remote.h"
  22. #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
  23. #include "ui/gfx/geometry/size.h"
  24. #include "url/gurl.h"
  25. #include "url/origin.h"
  26. // TODO(crbug.com/722453): Use a dedicated build flag for GuestView.
  27. #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
  28. #include "components/guest_view/browser/guest_view_base.h" // nogncheck
  29. #endif
  30. using base::TimeTicks;
  31. using content::RenderViewHost;
  32. using content::SessionStorageNamespace;
  33. namespace prerender {
  34. namespace {
  35. int GetNextLinkTriggerId() {
  36. static int next_id = 1;
  37. return next_id++;
  38. }
  39. } // namespace
  40. NoStatePrefetchLinkManager::LinkTrigger::LinkTrigger(
  41. int launcher_render_process_id,
  42. int launcher_render_view_id,
  43. blink::mojom::PrerenderAttributesPtr attributes,
  44. const url::Origin& initiator_origin,
  45. base::TimeTicks creation_time,
  46. NoStatePrefetchContents* deferred_launcher)
  47. : launcher_render_process_id(launcher_render_process_id),
  48. launcher_render_view_id(launcher_render_view_id),
  49. url(attributes->url),
  50. trigger_type(attributes->trigger_type),
  51. referrer(content::Referrer(*attributes->referrer)),
  52. initiator_origin(initiator_origin),
  53. size(attributes->view_size),
  54. creation_time(creation_time),
  55. deferred_launcher(deferred_launcher),
  56. has_been_abandoned(false),
  57. link_trigger_id(GetNextLinkTriggerId()) {}
  58. NoStatePrefetchLinkManager::LinkTrigger::~LinkTrigger() {
  59. DCHECK_EQ(nullptr, handle.get())
  60. << "The NoStatePrefetchHandle should be destroyed before its Prerender.";
  61. }
  62. NoStatePrefetchLinkManager::NoStatePrefetchLinkManager(
  63. NoStatePrefetchManager* manager)
  64. : has_shutdown_(false), manager_(manager) {}
  65. NoStatePrefetchLinkManager::~NoStatePrefetchLinkManager() {
  66. for (auto& trigger : triggers_) {
  67. if (trigger->handle) {
  68. DCHECK(!trigger->handle->IsPrefetching())
  69. << "All running prefetchers should stop at the same time as the "
  70. << "NoStatePrefetchManager.";
  71. trigger->handle.reset();
  72. }
  73. }
  74. }
  75. absl::optional<int> NoStatePrefetchLinkManager::OnStartLinkTrigger(
  76. int launcher_render_process_id,
  77. int launcher_render_view_id,
  78. blink::mojom::PrerenderAttributesPtr attributes,
  79. const url::Origin& initiator_origin) {
  80. // TODO(crbug.com/722453): Use a dedicated build flag for GuestView.
  81. #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
  82. content::RenderViewHost* rvh = content::RenderViewHost::FromID(
  83. launcher_render_process_id, launcher_render_view_id);
  84. content::WebContents* web_contents =
  85. rvh ? content::WebContents::FromRenderViewHost(rvh) : nullptr;
  86. // Guests inside <webview> do not support cross-process navigation and so we
  87. // do not allow guests to prerender content.
  88. if (guest_view::GuestViewBase::IsGuest(web_contents))
  89. return absl::nullopt;
  90. #endif
  91. // Check if the launcher is itself an unswapped prerender.
  92. NoStatePrefetchContents* no_state_prefetch_contents =
  93. manager_->GetNoStatePrefetchContentsForRoute(launcher_render_process_id,
  94. launcher_render_view_id);
  95. if (no_state_prefetch_contents &&
  96. no_state_prefetch_contents->final_status() != FINAL_STATUS_UNKNOWN) {
  97. // The launcher is a prerender about to be destroyed asynchronously, but
  98. // its AddLinkRelPrerender message raced with shutdown. Ignore it.
  99. DCHECK_NE(FINAL_STATUS_USED, no_state_prefetch_contents->final_status());
  100. return absl::nullopt;
  101. }
  102. auto trigger = std::make_unique<LinkTrigger>(
  103. launcher_render_process_id, launcher_render_view_id,
  104. std::move(attributes), initiator_origin, manager_->GetCurrentTimeTicks(),
  105. no_state_prefetch_contents);
  106. // Stash pointer used only for comparison later.
  107. const LinkTrigger* trigger_ptr = trigger.get();
  108. triggers_.push_back(std::move(trigger));
  109. if (!no_state_prefetch_contents)
  110. StartLinkTriggers();
  111. // Check if the trigger we added is still at the end of the list. It
  112. // may have been discarded by StartLinkTriggers().
  113. if (!triggers_.empty() && triggers_.back().get() == trigger_ptr)
  114. return trigger_ptr->link_trigger_id;
  115. return absl::nullopt;
  116. }
  117. void NoStatePrefetchLinkManager::OnCancelLinkTrigger(int link_trigger_id) {
  118. LinkTrigger* trigger = FindByLinkTriggerId(link_trigger_id);
  119. if (!trigger)
  120. return;
  121. CancelLinkTrigger(trigger);
  122. StartLinkTriggers();
  123. }
  124. void NoStatePrefetchLinkManager::OnAbandonLinkTrigger(int link_trigger_id) {
  125. LinkTrigger* trigger = FindByLinkTriggerId(link_trigger_id);
  126. if (!trigger)
  127. return;
  128. if (!trigger->handle) {
  129. RemoveLinkTrigger(trigger);
  130. return;
  131. }
  132. trigger->has_been_abandoned = true;
  133. trigger->handle->OnNavigateAway();
  134. DCHECK(trigger->handle);
  135. // If the prefetcher is not running, remove it from the list so it does not
  136. // leak. If it is running, it will send a cancel event when it stops which
  137. // will remove it.
  138. if (!trigger->handle->IsPrefetching())
  139. RemoveLinkTrigger(trigger);
  140. }
  141. bool NoStatePrefetchLinkManager::IsEmpty() const {
  142. return triggers_.empty();
  143. }
  144. bool NoStatePrefetchLinkManager::TriggerIsRunningForTesting(
  145. LinkTrigger* trigger) const {
  146. return trigger->handle.get() != nullptr;
  147. }
  148. size_t NoStatePrefetchLinkManager::CountRunningTriggers() const {
  149. return std::count_if(triggers_.begin(), triggers_.end(),
  150. [](const std::unique_ptr<LinkTrigger>& trigger) {
  151. return trigger->handle &&
  152. trigger->handle->IsPrefetching();
  153. });
  154. }
  155. void NoStatePrefetchLinkManager::StartLinkTriggers() {
  156. if (has_shutdown_)
  157. return;
  158. size_t total_started_trigger_count = 0;
  159. std::list<LinkTrigger*> abandoned_triggers;
  160. std::list<std::list<std::unique_ptr<LinkTrigger>>::iterator> pending_triggers;
  161. std::multiset<std::pair<int, int>> running_launcher_and_render_view_routes;
  162. // Scan the list, counting how many prefetches have handles (and so were added
  163. // to the NoStatePrefetchManager). The count is done for the system as a
  164. // whole, and also per launcher.
  165. for (auto it = triggers_.begin(); it != triggers_.end(); ++it) {
  166. std::unique_ptr<LinkTrigger>& trigger = *it;
  167. // Skip triggers launched by a trigger.
  168. if (trigger->deferred_launcher)
  169. continue;
  170. if (!trigger->handle) {
  171. pending_triggers.push_back(it);
  172. } else {
  173. ++total_started_trigger_count;
  174. if (trigger->has_been_abandoned) {
  175. abandoned_triggers.push_back(trigger.get());
  176. } else {
  177. // We do not count abandoned prefetches towards their launcher, since it
  178. // has already navigated on to another page.
  179. std::pair<int, int> launcher_and_render_view_route(
  180. trigger->launcher_render_process_id,
  181. trigger->launcher_render_view_id);
  182. running_launcher_and_render_view_routes.insert(
  183. launcher_and_render_view_route);
  184. DCHECK_GE(manager_->config().max_link_concurrency_per_launcher,
  185. running_launcher_and_render_view_routes.count(
  186. launcher_and_render_view_route));
  187. }
  188. }
  189. }
  190. DCHECK_LE(abandoned_triggers.size(), total_started_trigger_count);
  191. DCHECK_GE(manager_->config().max_link_concurrency,
  192. total_started_trigger_count);
  193. DCHECK_LE(CountRunningTriggers(), total_started_trigger_count);
  194. TimeTicks now = manager_->GetCurrentTimeTicks();
  195. // Scan the pending triggers, starting triggers as we can.
  196. for (const std::list<std::unique_ptr<LinkTrigger>>::iterator& it :
  197. pending_triggers) {
  198. LinkTrigger* pending_trigger = it->get();
  199. base::TimeDelta trigger_age = now - pending_trigger->creation_time;
  200. if (trigger_age >= manager_->config().max_wait_to_launch) {
  201. // This trigger waited too long in the queue before launching.
  202. triggers_.erase(it);
  203. continue;
  204. }
  205. std::pair<int, int> launcher_and_render_view_route(
  206. pending_trigger->launcher_render_process_id,
  207. pending_trigger->launcher_render_view_id);
  208. if (manager_->config().max_link_concurrency_per_launcher <=
  209. running_launcher_and_render_view_routes.count(
  210. launcher_and_render_view_route)) {
  211. // This trigger's launcher is already at its limit.
  212. continue;
  213. }
  214. if (total_started_trigger_count >=
  215. manager_->config().max_link_concurrency ||
  216. total_started_trigger_count >= triggers_.size()) {
  217. // The system is already at its prerender concurrency limit. Try removing
  218. // an abandoned trigger, if one exists, to make room.
  219. if (abandoned_triggers.empty())
  220. return;
  221. CancelLinkTrigger(abandoned_triggers.front());
  222. --total_started_trigger_count;
  223. abandoned_triggers.pop_front();
  224. }
  225. std::unique_ptr<NoStatePrefetchHandle> handle =
  226. manager_->StartPrefetchingFromLinkRelPrerender(
  227. pending_trigger->launcher_render_process_id,
  228. pending_trigger->launcher_render_view_id, pending_trigger->url,
  229. pending_trigger->trigger_type, pending_trigger->referrer,
  230. pending_trigger->initiator_origin, pending_trigger->size);
  231. if (!handle) {
  232. // This trigger couldn't be launched, it's gone.
  233. triggers_.erase(it);
  234. continue;
  235. }
  236. if (handle->IsPrefetching()) {
  237. // We have successfully started a new prefetcher.
  238. pending_trigger->handle = std::move(handle);
  239. ++total_started_trigger_count;
  240. pending_trigger->handle->SetObserver(this);
  241. running_launcher_and_render_view_routes.insert(
  242. launcher_and_render_view_route);
  243. } else {
  244. triggers_.erase(it);
  245. }
  246. }
  247. }
  248. NoStatePrefetchLinkManager::LinkTrigger*
  249. NoStatePrefetchLinkManager::FindByNoStatePrefetchHandle(
  250. NoStatePrefetchHandle* no_state_prefetch_handle) {
  251. DCHECK(no_state_prefetch_handle);
  252. for (auto& trigger : triggers_) {
  253. if (trigger->handle.get() == no_state_prefetch_handle)
  254. return trigger.get();
  255. }
  256. return nullptr;
  257. }
  258. NoStatePrefetchLinkManager::LinkTrigger*
  259. NoStatePrefetchLinkManager::FindByLinkTriggerId(int link_trigger_id) {
  260. for (auto& trigger : triggers_) {
  261. if (trigger->link_trigger_id == link_trigger_id)
  262. return trigger.get();
  263. }
  264. return nullptr;
  265. }
  266. void NoStatePrefetchLinkManager::RemoveLinkTrigger(LinkTrigger* trigger) {
  267. for (auto it = triggers_.begin(); it != triggers_.end(); ++it) {
  268. LinkTrigger* current_trigger = it->get();
  269. if (current_trigger == trigger) {
  270. std::unique_ptr<NoStatePrefetchHandle> own_handle =
  271. std::move(trigger->handle);
  272. triggers_.erase(it);
  273. return;
  274. }
  275. }
  276. NOTREACHED();
  277. }
  278. void NoStatePrefetchLinkManager::CancelLinkTrigger(LinkTrigger* trigger) {
  279. for (auto it = triggers_.begin(); it != triggers_.end(); ++it) {
  280. LinkTrigger* current_trigger = it->get();
  281. if (current_trigger == trigger) {
  282. std::unique_ptr<NoStatePrefetchHandle> own_handle =
  283. std::move(trigger->handle);
  284. triggers_.erase(it);
  285. if (own_handle)
  286. own_handle->OnCancel();
  287. return;
  288. }
  289. }
  290. NOTREACHED();
  291. }
  292. void NoStatePrefetchLinkManager::Shutdown() {
  293. has_shutdown_ = true;
  294. }
  295. void NoStatePrefetchLinkManager::OnPrefetchStop(
  296. NoStatePrefetchHandle* no_state_prefetch_handle) {
  297. LinkTrigger* trigger = FindByNoStatePrefetchHandle(no_state_prefetch_handle);
  298. if (!trigger)
  299. return;
  300. RemoveLinkTrigger(trigger);
  301. StartLinkTriggers();
  302. }
  303. void NoStatePrefetchLinkManager::OnPrefetchNetworkBytesChanged(
  304. NoStatePrefetchHandle* no_state_prefetch_handle) {}
  305. } // namespace prerender