• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 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 "net/first_party_sets/global_first_party_sets.h"
6 
7 #include <iterator>
8 #include <map>
9 #include <optional>
10 #include <set>
11 #include <tuple>
12 #include <utility>
13 
14 #include "base/containers/contains.h"
15 #include "base/containers/flat_map.h"
16 #include "base/containers/flat_set.h"
17 #include "base/functional/function_ref.h"
18 #include "base/not_fatal_until.h"
19 #include "base/ranges/algorithm.h"
20 #include "base/types/optional_ref.h"
21 #include "net/base/schemeful_site.h"
22 #include "net/first_party_sets/addition_overlaps_union_find.h"
23 #include "net/first_party_sets/first_party_set_entry.h"
24 #include "net/first_party_sets/first_party_set_entry_override.h"
25 #include "net/first_party_sets/first_party_set_metadata.h"
26 #include "net/first_party_sets/first_party_sets_context_config.h"
27 #include "net/first_party_sets/first_party_sets_validator.h"
28 #include "net/first_party_sets/local_set_declaration.h"
29 
30 namespace net {
31 
32 namespace {
33 
34 using FlattenedSets = base::flat_map<SchemefulSite, FirstPartySetEntry>;
35 using SingleSet = base::flat_map<SchemefulSite, FirstPartySetEntry>;
36 
37 // Converts a list of First-Party Sets from a SingleSet to a FlattenedSet
38 // representation.
Flatten(const std::vector<SingleSet> & set_list)39 FlattenedSets Flatten(const std::vector<SingleSet>& set_list) {
40   FlattenedSets sets;
41   for (const auto& set : set_list) {
42     for (const auto& site_and_entry : set) {
43       bool inserted = sets.emplace(site_and_entry).second;
44       CHECK(inserted);
45     }
46   }
47   return sets;
48 }
49 
50 std::pair<SchemefulSite, FirstPartySetEntryOverride>
SiteAndEntryToSiteAndOverride(const std::pair<SchemefulSite,FirstPartySetEntry> & pair)51 SiteAndEntryToSiteAndOverride(
52     const std::pair<SchemefulSite, FirstPartySetEntry>& pair) {
53   return std::make_pair(pair.first, FirstPartySetEntryOverride(pair.second));
54 }
55 
56 }  // namespace
57 
58 GlobalFirstPartySets::GlobalFirstPartySets() = default;
59 
GlobalFirstPartySets(base::Version public_sets_version,base::flat_map<SchemefulSite,FirstPartySetEntry> entries,base::flat_map<SchemefulSite,SchemefulSite> aliases)60 GlobalFirstPartySets::GlobalFirstPartySets(
61     base::Version public_sets_version,
62     base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
63     base::flat_map<SchemefulSite, SchemefulSite> aliases)
64     : GlobalFirstPartySets(
65           public_sets_version,
66           public_sets_version.IsValid()
67               ? std::move(entries)
68               : base::flat_map<SchemefulSite, FirstPartySetEntry>(),
69           public_sets_version.IsValid()
70               ? std::move(aliases)
71               : base::flat_map<SchemefulSite, SchemefulSite>(),
72           FirstPartySetsContextConfig(),
73           base::flat_map<SchemefulSite, SchemefulSite>()) {}
74 
GlobalFirstPartySets(base::Version public_sets_version,base::flat_map<SchemefulSite,FirstPartySetEntry> entries,base::flat_map<SchemefulSite,SchemefulSite> aliases,FirstPartySetsContextConfig manual_config,base::flat_map<SchemefulSite,SchemefulSite> manual_aliases)75 GlobalFirstPartySets::GlobalFirstPartySets(
76     base::Version public_sets_version,
77     base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
78     base::flat_map<SchemefulSite, SchemefulSite> aliases,
79     FirstPartySetsContextConfig manual_config,
80     base::flat_map<SchemefulSite, SchemefulSite> manual_aliases)
81     : public_sets_version_(std::move(public_sets_version)),
82       entries_(std::move(entries)),
83       aliases_(std::move(aliases)),
84       manual_config_(std::move(manual_config)),
85       manual_aliases_(std::move(manual_aliases)) {
86   if (!public_sets_version_.IsValid()) {
87     CHECK(entries_.empty());
88     CHECK(aliases_.empty());
89   }
90 
91   CHECK(base::ranges::all_of(aliases_, [&](const auto& pair) {
92     return entries_.contains(pair.second);
93   }));
94   CHECK(IsValid(), base::NotFatalUntil::M130) << "Sets must be valid";
95 }
96 
97 GlobalFirstPartySets::GlobalFirstPartySets(GlobalFirstPartySets&&) = default;
98 GlobalFirstPartySets& GlobalFirstPartySets::operator=(GlobalFirstPartySets&&) =
99     default;
100 
101 GlobalFirstPartySets::~GlobalFirstPartySets() = default;
102 
103 bool GlobalFirstPartySets::operator==(const GlobalFirstPartySets& other) const =
104     default;
105 
106 bool GlobalFirstPartySets::operator!=(const GlobalFirstPartySets& other) const =
107     default;
108 
Clone() const109 GlobalFirstPartySets GlobalFirstPartySets::Clone() const {
110   return GlobalFirstPartySets(public_sets_version_, entries_, aliases_,
111                               manual_config_.Clone(), manual_aliases_);
112 }
113 
FindEntry(const SchemefulSite & site,const FirstPartySetsContextConfig & config) const114 std::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
115     const SchemefulSite& site,
116     const FirstPartySetsContextConfig& config) const {
117   return FindEntry(site, &config);
118 }
119 
FindEntry(const SchemefulSite & site,const FirstPartySetsContextConfig * config) const120 std::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
121     const SchemefulSite& site,
122     const FirstPartySetsContextConfig* config) const {
123   // Check if `site` can be found in the customizations first.
124   if (config) {
125     if (const auto override = config->FindOverride(site);
126         override.has_value()) {
127       return override->IsDeletion() ? std::nullopt
128                                     : std::make_optional(override->GetEntry());
129     }
130   }
131 
132   // Now see if it's in the manual config (with or without a manual alias).
133   if (const auto manual_override = manual_config_.FindOverride(site);
134       manual_override.has_value()) {
135     return manual_override->IsDeletion()
136                ? std::nullopt
137                : std::make_optional(manual_override->GetEntry());
138   }
139 
140   // Finally, look up in `entries_`, applying an alias if applicable.
141   const auto canonical_it = aliases_.find(site);
142   const SchemefulSite& canonical_site =
143       canonical_it == aliases_.end() ? site : canonical_it->second;
144   if (const auto entry_it = entries_.find(canonical_site);
145       entry_it != entries_.end()) {
146     return entry_it->second;
147   }
148 
149   return std::nullopt;
150 }
151 
152 base::flat_map<SchemefulSite, FirstPartySetEntry>
FindEntries(const base::flat_set<SchemefulSite> & sites,const FirstPartySetsContextConfig & config) const153 GlobalFirstPartySets::FindEntries(
154     const base::flat_set<SchemefulSite>& sites,
155     const FirstPartySetsContextConfig& config) const {
156   std::vector<std::pair<SchemefulSite, FirstPartySetEntry>> sites_to_entries;
157   for (const SchemefulSite& site : sites) {
158     const std::optional<FirstPartySetEntry> entry = FindEntry(site, config);
159     if (entry.has_value()) {
160       sites_to_entries.emplace_back(site, entry.value());
161     }
162   }
163   return sites_to_entries;
164 }
165 
ComputeMetadata(const SchemefulSite & site,base::optional_ref<const SchemefulSite> top_frame_site,const FirstPartySetsContextConfig & fps_context_config) const166 FirstPartySetMetadata GlobalFirstPartySets::ComputeMetadata(
167     const SchemefulSite& site,
168     base::optional_ref<const SchemefulSite> top_frame_site,
169     const FirstPartySetsContextConfig& fps_context_config) const {
170   return FirstPartySetMetadata(
171       FindEntry(site, fps_context_config),
172       top_frame_site ? FindEntry(*top_frame_site, fps_context_config)
173                      : std::nullopt);
174 }
175 
ApplyManuallySpecifiedSet(const LocalSetDeclaration & local_set_declaration)176 void GlobalFirstPartySets::ApplyManuallySpecifiedSet(
177     const LocalSetDeclaration& local_set_declaration) {
178   CHECK(manual_config_.empty());
179   CHECK(manual_aliases_.empty());
180   if (local_set_declaration.empty()) {
181     // Nothing to do.
182     return;
183   }
184 
185   base::flat_map<SchemefulSite, SchemefulSite> manual_aliases =
186       local_set_declaration.aliases();
187 
188   base::flat_map<SchemefulSite, FirstPartySetEntry> manual_entries =
189       local_set_declaration.entries();
190   for (const auto& [alias, canonical] : manual_aliases) {
191     manual_entries.emplace(alias, manual_entries.find(canonical)->second);
192   }
193 
194   // We handle the manually-specified set the same way as we handle
195   // replacement enterprise policy sets.
196   manual_config_ = ComputeConfig(SetsMutation(
197       /*replacement_sets=*/{manual_entries},
198       /*addition_sets=*/{}));
199   manual_aliases_ = std::move(manual_aliases);
200 
201   CHECK(IsValid(), base::NotFatalUntil::M130) << "Sets must be valid";
202 }
203 
UnsafeSetManualConfig(FirstPartySetsContextConfig manual_config)204 void GlobalFirstPartySets::UnsafeSetManualConfig(
205     FirstPartySetsContextConfig manual_config) {
206   CHECK(manual_config_.empty());
207   manual_config_ = std::move(manual_config);
208 }
209 
210 base::flat_map<SchemefulSite, FirstPartySetEntry>
FindPrimariesAffectedByAdditions(const FlattenedSets & additions) const211 GlobalFirstPartySets::FindPrimariesAffectedByAdditions(
212     const FlattenedSets& additions) const {
213   std::vector<std::pair<SchemefulSite, FirstPartySetEntry>>
214       addition_intersected_primaries;
215   for (const auto& [new_member, new_entry] : additions) {
216     if (const auto entry = FindEntry(new_member, /*config=*/nullptr);
217         entry.has_value()) {
218       // Found an overlap with the existing list of sets.
219       addition_intersected_primaries.emplace_back(entry->primary(), new_entry);
220     }
221   }
222   return addition_intersected_primaries;
223 }
224 
225 std::pair<base::flat_map<SchemefulSite, base::flat_set<SchemefulSite>>,
226           base::flat_set<SchemefulSite>>
FindPrimariesAffectedByReplacements(const FlattenedSets & replacements,const FlattenedSets & additions,const base::flat_map<SchemefulSite,FirstPartySetEntry> & addition_intersected_primaries) const227 GlobalFirstPartySets::FindPrimariesAffectedByReplacements(
228     const FlattenedSets& replacements,
229     const FlattenedSets& additions,
230     const base::flat_map<SchemefulSite, FirstPartySetEntry>&
231         addition_intersected_primaries) const {
232   if (replacements.empty()) {
233     return {{}, {}};
234   }
235 
236   const auto canonicalize = [&](const SchemefulSite& site) {
237     const auto it = aliases_.find(site);
238     return it != aliases_.end() ? it->second : site;
239   };
240   std::map<SchemefulSite, std::set<SchemefulSite>> canonical_to_aliases;
241   ForEachAlias([&](const SchemefulSite& alias, const SchemefulSite& canonical) {
242     canonical_to_aliases[canonical].insert(alias);
243   });
244   // Runs the given FunctionRef for all (existing) variants of the given site,
245   // i.e. all the aliases and the "canonical" variant.
246   const auto for_all_variants =
247       [canonical_to_aliases = std::move(canonical_to_aliases),
248        canonicalize = std::move(canonicalize)](
249           const SchemefulSite& site,
250           const base::FunctionRef<void(const SchemefulSite&)> f) {
251         const SchemefulSite canonical = canonicalize(site);
252         f(canonical);
253         if (const auto it = canonical_to_aliases.find(canonical);
254             it != canonical_to_aliases.end()) {
255           for (const auto& alias : it->second) {
256             f(alias);
257           }
258         }
259       };
260 
261   // Maps an existing primary site to the members it lost due to replacement.
262   base::flat_map<SchemefulSite, base::flat_set<SchemefulSite>>
263       potential_singletons;
264   // Stores existing primary sites which have left their sets (via
265   // replacement), and whose existing members should be removed from the set
266   // (excluding any custom sets that those members are involved in).
267   base::flat_set<SchemefulSite> replaced_existing_primaries;
268   for (const auto& [new_site, unused_entry] : replacements) {
269     const auto existing_entry = FindEntry(new_site, /*config=*/nullptr);
270     if (!existing_entry.has_value()) {
271       continue;
272     }
273     if (!addition_intersected_primaries.contains(existing_entry->primary()) &&
274         !additions.contains(existing_entry->primary()) &&
275         !replacements.contains(existing_entry->primary())) {
276       // The existing site's primary isn't involved in any of the customized
277       // sets, so it might become a singleton (if all of its variants and
278       // non-primaries [and their variants] are replaced by the
279       // customizations).
280       for_all_variants(new_site, [&](const SchemefulSite& variant) {
281         if (existing_entry->primary() != variant) {
282           potential_singletons[existing_entry->primary()].insert(variant);
283         }
284       });
285     }
286 
287     if (existing_entry->primary() == new_site) {
288       // `new_site` was a primary in the existing sets, but is in the
289       // replacement sets, so its non-primaries (and aliases) might need to be
290       // deleted/hidden.
291       bool inserted =
292           replaced_existing_primaries.emplace(existing_entry->primary()).second;
293       CHECK(inserted);
294     }
295   }
296 
297   return std::make_pair(potential_singletons, replaced_existing_primaries);
298 }
299 
ComputeConfig(const SetsMutation & mutation) const300 FirstPartySetsContextConfig GlobalFirstPartySets::ComputeConfig(
301     const SetsMutation& mutation) const {
302   if (base::ranges::all_of(mutation.replacements(), &SingleSet::empty) &&
303       base::ranges::all_of(mutation.additions(), &SingleSet::empty)) {
304     // Nothing to do.
305     return FirstPartySetsContextConfig();
306   }
307 
308   const FlattenedSets replacements = Flatten(mutation.replacements());
309   const FlattenedSets additions =
310       Flatten(NormalizeAdditionSets(mutation.additions()));
311 
312   // Maps a site to its override.
313   std::vector<std::pair<SchemefulSite, FirstPartySetEntryOverride>>
314       site_to_override;
315   base::ranges::transform(replacements, std::back_inserter(site_to_override),
316                           SiteAndEntryToSiteAndOverride);
317   base::ranges::transform(additions, std::back_inserter(site_to_override),
318                           SiteAndEntryToSiteAndOverride);
319 
320   // Maps old primary site to new entry.
321   const base::flat_map<SchemefulSite, FirstPartySetEntry>
322       addition_intersected_primaries =
323           FindPrimariesAffectedByAdditions(additions);
324 
325   auto [potential_singletons, replaced_existing_primaries] =
326       FindPrimariesAffectedByReplacements(replacements, additions,
327                                           addition_intersected_primaries);
328 
329   if (!addition_intersected_primaries.empty() ||
330       !potential_singletons.empty() || !replaced_existing_primaries.empty()) {
331     // Find out which potential singletons are actually singletons; delete
332     // members whose primaries left; and reparent the sets that intersected with
333     // an addition set.
334     // Note: use a null config here, to avoid taking unrelated policy sets into
335     // account.
336     ForEachEffectiveSetEntry(
337         /*config=*/nullptr,
338         [&](const SchemefulSite& member, const FirstPartySetEntry& set_entry) {
339           // Reparent all sites in any intersecting addition sets.
340           if (const auto entry =
341                   addition_intersected_primaries.find(set_entry.primary());
342               entry != addition_intersected_primaries.end() &&
343               !replacements.contains(member)) {
344             site_to_override.emplace_back(
345                 member, FirstPartySetEntry(entry->second.primary(),
346                                            member == entry->second.primary()
347                                                ? SiteType::kPrimary
348                                                : SiteType::kAssociated,
349                                            std::nullopt));
350           }
351           if (member == set_entry.primary())
352             return true;
353           // Remove non-singletons from the potential list.
354           if (const auto entry = potential_singletons.find(set_entry.primary());
355               entry != potential_singletons.end() &&
356               !entry->second.contains(member)) {
357             // This primary lost members, but it still has at least one
358             // (`member`), so it's not a singleton.
359             potential_singletons.erase(entry);
360           }
361           // Remove members from sets whose primary left.
362           if (replaced_existing_primaries.contains(set_entry.primary()) &&
363               !replacements.contains(member) &&
364               !addition_intersected_primaries.contains(set_entry.primary())) {
365             site_to_override.emplace_back(member, FirstPartySetEntryOverride());
366           }
367 
368           return true;
369         });
370 
371     // Any primary remaining in `potential_singleton` is a real singleton, so
372     // delete it:
373     for (const auto& [primary, members] : potential_singletons) {
374       site_to_override.emplace_back(primary, FirstPartySetEntryOverride());
375     }
376   }
377 
378   // For every pre-existing alias that would now refer to a site in the overlay,
379   // which is not already contained in the overlay, we explicitly ignore that
380   // alias.
381   ForEachAlias([&](const SchemefulSite& alias, const SchemefulSite& canonical) {
382     if (base::Contains(
383             site_to_override, canonical,
384             &std::pair<SchemefulSite, FirstPartySetEntryOverride>::first) &&
385         !base::Contains(
386             site_to_override, alias,
387             &std::pair<SchemefulSite, FirstPartySetEntryOverride>::first)) {
388       site_to_override.emplace_back(alias, FirstPartySetEntryOverride());
389     }
390   });
391 
392   FirstPartySetsContextConfig config(std::move(site_to_override));
393   CHECK(IsValid(&config), base::NotFatalUntil::M130)
394       << "Sets must not contain singleton or orphan";
395   return config;
396 }
397 
398 std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>
NormalizeAdditionSets(const std::vector<base::flat_map<SchemefulSite,FirstPartySetEntry>> & addition_sets) const399 GlobalFirstPartySets::NormalizeAdditionSets(
400     const std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>&
401         addition_sets) const {
402   if (base::ranges::all_of(addition_sets, &SingleSet::empty)) {
403     // Nothing to do.
404     return {};
405   }
406 
407   // Find all the addition sets that intersect with any given public set.
408   base::flat_map<SchemefulSite, base::flat_set<size_t>> addition_set_overlaps;
409   for (size_t set_idx = 0; set_idx < addition_sets.size(); set_idx++) {
410     for (const auto& site_and_entry : addition_sets[set_idx]) {
411       if (const auto entry =
412               FindEntry(site_and_entry.first, /*config=*/nullptr);
413           entry.has_value()) {
414         addition_set_overlaps[entry->primary()].insert(set_idx);
415       }
416     }
417   }
418 
419   // Union together all transitively-overlapping addition sets.
420   AdditionOverlapsUnionFind union_finder(addition_sets.size());
421   for (const auto& [public_site, addition_set_indices] :
422        addition_set_overlaps) {
423     for (size_t representative : addition_set_indices) {
424       union_finder.Union(*addition_set_indices.begin(), representative);
425     }
426   }
427 
428   // Now build the new addition sets, with all transitive overlaps eliminated.
429   std::vector<SingleSet> normalized_additions;
430   for (const auto& [rep, children] : union_finder.SetsMapping()) {
431     SingleSet normalized = addition_sets[rep];
432     const SchemefulSite& rep_primary =
433         addition_sets[rep].begin()->second.primary();
434     for (size_t child_set_idx : children) {
435       for (const auto& child_site_and_entry : addition_sets[child_set_idx]) {
436         bool inserted =
437             normalized
438                 .emplace(child_site_and_entry.first,
439                          FirstPartySetEntry(rep_primary, SiteType::kAssociated,
440                                             std::nullopt))
441                 .second;
442         CHECK(inserted);
443       }
444     }
445     normalized_additions.push_back(normalized);
446   }
447   return normalized_additions;
448 }
449 
ForEachPublicSetEntry(base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntry &)> f) const450 bool GlobalFirstPartySets::ForEachPublicSetEntry(
451     base::FunctionRef<bool(const SchemefulSite&, const FirstPartySetEntry&)> f)
452     const {
453   for (const auto& [site, entry] : entries_) {
454     if (!f(site, entry))
455       return false;
456   }
457   for (const auto& [alias, canonical] : aliases_) {
458     auto it = entries_.find(canonical);
459     CHECK(it != entries_.end());
460     if (!f(alias, it->second))
461       return false;
462   }
463   return true;
464 }
465 
ForEachManualConfigEntry(base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntryOverride &)> f) const466 bool GlobalFirstPartySets::ForEachManualConfigEntry(
467     base::FunctionRef<bool(const SchemefulSite&,
468                            const FirstPartySetEntryOverride&)> f) const {
469   return manual_config_.ForEachCustomizationEntry(f);
470 }
471 
ForEachEffectiveSetEntry(const FirstPartySetsContextConfig & config,base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntry &)> f) const472 bool GlobalFirstPartySets::ForEachEffectiveSetEntry(
473     const FirstPartySetsContextConfig& config,
474     base::FunctionRef<bool(const SchemefulSite&, const FirstPartySetEntry&)> f)
475     const {
476   return ForEachEffectiveSetEntry(&config, f);
477 }
478 
ForEachEffectiveSetEntry(const FirstPartySetsContextConfig * config,base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntry &)> f) const479 bool GlobalFirstPartySets::ForEachEffectiveSetEntry(
480     const FirstPartySetsContextConfig* config,
481     base::FunctionRef<bool(const SchemefulSite&, const FirstPartySetEntry&)> f)
482     const {
483   // Policy sets have highest precedence:
484   if (config != nullptr) {
485     if (!config->ForEachCustomizationEntry(
486             [&](const SchemefulSite& site,
487                 const FirstPartySetEntryOverride& override) {
488               if (!override.IsDeletion())
489                 return f(site, override.GetEntry());
490               return true;
491             })) {
492       return false;
493     }
494   }
495 
496   // Then the manual set:
497   if (!manual_config_.ForEachCustomizationEntry(
498           [&](const SchemefulSite& site,
499               const FirstPartySetEntryOverride& override) {
500             if (!override.IsDeletion() && (!config || !config->Contains(site)))
501               return f(site, override.GetEntry());
502             return true;
503           })) {
504     return false;
505   }
506 
507   // Finally, the public sets.
508   return ForEachPublicSetEntry([&](const SchemefulSite& site,
509                                    const FirstPartySetEntry& entry) {
510     if ((!config || !config->Contains(site)) && !manual_config_.Contains(site))
511       return f(site, entry);
512     return true;
513   });
514 }
515 
ForEachAlias(base::FunctionRef<void (const SchemefulSite &,const SchemefulSite &)> f) const516 void GlobalFirstPartySets::ForEachAlias(
517     base::FunctionRef<void(const SchemefulSite&, const SchemefulSite&)> f)
518     const {
519   for (const auto& [alias, site] : manual_aliases_) {
520     f(alias, site);
521   }
522   for (const auto& [alias, site] : aliases_) {
523     if (manual_config_.Contains(alias)) {
524       continue;
525     }
526     f(alias, site);
527   }
528 }
529 
IsValid(const FirstPartySetsContextConfig * config) const530 bool GlobalFirstPartySets::IsValid(
531     const FirstPartySetsContextConfig* config) const {
532   FirstPartySetsValidator validator;
533   ForEachEffectiveSetEntry(
534       config,
535       [&](const SchemefulSite& site, const FirstPartySetEntry& entry) -> bool {
536         validator.Update(site, entry.primary());
537         return true;
538       });
539 
540   return validator.IsValid();
541 }
542 
operator <<(std::ostream & os,const GlobalFirstPartySets & sets)543 std::ostream& operator<<(std::ostream& os, const GlobalFirstPartySets& sets) {
544   os << "{entries = {";
545   for (const auto& [site, entry] : sets.entries_) {
546     os << "{" << site.Serialize() << ": " << entry << "}, ";
547   }
548   os << "}, aliases = {";
549   for (const auto& [alias, canonical] : sets.aliases_) {
550     os << "{" << alias.Serialize() << ": " << canonical.Serialize() << "}, ";
551   }
552   os << "}, manual_config = {";
553   sets.ForEachManualConfigEntry(
554       [&](const net::SchemefulSite& site,
555           const FirstPartySetEntryOverride& override) {
556         os << "{" << site.Serialize() << ": " << override << "},";
557         return true;
558       });
559   os << "}, manual_aliases = {";
560   for (const auto& [alias, canonical] : sets.manual_aliases_) {
561     os << "{" << alias.Serialize() << ": " << canonical.Serialize() << "}, ";
562   }
563   os << "}}";
564   return os;
565 }
566 
567 }  // namespace net
568