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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
content / browser / browsing_instance.cc [blame]
// Copyright 2012 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/browsing_instance.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/origin_agent_cluster_isolation_state.h"
#include "content/browser/security/coop/coop_related_group.h"
#include "content/browser/site_info.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_or_resource_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
namespace content {
// Start the BrowsingInstance ID counter from 1 to avoid a conflict with the
// invalid BrowsingInstanceId value, which is 0 in its underlying IdType32.
int BrowsingInstance::next_browsing_instance_id_ = 1;
BrowsingInstance::BrowsingInstance(
BrowserContext* browser_context,
const WebExposedIsolationInfo& web_exposed_isolation_info,
bool is_guest,
bool is_fenced,
bool is_fixed_storage_partition,
const scoped_refptr<CoopRelatedGroup>& coop_related_group,
std::optional<url::Origin> common_coop_origin)
: isolation_context_(
BrowsingInstanceId::FromUnsafeValue(next_browsing_instance_id_++),
BrowserOrResourceContext(browser_context),
is_guest,
is_fenced,
OriginAgentClusterIsolationState::CreateForDefaultIsolation(
browser_context)),
active_contents_count_(0u),
default_site_instance_(nullptr),
web_exposed_isolation_info_(web_exposed_isolation_info),
coop_related_group_(coop_related_group),
common_coop_origin_(common_coop_origin),
is_fixed_storage_partition_(is_fixed_storage_partition) {
DCHECK(browser_context);
if (is_guest) {
CHECK(is_fixed_storage_partition);
}
// If we get passed an empty group, build a new one. This is the common case.
if (!coop_related_group_) {
coop_related_group_ =
base::WrapRefCounted<CoopRelatedGroup>(new CoopRelatedGroup(
browser_context, is_guest, is_fenced, is_fixed_storage_partition_));
}
DCHECK(coop_related_group_);
coop_related_group_->RegisterBrowsingInstance(this);
}
BrowserContext* BrowsingInstance::GetBrowserContext() const {
return isolation_context_.browser_or_resource_context().ToBrowserContext();
}
bool BrowsingInstance::HasSiteInstance(const SiteInfo& site_info) {
return base::Contains(site_instance_map_, site_info);
}
scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForURL(
const UrlInfo& url_info,
bool allow_default_instance) {
scoped_refptr<SiteInstanceImpl> site_instance =
GetSiteInstanceForURLHelper(url_info, allow_default_instance);
if (site_instance)
return site_instance;
// No current SiteInstance for this site, so let's create one.
scoped_refptr<SiteInstanceImpl> instance = new SiteInstanceImpl(this);
// Set the site of this new SiteInstance, which will register it with us.
// Some URLs should leave the SiteInstance's site unassigned, though if
// `instance` is for a guest, we should always set the site to ensure that it
// carries guest information contained within SiteInfo.
if (SiteInstanceImpl::ShouldAssignSiteForUrlInfo(url_info) ||
isolation_context_.is_guest()) {
instance->SetSite(url_info);
}
return instance;
}
SiteInfo BrowsingInstance::GetSiteInfoForURL(const UrlInfo& url_info,
bool allow_default_instance) {
scoped_refptr<SiteInstanceImpl> site_instance =
GetSiteInstanceForURLHelper(url_info, allow_default_instance);
if (site_instance)
return site_instance->GetSiteInfo();
return ComputeSiteInfoForURL(url_info);
}
scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForSiteInfo(
const SiteInfo& site_info) {
auto i = site_instance_map_.find(site_info);
if (i != site_instance_map_.end())
return i->second.get();
scoped_refptr<SiteInstanceImpl> instance = new SiteInstanceImpl(this);
instance->SetSite(site_info);
return instance;
}
scoped_refptr<SiteInstanceImpl>
BrowsingInstance::GetCoopRelatedSiteInstanceForURL(
const UrlInfo& url_info,
bool allow_default_instance) {
return coop_related_group_->GetCoopRelatedSiteInstanceForURL(
url_info, allow_default_instance);
}
scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForURLHelper(
const UrlInfo& url_info,
bool allow_default_instance) {
const SiteInfo site_info = ComputeSiteInfoForURL(url_info);
auto i = site_instance_map_.find(site_info);
if (i != site_instance_map_.end())
return i->second.get();
// Check to see if we can use the default SiteInstance for sites that don't
// need to be isolated in their own process.
if (allow_default_instance &&
SiteInstanceImpl::CanBePlacedInDefaultSiteInstance(
isolation_context_, url_info.url, site_info)) {
scoped_refptr<SiteInstanceImpl> site_instance =
default_site_instance_.get();
if (!site_instance) {
site_instance = new SiteInstanceImpl(this);
// Note: |default_site_instance_| will get set inside this call
// via RegisterSiteInstance().
site_instance->SetSiteInfoToDefault(site_info.storage_partition_config());
DCHECK_EQ(default_site_instance_, site_instance.get());
}
// Add |site_info| to the set so we can keep track of all the sites the
// the default SiteInstance has been returned for.
site_instance->AddSiteInfoToDefault(site_info);
return site_instance;
}
return nullptr;
}
void BrowsingInstance::RegisterSiteInstance(SiteInstanceImpl* site_instance) {
DCHECK(site_instance->browsing_instance_.get() == this);
DCHECK(site_instance->HasSite());
// Verify that the SiteInstance's StoragePartitionConfig matches this
// BrowsingInstance's StoragePartitionConfig if it already has one.
const StoragePartitionConfig& storage_partition_config =
site_instance->GetSiteInfo().storage_partition_config();
if (storage_partition_config_.has_value()) {
// We should only use a single StoragePartition within a BrowsingInstance.
// If we're attempting to use multiple, something has gone wrong with the
// logic at upper layers. Similarly, whether this StoragePartition is for
// a guest should remain constant over a BrowsingInstance's lifetime.
CHECK_EQ(storage_partition_config_.value(), storage_partition_config);
CHECK_EQ(isolation_context_.is_guest(), site_instance->IsGuest());
} else {
storage_partition_config_ = storage_partition_config;
}
// Explicitly prevent the default SiteInstance from being added since
// the map is only supposed to contain instances that map to a single site.
if (site_instance->IsDefaultSiteInstance()) {
CHECK(!default_site_instance_);
default_site_instance_ = site_instance;
return;
}
const SiteInfo& site_info = site_instance->GetSiteInfo();
// Only register if we don't have a SiteInstance for this site already.
// It's possible to have two SiteInstances point to the same site if two
// tabs are navigated there at the same time. (We don't call SetSite or
// register them until DidNavigate.) If there is a previously existing
// SiteInstance for this site, we just won't register the new one.
auto i = site_instance_map_.find(site_info);
if (i == site_instance_map_.end()) {
// Not previously registered, so register it.
site_instance_map_[site_info] = site_instance;
}
}
void BrowsingInstance::UnregisterSiteInstance(SiteInstanceImpl* site_instance) {
DCHECK(site_instance->browsing_instance_.get() == this);
DCHECK(site_instance->HasSite());
if (site_instance == default_site_instance_) {
// The last reference to the default SiteInstance is being destroyed.
default_site_instance_ = nullptr;
}
// Only unregister the SiteInstance if it is the same one that is registered
// for the site. (It might have been an unregistered SiteInstance. See the
// comments in RegisterSiteInstance.)
auto i = site_instance_map_.find(site_instance->GetSiteInfo());
if (i != site_instance_map_.end() && i->second == site_instance) {
// Matches, so erase it.
site_instance_map_.erase(i);
}
}
// static
BrowsingInstanceId BrowsingInstance::NextBrowsingInstanceId() {
return BrowsingInstanceId::FromUnsafeValue(next_browsing_instance_id_);
}
BrowsingInstance::~BrowsingInstance() {
// We should only be deleted when all of the SiteInstances that refer to
// us are gone.
DCHECK(site_instance_map_.empty());
DCHECK_EQ(0u, active_contents_count_);
DCHECK(!default_site_instance_);
// Remove any origin isolation opt-ins related to this instance.
ChildProcessSecurityPolicyImpl* policy =
ChildProcessSecurityPolicyImpl::GetInstance();
policy->RemoveOptInIsolatedOriginsForBrowsingInstance(
isolation_context_.browsing_instance_id());
coop_related_group_->UnregisterBrowsingInstance(this);
}
SiteInfo BrowsingInstance::ComputeSiteInfoForURL(
const UrlInfo& url_info) const {
// If a StoragePartitionConfig is specified in both `url_info` and this
// BrowsingInstance, make sure they match.
if (url_info.storage_partition_config.has_value() &&
storage_partition_config_.has_value()) {
CHECK_EQ(storage_partition_config_.value(),
url_info.storage_partition_config.value());
}
// If no StoragePartitionConfig was set in `url_info`, create a new UrlInfo
// that inherit's this BrowsingInstance's StoragePartitionConfig.
UrlInfo url_info_with_partition =
url_info.storage_partition_config.has_value()
? url_info
: UrlInfo(UrlInfoInit(url_info).WithStoragePartitionConfig(
storage_partition_config_));
// The WebExposedIsolationInfos must be compatible for this function to make
// sense.
DCHECK(WebExposedIsolationInfo::AreCompatible(
url_info.web_exposed_isolation_info, web_exposed_isolation_info_));
// If the passed in UrlInfo has a null WebExposedIsolationInfo, meaning that
// it is compatible with any isolation state, we reuse the isolation state of
// the BrowsingInstance.
url_info_with_partition.web_exposed_isolation_info =
url_info.web_exposed_isolation_info.value_or(web_exposed_isolation_info_);
return SiteInfo::Create(isolation_context_, url_info_with_partition);
}
int BrowsingInstance::EstimateOriginAgentClusterOverhead() {
DCHECK(SiteIsolationPolicy::IsProcessIsolationForOriginAgentClusterEnabled());
std::set<SiteInfo> site_info_set;
std::set<SiteInfo> site_info_set_no_oac;
// The following computes an estimate of how many additional processes have
// been created to deal with OriginAgentCluster (OAC) headers. When OAC
// headers forces an additional process, that corresponds to the SiteInfo's
// is_origin_keyed_ flag being set. To compute the estimate, we use the set of
// unique SiteInstances (each represented by a unique SiteInfo) in each
// BrowsingInstance as a proxy for the set of different RenderProcesses. We
// start with the total count of SiteInfos, then we create a new set of
// SiteInfos created by resetting the is_origin_keyed_ flag on each of the
// SiteInfos (along with any corresponding adjustments to the site_url_ and
// process_lock_url_ to reflect the possible conversion from origin to site).
// The assumption here is that SiteInfos that forced a new process due to OAC
// may no longer be unique once these values are reset, and as such the new
// set will have less elements than the original set, with the difference
// being the count of extra SiteInstances due to OAC. There are cases where
// ignoring the OAC header would still result in an extra process, e.g. when
// the SiteInfo's origin appears in the command-line origin isolation list.
//
// The estimate is computed using several simplifying assumptions:
// 1) We only consider HTTPS SiteInfos to compute the additional SiteInfos.
// This assumption should generally be valid, since we don't apply
// is_origin_keyed_ to non-HTTPS schemes.
// 2) We assume that SiteInfos from multiple BrowsingInstances aren't
// coalesced into a single RenderProcess. While this isn't true in general,
// it is difficult in practice to account for, so we don't try to.
for (auto& entry : site_instance_map_) {
const SiteInfo& site_info = entry.first;
GURL process_lock_url = site_info.process_lock_url();
if (!process_lock_url.SchemeIs(url::kHttpsScheme))
continue;
site_info_set.insert(site_info);
site_info_set_no_oac.insert(
site_info.GetNonOriginKeyedEquivalentForMetrics(isolation_context_));
}
DCHECK_GE(site_info_set.size(), site_info_set_no_oac.size());
int result = site_info_set.size() - site_info_set_no_oac.size();
return result;
}
size_t BrowsingInstance::GetCoopRelatedGroupActiveContentsCount() {
return coop_related_group_->active_contents_count();
}
void BrowsingInstance::IncrementActiveContentsCount() {
active_contents_count_++;
coop_related_group_->increment_active_contents_count();
}
void BrowsingInstance::DecrementActiveContentsCount() {
DCHECK_LT(0u, active_contents_count_);
active_contents_count_--;
coop_related_group_->decrement_active_contents_count();
}
} // namespace content