1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ProcessIsolation.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/dom/BrowsingContextGroup.h"
11 #include "mozilla/dom/CanonicalBrowsingContext.h"
12 #include "mozilla/dom/ContentChild.h"
13 #include "mozilla/dom/ContentParent.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/dom/RemoteType.h"
16 #include "mozilla/dom/WindowGlobalParent.h"
17 #include "mozilla/extensions/WebExtensionPolicy.h"
18 #include "mozilla/BasePrincipal.h"
19 #include "mozilla/ClearOnShutdown.h"
20 #include "mozilla/ContentPrincipal.h"
21 #include "mozilla/ExtensionPolicyService.h"
22 #include "mozilla/Logging.h"
23 #include "mozilla/NullPrincipal.h"
24 #include "mozilla/PermissionManager.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/RefPtr.h"
27 #include "mozilla/StaticPrefs_browser.h"
28 #include "mozilla/StaticPrefs_fission.h"
29 #include "mozilla/StaticPtr.h"
30 #include "nsAboutProtocolUtils.h"
31 #include "nsDocShell.h"
33 #include "nsIChromeRegistry.h"
34 #include "nsIHttpChannel.h"
35 #include "nsIHttpChannelInternal.h"
36 #include "nsIProtocolHandler.h"
37 #include "nsIXULRuntime.h"
38 #include "nsNetUtil.h"
39 #include "nsServiceManagerUtils.h"
40 #include "nsSHistory.h"
41 #include "nsURLHelper.h"
43 namespace mozilla::dom
{
45 mozilla::LazyLogModule gProcessIsolationLog
{"ProcessIsolation"};
49 // Strategy used to determine whether or not a particular site should load into
50 // a webIsolated content process. The particular strategy chosen is controlled
51 // by the `fission.webContentIsolationStrategy` pref, which must hold one of the
53 enum class WebContentIsolationStrategy
: uint32_t {
54 // All web content is loaded into a shared `web` content process. This is
55 // similar to the non-Fission behaviour, however remote subframes may still
56 // be used for sites with special isolation behaviour, such as extension or
57 // mozillaweb content processes.
59 // Web content is always isolated into its own `webIsolated` content process
60 // based on site-origin, and will only load in a shared `web` content process
61 // if site-origin could not be determined.
62 IsolateEverything
= 1,
63 // Only isolates web content loaded by sites which are considered "high
64 // value". A site is considered "high value" if it has been granted a
65 // `highValue*` permission by the permission manager, which is done in
66 // response to certain actions.
71 * Helper class for caching the result of splitting prefs which are represented
72 * as a comma-separated list of strings.
74 struct CommaSeparatedPref
{
76 explicit constexpr CommaSeparatedPref(nsLiteralCString aPrefName
)
77 : mPrefName(aPrefName
) {}
82 nsAutoCString prefValue
;
83 if (NS_SUCCEEDED(Preferences::GetCString(mPrefName
.get(), prefValue
))) {
84 for (const auto& value
:
85 nsCCharSeparatedTokenizer(prefValue
, ',').ToRange()) {
86 mValues
->EmplaceBack(value
);
92 const nsTArray
<nsCString
>& Get() {
94 mValues
= new nsTArray
<nsCString
>;
95 Preferences::RegisterCallbackAndCall(
96 [](const char*, void* aData
) {
97 static_cast<CommaSeparatedPref
*>(aData
)->OnChange();
100 RunOnShutdown([this] {
101 delete this->mValues
;
102 this->mValues
= nullptr;
108 auto begin() { return Get().cbegin(); }
109 auto end() { return Get().cend(); }
112 nsLiteralCString mPrefName
;
113 nsTArray
<nsCString
>* MOZ_OWNING_REF mValues
= nullptr;
116 CommaSeparatedPref sSeparatedMozillaDomains
{
117 "browser.tabs.remote.separatedMozillaDomains"_ns
};
120 * Certain URIs have special isolation behaviour, and need to be loaded within
121 * specific process types.
123 enum class IsolationBehavior
{
124 // This URI loads web content and should be treated as a content load, being
125 // isolated based on the response principal if enabled.
127 // Forcibly load in a process with the "web" remote type. This will ignore the
128 // response principal completely.
129 // This is generally reserved for internal documents which are loaded in
130 // content, but not in the privilegedabout content process.
132 // Load this URI in the privileged about content process.
134 // Load this URI in the extension process.
136 // Load this URI in the file content process.
138 // Load this URI in the priviliged mozilla content process.
140 // Load this URI explicitly in the parent process.
142 // Load this URI wherever the browsing context is currently loaded. This is
143 // generally used for error pages.
145 // May only be returned for subframes. Inherits the remote type of the parent
146 // document which is embedding this document.
148 // Special case for the `about:reader` URI which should be loaded in the same
149 // process which would be used for the "url" query parameter.
151 // There was a fatal error, and the load should be aborted.
156 * Returns a static string with the name of the given isolation behaviour. For
157 * use in logging code.
159 static const char* IsolationBehaviorName(IsolationBehavior aBehavior
) {
161 case IsolationBehavior::WebContent
:
163 case IsolationBehavior::ForceWebRemoteType
:
164 return "ForceWebRemoteType";
165 case IsolationBehavior::PrivilegedAbout
:
166 return "PrivilegedAbout";
167 case IsolationBehavior::Extension
:
169 case IsolationBehavior::File
:
171 case IsolationBehavior::PrivilegedMozilla
:
172 return "PrivilegedMozilla";
173 case IsolationBehavior::Parent
:
175 case IsolationBehavior::Anywhere
:
177 case IsolationBehavior::Inherit
:
179 case IsolationBehavior::AboutReader
:
180 return "AboutReader";
181 case IsolationBehavior::Error
:
189 * Returns a static string with the name of the given worker kind. For use in
192 static const char* WorkerKindName(WorkerKind aWorkerKind
) {
193 switch (aWorkerKind
) {
194 case WorkerKindDedicated
:
196 case WorkerKindShared
:
198 case WorkerKindService
:
206 * Check if a given URI has specialized process isolation behaviour, such as
207 * needing to be loaded within a specific type of content process.
209 * When handling a navigation, this method will be called twice: first with the
210 * channel's creation URI, and then it will be called with a result principal's
213 static IsolationBehavior
IsolationBehaviorForURI(nsIURI
* aURI
, bool aIsSubframe
,
214 bool aForChannelCreationURI
) {
215 nsAutoCString scheme
;
216 MOZ_ALWAYS_SUCCEEDS(aURI
->GetScheme(scheme
));
218 if (scheme
== "chrome"_ns
) {
219 // `chrome://` URIs are always loaded in the parent process, unless they
220 // have opted in to loading in a content process. This is currently only
223 // FIXME: These flags should be removed from `chrome` URIs at some point.
224 nsCOMPtr
<nsIXULChromeRegistry
> chromeReg
=
225 do_GetService("@mozilla.org/chrome/chrome-registry;1");
226 bool mustLoadRemotely
= false;
227 if (NS_SUCCEEDED(chromeReg
->MustLoadURLRemotely(aURI
, &mustLoadRemotely
)) &&
229 return IsolationBehavior::ForceWebRemoteType
;
231 bool canLoadRemotely
= false;
232 if (NS_SUCCEEDED(chromeReg
->CanLoadURLRemotely(aURI
, &canLoadRemotely
)) &&
234 return IsolationBehavior::Anywhere
;
236 return IsolationBehavior::Parent
;
239 if (scheme
== "about"_ns
) {
241 MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI
, path
));
243 // The `about:blank` and `about:srcdoc` pages are loaded by normal web
244 // content, and should be allocated processes based on their simple content
246 if (path
== "blank"_ns
|| path
== "srcdoc"_ns
) {
247 MOZ_ASSERT(NS_IsContentAccessibleAboutURI(aURI
));
248 return IsolationBehavior::WebContent
;
251 MOZ_ASSERT(!NS_IsContentAccessibleAboutURI(aURI
));
252 // If we're loading an `about:reader` URI, perform isolation based on the
253 // principal of the URI being loaded.
254 if (path
== "reader"_ns
&& aForChannelCreationURI
) {
255 return IsolationBehavior::AboutReader
;
258 // Otherwise, we're going to be loading an about: page. Consult the module.
259 nsCOMPtr
<nsIAboutModule
> aboutModule
;
260 if (NS_FAILED(NS_GetAboutModule(aURI
, getter_AddRefs(aboutModule
))) ||
262 // If we don't know of an about: module for this load, it's going to end
263 // up being a network error. Allow the load to finish as normal.
264 return IsolationBehavior::WebContent
;
267 // NOTE: about modules can be implemented in JS, so this may run script, and
268 // therefore can spuriously fail.
270 if (NS_FAILED(aboutModule
->GetURIFlags(aURI
, &flags
))) {
272 "nsIAboutModule::GetURIFlags unexpectedly failed. Abort the load");
273 return IsolationBehavior::Error
;
276 if (flags
& nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS
) {
277 return IsolationBehavior::Extension
;
280 if (flags
& nsIAboutModule::URI_MUST_LOAD_IN_CHILD
) {
281 if (flags
& nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS
) {
282 return IsolationBehavior::PrivilegedAbout
;
284 return IsolationBehavior::ForceWebRemoteType
;
287 if (flags
& nsIAboutModule::URI_CAN_LOAD_IN_CHILD
) {
288 return IsolationBehavior::Anywhere
;
291 return IsolationBehavior::Parent
;
294 // If the test-only `dataUriInDefaultWebProcess` pref is enabled, dump all
295 // `data:` URIs in a "web" content process, rather than loading them in
296 // content processes based on their precursor origins.
297 if (StaticPrefs::browser_tabs_remote_dataUriInDefaultWebProcess() &&
298 scheme
== "data"_ns
) {
299 return IsolationBehavior::ForceWebRemoteType
;
302 // Make sure to unwrap nested URIs before we early return for channel creation
303 // URI. The checks past this point are intended to operate on the principal,
304 // which has it's origin constructed from the innermost URI.
305 nsCOMPtr
<nsIURI
> inner
;
306 if (nsCOMPtr
<nsINestedURI
> nested
= do_QueryInterface(aURI
);
307 nested
&& NS_SUCCEEDED(nested
->GetInnerURI(getter_AddRefs(inner
)))) {
308 return IsolationBehaviorForURI(inner
, aIsSubframe
, aForChannelCreationURI
);
311 // If we're doing the initial check based on the channel creation URI, stop
312 // here as we want to only perform the following checks on the true channel
314 if (aForChannelCreationURI
) {
315 return IsolationBehavior::WebContent
;
318 // Protocols used by Thunderbird to display email messages.
319 if (scheme
== "imap"_ns
|| scheme
== "mailbox"_ns
|| scheme
== "news"_ns
||
320 scheme
== "nntp"_ns
|| scheme
== "snews"_ns
) {
321 return IsolationBehavior::Parent
;
324 // There is more handling for extension content processes in the caller, but
325 // they should load in an extension content process unless we're loading a
327 if (scheme
== "moz-extension"_ns
) {
329 // As a temporary measure, extension iframes must be loaded within the
330 // same process as their parent document.
331 return IsolationBehavior::Inherit
;
333 return IsolationBehavior::Extension
;
336 if (scheme
== "file"_ns
) {
337 return IsolationBehavior::File
;
340 // Check if the URI is listed as a privileged mozilla content process.
341 if (scheme
== "https"_ns
&&
343 browser_tabs_remote_separatePrivilegedMozillaWebContentProcess()) {
345 if (NS_SUCCEEDED(aURI
->GetAsciiHost(host
))) {
346 // This code is duplicated in E10SUtils.sys.mjs, please update both
347 for (const auto& separatedDomain
: sSeparatedMozillaDomains
) {
348 // If the domain exactly matches our host, or our host ends with "." +
349 // separatedDomain, we consider it matching.
350 if (separatedDomain
== host
||
351 (separatedDomain
.Length() < host
.Length() &&
352 host
.CharAt(host
.Length() - separatedDomain
.Length() - 1) == '.' &&
353 StringEndsWith(host
, separatedDomain
))) {
354 return IsolationBehavior::PrivilegedMozilla
;
360 nsCOMPtr
<nsIScriptSecurityManager
> secMan
=
361 nsContentUtils::GetSecurityManager();
362 bool inFileURIAllowList
= false;
363 if (NS_SUCCEEDED(secMan
->InFileURIAllowlist(aURI
, &inFileURIAllowList
)) &&
364 inFileURIAllowList
) {
365 return IsolationBehavior::File
;
368 return IsolationBehavior::WebContent
;
372 * Helper method for logging the origin of a principal as a string.
374 static nsAutoCString
OriginString(nsIPrincipal
* aPrincipal
) {
375 nsAutoCString origin
;
376 aPrincipal
->GetOrigin(origin
);
381 * Trim the OriginAttributes from aPrincipal, and use it to create a
382 * OriginSuffix string appropriate to use within a remoteType string.
384 static nsAutoCString
OriginSuffixForRemoteType(nsIPrincipal
* aPrincipal
) {
385 nsAutoCString originSuffix
;
386 OriginAttributes attrs
= aPrincipal
->OriginAttributesRef();
387 attrs
.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN
|
388 OriginAttributes::STRIP_PARITION_KEY
);
389 attrs
.CreateSuffix(originSuffix
);
394 * Given an about:reader URI, extract the "url" query parameter, and use it to
395 * construct a principal which should be used for process selection.
397 static already_AddRefed
<BasePrincipal
> GetAboutReaderURLPrincipal(
398 nsIURI
* aURI
, const OriginAttributes
& aAttrs
) {
400 MOZ_ASSERT(aURI
->SchemeIs("about"));
402 MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI
, path
));
403 MOZ_ASSERT(path
== "reader"_ns
);
407 MOZ_ALWAYS_SUCCEEDS(aURI
->GetQuery(query
));
409 // Extract the "url" parameter from the `about:reader`'s query parameters,
410 // and recover a content principal from it.
411 nsAutoCString readerSpec
;
412 if (URLParams::Extract(query
, "url"_ns
, readerSpec
)) {
413 nsCOMPtr
<nsIURI
> readerUri
;
414 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri
), readerSpec
))) {
415 return BasePrincipal::CreateContentPrincipal(readerUri
, aAttrs
);
422 * Returns `true` if loads for this site should be isolated on a per-site basis.
423 * If `aTopBC` is nullptr, this is being called to check if a shared or service
424 * worker should be isolated.
426 static bool ShouldIsolateSite(nsIPrincipal
* aPrincipal
,
427 bool aUseRemoteSubframes
) {
428 // If Fission is disabled, we never want to isolate. We check the toplevel BC
429 // if it's available, or the global pref if checking for shared or service
431 if (!aUseRemoteSubframes
) {
435 // non-content principals currently can't have webIsolated remote types
436 // assigned to them, so should not be isolated.
437 if (!aPrincipal
->GetIsContentPrincipal()) {
441 switch (WebContentIsolationStrategy(
442 StaticPrefs::fission_webContentIsolationStrategy())) {
443 case WebContentIsolationStrategy::IsolateNothing
:
444 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
445 ("Not isolating '%s' as isolation is disabled",
446 OriginString(aPrincipal
).get()));
448 case WebContentIsolationStrategy::IsolateEverything
:
449 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
450 ("Isolating '%s' as isolation is enabled for all sites",
451 OriginString(aPrincipal
).get()));
453 case WebContentIsolationStrategy::IsolateHighValue
: {
454 RefPtr
<PermissionManager
> perms
= PermissionManager::GetInstance();
455 if (NS_WARN_IF(!perms
)) {
456 // If we somehow have no permission manager, fall back to the safest
457 // option, and try to isolate.
458 MOZ_ASSERT_UNREACHABLE("Permission manager is missing");
462 static constexpr nsLiteralCString kHighValuePermissions
[] = {
463 mozilla::dom::kHighValueCOOPPermission
,
464 mozilla::dom::kHighValueHasSavedLoginPermission
,
465 mozilla::dom::kHighValueIsLoggedInPermission
,
468 for (const auto& type
: kHighValuePermissions
) {
469 uint32_t permission
= nsIPermissionManager::UNKNOWN_ACTION
;
470 if (NS_SUCCEEDED(perms
->TestPermissionFromPrincipal(aPrincipal
, type
,
472 permission
== nsIPermissionManager::ALLOW_ACTION
) {
473 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
474 ("Isolating '%s' due to high-value permission '%s'",
475 OriginString(aPrincipal
).get(), type
.get()));
479 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
480 ("Not isolating '%s' as it is not high-value",
481 OriginString(aPrincipal
).get()));
485 // An invalid pref value was used. Fall back to the safest option and
486 // isolate everything.
487 NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy");
488 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
489 ("Isolating '%s' due to unknown strategy pref value",
490 OriginString(aPrincipal
).get()));
495 static Result
<nsCString
, nsresult
> SpecialBehaviorRemoteType(
496 IsolationBehavior aBehavior
, const nsACString
& aCurrentRemoteType
,
497 WindowGlobalParent
* aParentWindow
) {
499 case IsolationBehavior::ForceWebRemoteType
:
500 return {WEB_REMOTE_TYPE
};
501 case IsolationBehavior::PrivilegedAbout
:
502 // The privileged about: content process cannot be disabled, as it
503 // causes various actors to break.
504 return {PRIVILEGEDABOUT_REMOTE_TYPE
};
505 case IsolationBehavior::Extension
:
506 if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
507 return {EXTENSION_REMOTE_TYPE
};
509 return {NOT_REMOTE_TYPE
};
510 case IsolationBehavior::File
:
511 if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
512 return {FILE_REMOTE_TYPE
};
514 return {WEB_REMOTE_TYPE
};
515 case IsolationBehavior::PrivilegedMozilla
:
516 return {PRIVILEGEDMOZILLA_REMOTE_TYPE
};
517 case IsolationBehavior::Parent
:
518 return {NOT_REMOTE_TYPE
};
519 case IsolationBehavior::Anywhere
:
520 return {nsCString(aCurrentRemoteType
)};
521 case IsolationBehavior::Inherit
:
522 MOZ_DIAGNOSTIC_ASSERT(aParentWindow
);
523 return {nsCString(aParentWindow
->GetRemoteType())};
525 case IsolationBehavior::Error
:
526 return Err(NS_ERROR_UNEXPECTED
);
529 MOZ_ASSERT_UNREACHABLE();
530 return Err(NS_ERROR_UNEXPECTED
);
534 enum class WebProcessType
{
542 Result
<NavigationIsolationOptions
, nsresult
> IsolationOptionsForNavigation(
543 CanonicalBrowsingContext
* aTopBC
, WindowGlobalParent
* aParentWindow
,
544 nsIURI
* aChannelCreationURI
, nsIChannel
* aChannel
,
545 const nsACString
& aCurrentRemoteType
, bool aHasCOOPMismatch
,
546 bool aForNewTab
, uint32_t aLoadStateLoadType
,
547 const Maybe
<uint64_t>& aChannelId
,
548 const Maybe
<nsCString
>& aRemoteTypeOverride
) {
549 // Get the final principal, used to select which process to load into.
550 nsCOMPtr
<nsIPrincipal
> resultPrincipal
;
551 nsresult rv
= nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
552 aChannel
, getter_AddRefs(resultPrincipal
));
554 MOZ_LOG(gProcessIsolationLog
, LogLevel::Error
,
555 ("failed to get channel result principal"));
560 gProcessIsolationLog
, LogLevel::Verbose
,
561 ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s",
562 OriginString(resultPrincipal
).get(),
563 aChannelCreationURI
->GetSpecOrDefault().get(),
564 aParentWindow
? aParentWindow
->GetDocumentURI()->GetSpecOrDefault().get()
567 // If we're loading a null principal, we can't easily make a process
568 // selection decision off ot it. Instead, we'll use our null principal's
569 // precursor principal to make process selection decisions.
570 bool isNullPrincipalPrecursor
= false;
571 nsCOMPtr
<nsIPrincipal
> resultOrPrecursor(resultPrincipal
);
572 if (nsCOMPtr
<nsIPrincipal
> precursor
=
573 resultOrPrecursor
->GetPrecursorPrincipal()) {
574 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
575 ("using null principal precursor origin %s",
576 OriginString(precursor
).get()));
577 resultOrPrecursor
= precursor
;
578 isNullPrincipalPrecursor
= true;
581 NavigationIsolationOptions options
;
582 options
.mReplaceBrowsingContext
= aHasCOOPMismatch
;
584 // Check if this load has an explicit remote type override. This is used to
585 // perform an about:blank load within a specific content process.
586 if (aRemoteTypeOverride
) {
587 MOZ_DIAGNOSTIC_ASSERT(
588 NS_IsAboutBlank(aChannelCreationURI
),
589 "Should only have aRemoteTypeOverride for about:blank URIs");
590 if (NS_WARN_IF(!resultPrincipal
->GetIsNullPrincipal())) {
591 MOZ_LOG(gProcessIsolationLog
, LogLevel::Error
,
592 ("invalid remote type override on non-null principal"));
593 return Err(NS_ERROR_DOM_SECURITY_ERR
);
596 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
597 ("using remote type override (%s) for load",
598 aRemoteTypeOverride
->get()));
599 options
.mRemoteType
= *aRemoteTypeOverride
;
603 // First, check for any special cases which should be handled using the
604 // channel creation URI, and handle them.
605 auto behavior
= IsolationBehaviorForURI(aChannelCreationURI
, aParentWindow
,
606 /* aForChannelCreationURI */ true);
607 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
608 ("Channel Creation Isolation Behavior: %s",
609 IsolationBehaviorName(behavior
)));
611 // In the about:reader special case, we want to fetch the relevant information
612 // from the URI, an then treat it as a normal web content load.
613 if (behavior
== IsolationBehavior::AboutReader
) {
614 if (RefPtr
<BasePrincipal
> readerURIPrincipal
= GetAboutReaderURLPrincipal(
615 aChannelCreationURI
, resultOrPrecursor
->OriginAttributesRef())) {
616 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
617 ("using about:reader's url origin %s",
618 OriginString(readerURIPrincipal
).get()));
619 resultOrPrecursor
= readerURIPrincipal
;
621 behavior
= IsolationBehavior::WebContent
;
622 // If loading an about:reader page in a BrowsingContext which shares a
623 // BrowsingContextGroup with other toplevel documents, replace the
624 // BrowsingContext to destroy any references.
626 // With SHIP we can apply this to all about:reader loads, but otherwise
627 // do it at least where there are opener/group relationships.
628 if (mozilla::SessionHistoryInParent() ||
629 aTopBC
->Group()->Toplevels().Length() > 1) {
630 options
.mReplaceBrowsingContext
= true;
634 // If we're running in a test which is requesting that system-triggered
635 // about:blank documents load within the current process, override the
636 // behaviour for loads which meet the requirements.
637 if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() &&
638 NS_IsAboutBlank(aChannelCreationURI
)) {
639 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
640 if (loadInfo
->TriggeringPrincipal()->IsSystemPrincipal() &&
641 resultOrPrecursor
->GetIsNullPrincipal()) {
642 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
643 ("Forcing system-principal triggered about:blank load to "
644 "complete in the current process"));
645 behavior
= IsolationBehavior::Anywhere
;
649 #ifdef MOZ_WIDGET_ANDROID
650 // If we're loading an error page on android, it must complete within the same
651 // process as the errored page load would complete in due to code expecting
652 // that behavior. See bug 1673763.
653 if (aLoadStateLoadType
== LOAD_ERROR_PAGE
) {
654 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
655 ("Forcing error page load to complete in the current process"));
656 behavior
= IsolationBehavior::Anywhere
;
660 // If we're loading for a specific extension, we'll need to perform a
661 // BCG-switching load to get our toplevel extension window in the correct
662 // BrowsingContextGroup.
663 if (auto* addonPolicy
=
664 BasePrincipal::Cast(resultOrPrecursor
)->AddonPolicy()) {
666 // As a temporary measure, extension iframes must be loaded within the
667 // same process as their parent document.
668 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
669 ("Loading extension subframe in same process as parent"));
670 behavior
= IsolationBehavior::Inherit
;
673 gProcessIsolationLog
, LogLevel::Verbose
,
674 ("Found extension frame with addon policy. Will use group id %" PRIx64
675 " (currentId: %" PRIx64
")",
676 addonPolicy
->GetBrowsingContextGroupId(), aTopBC
->Group()->Id()));
677 behavior
= IsolationBehavior::Extension
;
678 if (aTopBC
->Group()->Id() != addonPolicy
->GetBrowsingContextGroupId()) {
679 options
.mReplaceBrowsingContext
= true;
680 options
.mSpecificGroupId
= addonPolicy
->GetBrowsingContextGroupId();
685 // Do a second run of `GetIsolationBehavior`, this time using the
686 // principal's URI to handle additional special cases such as the file and
687 // privilegedmozilla content process.
688 if (behavior
== IsolationBehavior::WebContent
) {
689 if (resultOrPrecursor
->IsSystemPrincipal()) {
690 // We're loading something with a system principal which isn't caught in
691 // one of our other edge-cases. If the load started in the parent process,
692 // and it's safe for it to end in the parent process, we should finish the
694 bool isUIResource
= false;
695 if (aCurrentRemoteType
.IsEmpty() &&
696 (aChannelCreationURI
->SchemeIs("about") ||
697 (NS_SUCCEEDED(NS_URIChainHasFlags(
698 aChannelCreationURI
, nsIProtocolHandler::URI_IS_UI_RESOURCE
,
701 behavior
= IsolationBehavior::Parent
;
703 // In general, we don't want to load documents with a system principal
704 // in a content process, however we need to in some cases, such as when
705 // loading blob: URLs created by system code. We can force the load to
706 // finish in a content process instead.
707 behavior
= IsolationBehavior::ForceWebRemoteType
;
709 } else if (nsCOMPtr
<nsIURI
> principalURI
= resultOrPrecursor
->GetURI()) {
710 behavior
= IsolationBehaviorForURI(principalURI
, aParentWindow
,
711 /* aForChannelCreationURI */ false);
715 // If we're currently loaded in the extension process, and are going to switch
716 // to some other remote type, make sure we leave the extension's BCG which we
717 // may have entered earlier to separate extension and non-extension BCGs from
719 if (!aParentWindow
&& aCurrentRemoteType
== EXTENSION_REMOTE_TYPE
&&
720 behavior
!= IsolationBehavior::Extension
&&
721 behavior
!= IsolationBehavior::Anywhere
) {
722 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
723 ("Forcing BC replacement to leave extension BrowsingContextGroup "
724 "%" PRIx64
" on navigation",
725 aTopBC
->Group()->Id()));
726 options
.mReplaceBrowsingContext
= true;
729 // We don't want to load documents with sandboxed null principals, like
730 // `data:` URIs, in the parent process, even if they were created by a
731 // document which would otherwise be loaded in the parent process.
732 if (behavior
== IsolationBehavior::Parent
&& isNullPrincipalPrecursor
) {
733 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
734 ("Ensuring sandboxed null-principal load doesn't occur in the "
736 behavior
= IsolationBehavior::ForceWebRemoteType
;
740 gProcessIsolationLog
, LogLevel::Debug
,
741 ("Using IsolationBehavior %s for %s (original uri %s)",
742 IsolationBehaviorName(behavior
), OriginString(resultOrPrecursor
).get(),
743 aChannelCreationURI
->GetSpecOrDefault().get()));
745 // Check if we can put the previous document into the BFCache.
746 if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 &&
747 !aForNewTab
&& !aParentWindow
&& !aTopBC
->HadOriginalOpener() &&
748 behavior
!= IsolationBehavior::Parent
&&
749 (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() ||
750 behavior
!= IsolationBehavior::Extension
) &&
751 !aCurrentRemoteType
.IsEmpty() &&
752 aTopBC
->GetHasLoadedNonInitialDocument() &&
753 (aLoadStateLoadType
== LOAD_NORMAL
||
754 aLoadStateLoadType
== LOAD_HISTORY
|| aLoadStateLoadType
== LOAD_LINK
||
755 aLoadStateLoadType
== LOAD_STOP_CONTENT
||
756 aLoadStateLoadType
== LOAD_STOP_CONTENT_AND_REPLACE
) &&
757 (!aTopBC
->GetActiveSessionHistoryEntry() ||
758 aTopBC
->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) {
759 if (nsCOMPtr
<nsIURI
> uri
= aTopBC
->GetCurrentURI()) {
760 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
761 ("current uri: %s", uri
->GetSpecOrDefault().get()));
763 options
.mTryUseBFCache
=
764 aTopBC
->AllowedInBFCache(aChannelId
, aChannelCreationURI
);
765 if (options
.mTryUseBFCache
) {
766 options
.mReplaceBrowsingContext
= true;
767 options
.mActiveSessionHistoryEntry
=
768 aTopBC
->GetActiveSessionHistoryEntry();
772 // If the load has any special remote type handling, do so at this point.
773 if (behavior
!= IsolationBehavior::WebContent
) {
776 SpecialBehaviorRemoteType(behavior
, aCurrentRemoteType
, aParentWindow
));
778 if (options
.mRemoteType
!= aCurrentRemoteType
&&
779 (options
.mRemoteType
.IsEmpty() || aCurrentRemoteType
.IsEmpty())) {
780 options
.mReplaceBrowsingContext
= true;
784 gProcessIsolationLog
, LogLevel::Debug
,
785 ("Selecting specific remote type (%s) due to a special case isolation "
787 options
.mRemoteType
.get(), IsolationBehaviorName(behavior
)));
791 // At this point we're definitely not going to be loading in the parent
792 // process anymore, so we're definitely going to be replacing BrowsingContext
793 // if we're in the parent process.
794 if (aCurrentRemoteType
.IsEmpty()) {
795 MOZ_ASSERT(!aParentWindow
);
796 options
.mReplaceBrowsingContext
= true;
799 nsAutoCString siteOriginNoSuffix
;
800 MOZ_TRY(resultOrPrecursor
->GetSiteOriginNoSuffix(siteOriginNoSuffix
));
802 // Check if we've already loaded a document with the given principal in some
803 // content process. We want to finish the load in the same process in that
806 // The exception to that is with extension loads and the system principal,
807 // where we may have multiple documents with the same principal in different
808 // processes. Those have been handled above, and will not be reaching here.
810 // If we're doing a replace load or opening a new tab, we won't be staying in
811 // the same BrowsingContextGroup, so ignore this step.
812 if (!options
.mReplaceBrowsingContext
&& !aForNewTab
) {
813 // Helper for efficiently determining if a given origin is same-site. This
814 // will attempt to do a fast equality check, and will only fall back to
815 // computing the site-origin for content principals.
816 auto principalIsSameSite
= [&](nsIPrincipal
* aDocumentPrincipal
) -> bool {
817 // If we're working with a null principal with a precursor, compare
818 // precursors, as `resultOrPrecursor` has already been stripped to its
820 nsCOMPtr
<nsIPrincipal
> documentPrincipal(aDocumentPrincipal
);
821 if (nsCOMPtr
<nsIPrincipal
> precursor
=
822 documentPrincipal
->GetPrecursorPrincipal()) {
823 documentPrincipal
= precursor
;
826 // First, attempt to use `Equals` to compare principals, and if that
827 // fails compare siteOrigins. Only compare siteOrigin for content
828 // principals, as non-content principals will never have siteOrigin !=
830 nsAutoCString documentSiteOrigin
;
831 return resultOrPrecursor
->Equals(documentPrincipal
) ||
832 (documentPrincipal
->GetIsContentPrincipal() &&
833 resultOrPrecursor
->GetIsContentPrincipal() &&
834 NS_SUCCEEDED(documentPrincipal
->GetSiteOriginNoSuffix(
835 documentSiteOrigin
)) &&
836 documentSiteOrigin
== siteOriginNoSuffix
);
839 // XXX: Consider also checking in-flight process switches to see if any have
840 // matching principals?
841 AutoTArray
<RefPtr
<BrowsingContext
>, 8> contexts
;
842 aTopBC
->Group()->GetToplevels(contexts
);
843 while (!contexts
.IsEmpty()) {
844 auto bc
= contexts
.PopLastElement();
845 for (const auto& wc
: bc
->GetWindowContexts()) {
846 WindowGlobalParent
* wgp
= wc
->Canonical();
848 // Check if this WindowGlobalParent has the given resultPrincipal, and
849 // if it does, we need to load in that process.
850 if (!wgp
->GetRemoteType().IsEmpty() &&
851 principalIsSameSite(wgp
->DocumentPrincipal())) {
852 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
853 ("Found existing frame with matching principal "
854 "(remoteType:(%s), origin:%s)",
855 PromiseFlatCString(wgp
->GetRemoteType()).get(),
856 OriginString(wgp
->DocumentPrincipal()).get()));
857 options
.mRemoteType
= wgp
->GetRemoteType();
861 // Also enumerate over this WindowContexts' subframes.
862 contexts
.AppendElements(wc
->Children());
867 nsAutoCString originSuffix
= OriginSuffixForRemoteType(resultOrPrecursor
);
869 WebProcessType webProcessType
= WebProcessType::Web
;
870 if (ShouldIsolateSite(resultOrPrecursor
, aTopBC
->UseRemoteSubframes())) {
871 webProcessType
= WebProcessType::WebIsolated
;
874 // Check if we should be loading in a webCOOP+COEP remote type due to our COOP
876 nsILoadInfo::CrossOriginOpenerPolicy coop
=
877 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE
;
879 coop
= aTopBC
->GetOpenerPolicy();
880 } else if (nsCOMPtr
<nsIHttpChannelInternal
> httpChannel
=
881 do_QueryInterface(aChannel
)) {
882 MOZ_ALWAYS_SUCCEEDS(httpChannel
->GetCrossOriginOpenerPolicy(&coop
));
885 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP
) {
886 webProcessType
= WebProcessType::WebCoopCoep
;
888 // If we're changing BrowsingContext, and are going to end up within a
889 // webCOOP+COEP group, ensure we use a cross-origin isolated BCG ID.
890 if (options
.mReplaceBrowsingContext
) {
891 MOZ_ASSERT(!options
.mSpecificGroupId
,
892 "overriding previously-specified BCG ID");
893 options
.mSpecificGroupId
= BrowsingContextGroup::CreateId(
894 /* aPotentiallyCrossOriginIsolated */ true);
898 switch (webProcessType
) {
899 case WebProcessType::Web
:
900 options
.mRemoteType
= WEB_REMOTE_TYPE
;
902 case WebProcessType::WebIsolated
:
903 options
.mRemoteType
=
904 FISSION_WEB_REMOTE_TYPE
"="_ns
+ siteOriginNoSuffix
+ originSuffix
;
906 case WebProcessType::WebCoopCoep
:
907 options
.mRemoteType
=
908 WITH_COOP_COEP_REMOTE_TYPE
"="_ns
+ siteOriginNoSuffix
+ originSuffix
;
914 Result
<WorkerIsolationOptions
, nsresult
> IsolationOptionsForWorker(
915 nsIPrincipal
* aPrincipal
, WorkerKind aWorkerKind
,
916 const nsACString
& aCurrentRemoteType
, bool aUseRemoteSubframes
) {
917 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
918 ("IsolationOptionsForWorker principal:%s, kind:%s, current:%s",
919 OriginString(aPrincipal
).get(), WorkerKindName(aWorkerKind
),
920 PromiseFlatCString(aCurrentRemoteType
).get()));
922 MOZ_ASSERT(NS_IsMainThread());
924 aWorkerKind
== WorkerKindService
|| aWorkerKind
== WorkerKindShared
,
925 "Unexpected remote worker kind");
927 if (aWorkerKind
== WorkerKindService
&&
928 !aPrincipal
->GetIsContentPrincipal()) {
929 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
930 ("Rejecting service worker with non-content principal"));
931 return Err(NS_ERROR_UNEXPECTED
);
934 if (aPrincipal
->GetIsExpandedPrincipal()) {
935 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
936 ("Rejecting remote worker with expanded principal"));
937 return Err(NS_ERROR_UNEXPECTED
);
940 // In some cases, such as for null principals without precursors, we will want
941 // to load a shared worker in a process based on the current process. This is
942 // not done for service workers - process selection for those should function
943 // the same in all processes.
945 // We only allow the current remote type to be used if it is not a COOP+COEP
946 // remote type, in order to avoid loading a shared worker in one of these
947 // processes. Currently process selection for workers occurs before response
948 // headers are available, so we will never select to load a shared worker in a
949 // COOP+COEP content process.
950 nsCString preferredRemoteType
= DEFAULT_REMOTE_TYPE
;
951 if (aWorkerKind
== WorkerKind::WorkerKindShared
&&
952 !StringBeginsWith(aCurrentRemoteType
,
953 WITH_COOP_COEP_REMOTE_TYPE_PREFIX
)) {
954 preferredRemoteType
= aCurrentRemoteType
;
957 WorkerIsolationOptions options
;
959 // If we're loading a null principal, we can't easily make a process
960 // selection decision off ot it. Instead, we'll use our null principal's
961 // precursor principal to make process selection decisions.
962 bool isNullPrincipalPrecursor
= false;
963 nsCOMPtr
<nsIPrincipal
> resultOrPrecursor(aPrincipal
);
964 if (nsCOMPtr
<nsIPrincipal
> precursor
=
965 resultOrPrecursor
->GetPrecursorPrincipal()) {
966 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
967 ("using null principal precursor origin %s",
968 OriginString(precursor
).get()));
969 resultOrPrecursor
= precursor
;
970 isNullPrincipalPrecursor
= true;
973 IsolationBehavior behavior
= IsolationBehavior::WebContent
;
974 if (resultOrPrecursor
->GetIsContentPrincipal()) {
975 nsCOMPtr
<nsIURI
> uri
= resultOrPrecursor
->GetURI();
976 behavior
= IsolationBehaviorForURI(uri
, /* aIsSubframe */ false,
977 /* aForChannelCreationURI */ false);
978 } else if (resultOrPrecursor
->IsSystemPrincipal()) {
979 MOZ_ASSERT(aWorkerKind
== WorkerKindShared
);
981 // Allow system principal shared workers to load within either the
982 // parent process or privilegedabout process, depending on the
983 // responsible process.
984 if (preferredRemoteType
== NOT_REMOTE_TYPE
) {
985 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
986 ("Loading system principal shared worker in parent process"));
987 behavior
= IsolationBehavior::Parent
;
988 } else if (preferredRemoteType
== PRIVILEGEDABOUT_REMOTE_TYPE
) {
989 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
990 ("Loading system principal shared worker in privilegedabout "
992 behavior
= IsolationBehavior::PrivilegedAbout
;
994 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
995 ("Cannot load system-principal shared worker in "
996 "non-privilegedabout content process"));
997 return Err(NS_ERROR_UNEXPECTED
);
1000 MOZ_ASSERT(resultOrPrecursor
->GetIsNullPrincipal());
1001 MOZ_ASSERT(aWorkerKind
== WorkerKindShared
);
1003 if (preferredRemoteType
== NOT_REMOTE_TYPE
) {
1004 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1005 ("Ensuring precursorless null principal shared worker loads in a "
1006 "content process"));
1007 behavior
= IsolationBehavior::ForceWebRemoteType
;
1009 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1010 ("Loading precursorless null principal shared worker within "
1011 "current remotetype: (%s)",
1012 preferredRemoteType
.get()));
1013 behavior
= IsolationBehavior::Anywhere
;
1017 if (behavior
== IsolationBehavior::Parent
&& isNullPrincipalPrecursor
) {
1018 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1019 ("Ensuring sandboxed null-principal shared worker doesn't load in "
1020 "the parent process"));
1021 behavior
= IsolationBehavior::ForceWebRemoteType
;
1024 if (behavior
!= IsolationBehavior::WebContent
) {
1026 options
.mRemoteType
,
1027 SpecialBehaviorRemoteType(behavior
, preferredRemoteType
, nullptr));
1030 gProcessIsolationLog
, LogLevel::Debug
,
1031 ("Selecting specific %s worker remote type (%s) due to a special case "
1032 "isolation behavior %s",
1033 WorkerKindName(aWorkerKind
), options
.mRemoteType
.get(),
1034 IsolationBehaviorName(behavior
)));
1038 // If we should be isolating this site, we can determine the correct fission
1039 // remote type from the principal's site-origin.
1040 if (ShouldIsolateSite(resultOrPrecursor
, aUseRemoteSubframes
)) {
1041 nsAutoCString siteOriginNoSuffix
;
1042 MOZ_TRY(resultOrPrecursor
->GetSiteOriginNoSuffix(siteOriginNoSuffix
));
1043 nsAutoCString originSuffix
= OriginSuffixForRemoteType(resultOrPrecursor
);
1045 nsCString prefix
= aWorkerKind
== WorkerKindService
1046 ? SERVICEWORKER_REMOTE_TYPE
1047 : FISSION_WEB_REMOTE_TYPE
;
1048 options
.mRemoteType
= prefix
+ "="_ns
+ siteOriginNoSuffix
+ originSuffix
;
1050 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1051 ("Isolating web content %s worker in remote type (%s)",
1052 WorkerKindName(aWorkerKind
), options
.mRemoteType
.get()));
1054 options
.mRemoteType
= WEB_REMOTE_TYPE
;
1056 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1057 ("Loading web content %s worker in shared web remote type",
1058 WorkerKindName(aWorkerKind
)));
1063 void AddHighValuePermission(nsIPrincipal
* aResultPrincipal
,
1064 const nsACString
& aPermissionType
) {
1065 RefPtr
<PermissionManager
> perms
= PermissionManager::GetInstance();
1066 if (NS_WARN_IF(!perms
)) {
1070 // We can't act on non-content principals, so if the load was sandboxed, try
1071 // to use the unsandboxed precursor principal to add the highValue permission.
1072 nsCOMPtr
<nsIPrincipal
> resultOrPrecursor(aResultPrincipal
);
1073 if (!aResultPrincipal
->GetIsContentPrincipal()) {
1074 resultOrPrecursor
= aResultPrincipal
->GetPrecursorPrincipal();
1075 if (!resultOrPrecursor
) {
1080 // Use the site-origin principal as we want to add the permission for the
1081 // entire site, rather than a specific subdomain, as process isolation acts on
1082 // a site granularity.
1083 nsAutoCString siteOrigin
;
1084 if (NS_FAILED(resultOrPrecursor
->GetSiteOrigin(siteOrigin
))) {
1088 nsCOMPtr
<nsIPrincipal
> sitePrincipal
=
1089 BasePrincipal::CreateContentPrincipal(siteOrigin
);
1090 if (!sitePrincipal
|| !sitePrincipal
->GetIsContentPrincipal()) {
1094 MOZ_LOG(dom::gProcessIsolationLog
, LogLevel::Verbose
,
1095 ("Adding %s Permission for site '%s'", aPermissionType
.BeginReading(),
1098 uint32_t expiration
= 0;
1099 if (aPermissionType
.Equals(mozilla::dom::kHighValueCOOPPermission
)) {
1100 expiration
= StaticPrefs::fission_highValue_coop_expiration();
1101 } else if (aPermissionType
.Equals(
1102 mozilla::dom::kHighValueHasSavedLoginPermission
) ||
1103 aPermissionType
.Equals(
1104 mozilla::dom::kHighValueIsLoggedInPermission
)) {
1105 expiration
= StaticPrefs::fission_highValue_login_expiration();
1107 MOZ_ASSERT_UNREACHABLE("Unknown permission type");
1110 // XXX: Would be nice if we could use `TimeStamp` here, but there's
1111 // unfortunately no convenient way to recover a time in milliseconds since the
1112 // unix epoch from `TimeStamp`.
1113 int64_t expirationTime
=
1114 (PR_Now() / PR_USEC_PER_MSEC
) + (int64_t(expiration
) * PR_MSEC_PER_SEC
);
1115 Unused
<< perms
->AddFromPrincipal(
1116 sitePrincipal
, aPermissionType
, nsIPermissionManager::ALLOW_ACTION
,
1117 nsIPermissionManager::EXPIRE_TIME
, expirationTime
);
1120 void AddHighValuePermission(const nsACString
& aOrigin
,
1121 const nsACString
& aPermissionType
) {
1122 nsIScriptSecurityManager
* ssm
= nsContentUtils::GetSecurityManager();
1123 nsCOMPtr
<nsIPrincipal
> principal
;
1125 ssm
->CreateContentPrincipalFromOrigin(aOrigin
, getter_AddRefs(principal
));
1126 if (NS_WARN_IF(NS_FAILED(rv
))) {
1130 AddHighValuePermission(principal
, aPermissionType
);
1133 bool IsIsolateHighValueSiteEnabled() {
1134 return mozilla::FissionAutostart() &&
1135 WebContentIsolationStrategy(
1136 StaticPrefs::fission_webContentIsolationStrategy()) ==
1137 WebContentIsolationStrategy::IsolateHighValue
;
1140 } // namespace mozilla::dom