| // Copyright 2024 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/devtools/devtools_preload_storage.h" | |
| #include <optional> | |
| #include <set> | |
| #include "base/containers/flat_set.h" | |
| #include "base/notimplemented.h" | |
| #include "base/unguessable_token.h" | |
| #include "content/browser/devtools/protocol/preload_handler.h" | |
| #include "content/browser/preloading/prerender/prerender_metrics.h" | |
| #include "content/browser/renderer_host/render_frame_host_impl.h" | |
| #include "content/public/browser/preloading.h" | |
| #include "content/public/browser/render_frame_host.h" | |
| #include "net/http/http_request_headers.h" | |
| #include "services/network/public/cpp/headers_matcher.h" | |
| #include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h" | |
| #include "url/gurl.h" | |
| namespace content { | |
| namespace { | |
| // Mirrors `PrerenderInfoComparator`, which groups candidates by URL and | |
| // whether they target a new browsing context. It intentionally ignores form | |
| // submission and treats no target hint and `_self` as equivalent. | |
| struct PrerenderGroupKey { | |
| GURL prerender_url; | |
| bool is_target_blank; | |
| auto operator<=>(const PrerenderGroupKey&) const = default; | |
| }; | |
| } // namespace | |
| DOCUMENT_USER_DATA_KEY_IMPL(DevToolsPreloadStorage); | |
| DevToolsPreloadStorage::~DevToolsPreloadStorage() = default; | |
| void DevToolsPreloadStorage::UpdatePrefetchStatus( | |
| const GURL& prefetch_url, | |
| const base::UnguessableToken& preload_pipeline_id, | |
| PreloadingTriggeringOutcome outcome, | |
| PrefetchStatus status, | |
| const std::string& request_id) { | |
| // We return early in this case as the key will already have been removed | |
| // from `prefetch_data_map_` in `SpeculationCandidatesUpdated` and we don't | |
| // want to re-add it. | |
| if (status == PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved) { | |
| return; | |
| } | |
| prefetch_data_map_[prefetch_url] = { | |
| .preload_pipeline_id = preload_pipeline_id, | |
| .outcome = outcome, | |
| .status = status, | |
| .request_id = request_id}; | |
| } | |
| void DevToolsPreloadStorage::UpdatePrerenderStatus( | |
| blink::mojom::SpeculationAction action, | |
| std::optional<blink::mojom::SpeculationAction> effective_action, | |
| const GURL& prerender_url, | |
| bool form_submission, | |
| std::optional<blink::mojom::SpeculationTargetHint> target_hint, | |
| const base::UnguessableToken& preload_pipeline_id, | |
| PreloadingTriggeringOutcome outcome, | |
| std::optional<PrerenderFinalStatus> status, | |
| const std::optional<std::string>& disallowed_mojo_interface, | |
| const std::vector<network::MismatchedHttpRequestHeader>* | |
| mismatched_headers) { | |
| PrerenderKey key = {.prerender_url = prerender_url, | |
| .form_submission = form_submission, | |
| .target_hint = target_hint}; | |
| PrerenderData data; | |
| data.preload_pipeline_id = preload_pipeline_id; | |
| data.outcome = outcome; | |
| data.effective_action = effective_action; | |
| data.status = status; | |
| data.disallowed_mojo_interface = disallowed_mojo_interface; | |
| if (mismatched_headers) { | |
| data.mismatched_headers = *mismatched_headers; | |
| } | |
| switch (action) { | |
| case blink::mojom::SpeculationAction::kPrerender: | |
| prerender_data_map_[key] = std::move(data); | |
| break; | |
| case blink::mojom::SpeculationAction::kPrerenderUntilScript: | |
| prerender_until_script_data_map_[key] = std::move(data); | |
| break; | |
| case blink::mojom::SpeculationAction::kPrefetch: | |
| NOTREACHED(); | |
| } | |
| } | |
| void DevToolsPreloadStorage::SpeculationCandidatesUpdated( | |
| const std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) { | |
| std::set<PrefetchKey> prefetch_keys_from_candidates; | |
| std::set<PrerenderKey> prerender_keys_from_candidates; | |
| std::set<PrerenderKey> prerender_until_script_keys_from_candidates; | |
| for (const auto& candidate_ptr : candidates) { | |
| switch (candidate_ptr->action) { | |
| case blink::mojom::SpeculationAction::kPrefetch: | |
| prefetch_keys_from_candidates.insert(candidate_ptr->url); | |
| break; | |
| case blink::mojom::SpeculationAction::kPrerender: | |
| prerender_keys_from_candidates.insert( | |
| {.prerender_url = candidate_ptr->url, | |
| .form_submission = candidate_ptr->form_submission, | |
| .target_hint = candidate_ptr->target_browsing_context_name_hint}); | |
| break; | |
| case blink::mojom::SpeculationAction::kPrerenderUntilScript: | |
| prerender_until_script_keys_from_candidates.insert( | |
| {.prerender_url = candidate_ptr->url, | |
| .form_submission = candidate_ptr->form_submission, | |
| .target_hint = candidate_ptr->target_browsing_context_name_hint}); | |
| break; | |
| }; | |
| } | |
| std::erase_if(prefetch_data_map_, [&](const auto& pair) { | |
| return !prefetch_keys_from_candidates.contains(pair.first); | |
| }); | |
| std::erase_if(prerender_data_map_, [&](const auto& pair) { | |
| return !prerender_keys_from_candidates.contains(pair.first); | |
| }); | |
| std::optional<base::flat_set<PrerenderGroupKey>> | |
| prerender_groups_from_candidates; | |
| std::erase_if(prerender_until_script_data_map_, [&](const auto& pair) { | |
| if (prerender_until_script_keys_from_candidates.contains(pair.first)) { | |
| return false; | |
| } | |
| if (pair.second.effective_action != | |
| blink::mojom::SpeculationAction::kPrerender) { | |
| return true; | |
| } | |
| // Keep an upgraded attempt while the runtime retains its host for any | |
| // candidate in the same prerender group. Otherwise, a candidate key change | |
| // could make late DevTools attachment lose the upgraded attempt. | |
| if (!prerender_groups_from_candidates) { | |
| prerender_groups_from_candidates.emplace(); | |
| for (const auto& candidate_ptr : candidates) { | |
| if (candidate_ptr->action == | |
| blink::mojom::SpeculationAction::kPrerender || | |
| candidate_ptr->action == | |
| blink::mojom::SpeculationAction::kPrerenderUntilScript) { | |
| prerender_groups_from_candidates->insert( | |
| {.prerender_url = candidate_ptr->url, | |
| .is_target_blank = | |
| candidate_ptr->target_browsing_context_name_hint == | |
| blink::mojom::SpeculationTargetHint::kBlank}); | |
| } | |
| } | |
| } | |
| return !prerender_groups_from_candidates->contains( | |
| {.prerender_url = pair.first.prerender_url, | |
| .is_target_blank = pair.first.target_hint == | |
| blink::mojom::SpeculationTargetHint::kBlank}); | |
| }); | |
| } | |
| DevToolsPreloadStorage::DevToolsPreloadStorage(RenderFrameHost* rfh) | |
| : DocumentUserData<DevToolsPreloadStorage>(rfh) {} | |
| DevToolsPreloadStorage::PrerenderData::PrerenderData() = default; | |
| DevToolsPreloadStorage::PrerenderData::PrerenderData( | |
| const PrerenderData& other) = default; | |
| DevToolsPreloadStorage::PrerenderData::PrerenderData(PrerenderData&& other) = | |
| default; | |
| DevToolsPreloadStorage::PrerenderData& | |
| DevToolsPreloadStorage::PrerenderData::operator=(const PrerenderData& other) = | |
| default; | |
| DevToolsPreloadStorage::PrerenderData& | |
| DevToolsPreloadStorage::PrerenderData::operator=(PrerenderData&& other) = | |
| default; | |
| DevToolsPreloadStorage::PrerenderData::~PrerenderData() = default; | |
| } // namespace content |