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
content / browser / preloading / prefetch / no_vary_search_helper.h [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_NO_VARY_SEARCH_HELPER_H_
#define CONTENT_BROWSER_PRELOADING_PREFETCH_NO_VARY_SEARCH_HELPER_H_
#include <map>
#include <memory>
#include <vector>
#include "base/feature_list.h"
#include "content/browser/preloading/prefetch/prefetch_container.h"
#include "net/http/http_no_vary_search_data.h"
#include "services/network/public/mojom/no_vary_search.mojom.h"
#include "url/gurl.h"
namespace network::mojom {
class URLResponseHead;
} // namespace network::mojom
namespace content {
class RenderFrameHost;
// Helpers to keep track of prefetched URLs that have No-Vary-Search
// header present in their responses.
// The source of truth is the `prefetches` and the helpers iterates over
// `prefetches` to find matching `PrefetchContainer`s.
namespace no_vary_search {
// See comments inside `IterateCandidates()` for requirements for `PrefetchKey`.
template <typename PrefetchKey>
class PrefetchKeyTraits;
template <>
class PrefetchKeyTraits<GURL> {
public:
using PrefetchKey = GURL;
static const GURL& GetURL(const PrefetchKey& key) { return key; }
static PrefetchKey KeyWithNewURL(const PrefetchKey& old_key,
const GURL& new_url) {
return new_url;
}
static bool NonUrlPartIsSame(const PrefetchKey& key1,
const PrefetchKey& key2) {
return true;
}
};
template <>
class PrefetchKeyTraits<PrefetchContainer::Key> {
public:
static const GURL& GetURL(const PrefetchContainer::Key& key) {
return key.url();
}
static PrefetchContainer::Key KeyWithNewURL(
const PrefetchContainer::Key& old_key,
const GURL& new_url) {
return old_key.WithNewUrl(new_url);
}
static bool NonUrlPartIsSame(const PrefetchContainer::Key& key1,
const PrefetchContainer::Key& key2) {
return key1.NonUrlPartIsSame(key2);
}
};
enum class MatchType {
// URL is exactly the same.
kExact,
// URL is equivalent due to the received No-Vary-Search header.
kNoVarySearchHeader,
// URL is equivalent due to the No-Vary-Search hint.
kNoVarySearchHint,
// The non-ref/query parts of URL are the same.
kOther
};
// Indicates whether `IterateCandidates` should continue or finish after
// `callback` is called.
enum class IterateCandidateResult { kContinue, kFinish };
// Call `callback` on every `PrefetchContainer`s that can match with `url`, in
// the order of
// 1. Exact match (`MatchType::kExact`).
// 2. No-Vary-Search matches (`MatchType::kNoVarySearch`), or
// URLs with the same non-ref/query part as `url` (`MatchType::kOther`).
template <typename PrefetchKey, typename Value>
void IterateCandidates(
const PrefetchKey& key,
const std::map<PrefetchKey, Value>& prefetches,
base::RepeatingCallback<IterateCandidateResult(const Value&, MatchType)>
callback) {
auto it_exact_match = prefetches.find(key);
if (it_exact_match != prefetches.end() && it_exact_match->second) {
if (callback.Run(it_exact_match->second, MatchType::kExact) ==
IterateCandidateResult::kFinish) {
return;
}
}
GURL::Replacements replacements;
replacements.ClearRef();
replacements.ClearQuery();
const GURL& key_url = PrefetchKeyTraits<PrefetchKey>::GetURL(key);
GURL url_with_no_query = key_url.ReplaceComponents(replacements);
// `std::map<GURL, ...>` is sorted by lexicographical string order of
// the normalized URLs (`GURL::spec_`, i.e. `possibly_invalid_spec()`).
// For a URL like `https://example.com/index.html?query#ref`, the
// `lower_bound` call will get the first URL starting with
// `https://example.com/index.html` (if any), and iterating by `++it` will get
// the URLs starting with `https://example.com/index.html` in lexicographical
// order until the URL without the `https://example.com/index.html` prefix is
// encountered.
//
// This is possible because URLs with the same prefix are in consecutively
// placed in the `std::map<PrefetchKey, ...>` iteration order. `GURL` and
// `std::pair<T, GURL>` satisfies this requirement and thus corresponding
// `PrefetchKeyTraits` are defined, while e.g. `std::pair<GURL, T>` wouldn't
// work.
//
// The same applies to `std::map<std::pair<DocumentToken, GURL>, ...>`, as
// URLs within the same `DocumentToken` is sorted in the same way.
// An additional check of `DocumentToken` is needed to ensure we still
// iterating URLs within the same `DocumentToken`, which is done in
// `NonUrlPartIsSame()`.
for (auto it =
prefetches.lower_bound(PrefetchKeyTraits<PrefetchKey>::KeyWithNewURL(
key, url_with_no_query));
it != prefetches.end(); ++it) {
const GURL& prefetch_container_url =
PrefetchKeyTraits<PrefetchKey>::GetURL(it->first);
if (!PrefetchKeyTraits<PrefetchKey>::NonUrlPartIsSame(key, it->first) ||
!prefetch_container_url.possibly_invalid_spec().starts_with(
url_with_no_query.possibly_invalid_spec())) {
break;
}
// `it_exact_match` is already visited above and thus skipped.
if (it == it_exact_match) {
continue;
}
if (!it->second) {
continue;
}
// The URLs starting with `https://example.com/index.html` don't necessarily
// have the same non-ref/query parts. See
// `NoVarySearchHelperTest.DoNotPrefixMatch` unit tests for concrete
// examples.
if (prefetch_container_url.ReplaceComponents(replacements) !=
url_with_no_query) {
continue;
}
const MatchType match_type = [&]() {
const auto& prefetch_container = it->second;
if (prefetch_container->IsNoVarySearchHeaderMatch(key_url)) {
return MatchType::kNoVarySearchHeader;
} else if (prefetch_container->ShouldWaitForNoVarySearchHeader(key_url)) {
return MatchType::kNoVarySearchHint;
} else {
return MatchType::kOther;
}
NOTREACHED();
}();
if (callback.Run(it->second, match_type) ==
IterateCandidateResult::kFinish) {
break;
}
}
}
// Get a PrefetchContainer from `prefetches` that can serve `url`, either:
// - Via exact match, or
// - Via No-Vary-Search information if exact match is not found, the feature is
// enabled and `SetNoVarySearchData()` is called for such `PrefetchContainer`s.
template <typename PrefetchKey, typename Value>
base::WeakPtr<PrefetchContainer> MatchUrl(
const PrefetchKey& key,
const std::map<PrefetchKey, Value>& prefetches) {
base::WeakPtr<PrefetchContainer> result = nullptr;
IterateCandidates(
key, prefetches,
base::BindRepeating(
[](base::WeakPtr<PrefetchContainer>* result,
const Value& prefetch_container, MatchType match_type) {
switch (match_type) {
case MatchType::kExact:
case MatchType::kNoVarySearchHeader:
// TODO(crbug.com/40064891): Revisit which PrefetchContainer to
// return when there are multiple candidates. Currently we
// return the first PrefetchContainer in URL lexicographic
// order.
*result = prefetch_container->GetWeakPtr();
return IterateCandidateResult::kFinish;
case MatchType::kNoVarySearchHint:
case MatchType::kOther:
return IterateCandidateResult::kContinue;
}
},
base::Unretained(&result)));
return result;
}
// Return the (URL,PrefetchContainer) pairs for a specific Url without
// query and reference. Allow as input urls with query and/or reference
// for ease of use (remove query/reference during lookup).
template <typename PrefetchKey, typename Value>
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>>
GetAllForUrlWithoutRefAndQueryForTesting(
const PrefetchKey& key,
const std::map<PrefetchKey, Value>& prefetches) {
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> result;
IterateCandidates(
key, prefetches,
base::BindRepeating(
[](std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>>*
result,
const Value& prefetch_container, MatchType match_type) {
result->emplace_back(prefetch_container->GetURL(),
prefetch_container->GetWeakPtr());
return IterateCandidateResult::kContinue;
},
base::Unretained(&result)));
return result;
}
// Parse and return `HttpNoVarySearchData` from `head`, if any.
//
// On parse errors, send No-Vary-Search parsing errors in DevTools console.
// The method will test if there are errors/warning that the developer
// needs to know about, and if there are send them to the DevTools console.
std::optional<net::HttpNoVarySearchData> ProcessHead(
const network::mojom::URLResponseHead& head,
const GURL& url,
RenderFrameHost* rfh);
// TODO(crbug.com/331591646): This is used in both prerender and prefetch,
// consider moving in a common location.
// Parse No-Vary-Search from mojom structure received from network service.
net::HttpNoVarySearchData ParseHttpNoVarySearchDataFromMojom(
const network::mojom::NoVarySearchPtr& no_vary_search_ptr);
} // namespace no_vary_search
} // namespace content
#endif // CONTENT_BROWSER_PRELOADING_PREFETCH_NO_VARY_SEARCH_HELPER_H_