blob: 6f5612e5d58da4c30ce1f2d647ad4e696d61e151 [file]
// 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