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 for (const auto& separatedDomain
: sSeparatedMozillaDomains
) {
347 // If the domain exactly matches our host, or our host ends with "." +
348 // separatedDomain, we consider it matching.
349 if (separatedDomain
== host
||
350 (separatedDomain
.Length() < host
.Length() &&
351 host
.CharAt(host
.Length() - separatedDomain
.Length() - 1) == '.' &&
352 StringEndsWith(host
, separatedDomain
))) {
353 return IsolationBehavior::PrivilegedMozilla
;
359 nsCOMPtr
<nsIScriptSecurityManager
> secMan
=
360 nsContentUtils::GetSecurityManager();
361 bool inFileURIAllowList
= false;
362 if (NS_SUCCEEDED(secMan
->InFileURIAllowlist(aURI
, &inFileURIAllowList
)) &&
363 inFileURIAllowList
) {
364 return IsolationBehavior::File
;
367 return IsolationBehavior::WebContent
;
371 * Helper method for logging the origin of a principal as a string.
373 static nsAutoCString
OriginString(nsIPrincipal
* aPrincipal
) {
374 nsAutoCString origin
;
375 aPrincipal
->GetOrigin(origin
);
380 * Trim the OriginAttributes from aPrincipal, and use it to create a
381 * OriginSuffix string appropriate to use within a remoteType string.
383 static nsAutoCString
OriginSuffixForRemoteType(nsIPrincipal
* aPrincipal
) {
384 nsAutoCString originSuffix
;
385 OriginAttributes attrs
= aPrincipal
->OriginAttributesRef();
386 attrs
.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN
|
387 OriginAttributes::STRIP_PARITION_KEY
);
388 attrs
.CreateSuffix(originSuffix
);
393 * Given an about:reader URI, extract the "url" query parameter, and use it to
394 * construct a principal which should be used for process selection.
396 static already_AddRefed
<BasePrincipal
> GetAboutReaderURLPrincipal(
397 nsIURI
* aURI
, const OriginAttributes
& aAttrs
) {
399 MOZ_ASSERT(aURI
->SchemeIs("about"));
401 MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI
, path
));
402 MOZ_ASSERT(path
== "reader"_ns
);
406 MOZ_ALWAYS_SUCCEEDS(aURI
->GetQuery(query
));
408 // Extract the "url" parameter from the `about:reader`'s query parameters,
409 // and recover a content principal from it.
410 nsAutoString readerSpec
;
411 if (URLParams::Extract(query
, u
"url"_ns
, readerSpec
)) {
412 nsCOMPtr
<nsIURI
> readerUri
;
413 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri
), readerSpec
))) {
414 return BasePrincipal::CreateContentPrincipal(readerUri
, aAttrs
);
421 * Returns `true` if loads for this site should be isolated on a per-site basis.
422 * If `aTopBC` is nullptr, this is being called to check if a shared or service
423 * worker should be isolated.
425 static bool ShouldIsolateSite(nsIPrincipal
* aPrincipal
,
426 bool aUseRemoteSubframes
) {
427 // If Fission is disabled, we never want to isolate. We check the toplevel BC
428 // if it's available, or the global pref if checking for shared or service
430 if (!aUseRemoteSubframes
) {
434 // non-content principals currently can't have webIsolated remote types
435 // assigned to them, so should not be isolated.
436 if (!aPrincipal
->GetIsContentPrincipal()) {
440 switch (WebContentIsolationStrategy(
441 StaticPrefs::fission_webContentIsolationStrategy())) {
442 case WebContentIsolationStrategy::IsolateNothing
:
443 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
444 ("Not isolating '%s' as isolation is disabled",
445 OriginString(aPrincipal
).get()));
447 case WebContentIsolationStrategy::IsolateEverything
:
448 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
449 ("Isolating '%s' as isolation is enabled for all sites",
450 OriginString(aPrincipal
).get()));
452 case WebContentIsolationStrategy::IsolateHighValue
: {
453 RefPtr
<PermissionManager
> perms
= PermissionManager::GetInstance();
454 if (NS_WARN_IF(!perms
)) {
455 // If we somehow have no permission manager, fall back to the safest
456 // option, and try to isolate.
457 MOZ_ASSERT_UNREACHABLE("Permission manager is missing");
461 static constexpr nsLiteralCString kHighValuePermissions
[] = {
462 mozilla::dom::kHighValueCOOPPermission
,
463 mozilla::dom::kHighValueHasSavedLoginPermission
,
464 mozilla::dom::kHighValueIsLoggedInPermission
,
467 for (const auto& type
: kHighValuePermissions
) {
468 uint32_t permission
= nsIPermissionManager::UNKNOWN_ACTION
;
469 if (NS_SUCCEEDED(perms
->TestPermissionFromPrincipal(aPrincipal
, type
,
471 permission
== nsIPermissionManager::ALLOW_ACTION
) {
472 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
473 ("Isolating '%s' due to high-value permission '%s'",
474 OriginString(aPrincipal
).get(), type
.get()));
478 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
479 ("Not isolating '%s' as it is not high-value",
480 OriginString(aPrincipal
).get()));
484 // An invalid pref value was used. Fall back to the safest option and
485 // isolate everything.
486 NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy");
487 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
488 ("Isolating '%s' due to unknown strategy pref value",
489 OriginString(aPrincipal
).get()));
494 static Result
<nsCString
, nsresult
> SpecialBehaviorRemoteType(
495 IsolationBehavior aBehavior
, const nsACString
& aCurrentRemoteType
,
496 WindowGlobalParent
* aParentWindow
) {
498 case IsolationBehavior::ForceWebRemoteType
:
499 return {WEB_REMOTE_TYPE
};
500 case IsolationBehavior::PrivilegedAbout
:
501 // The privileged about: content process cannot be disabled, as it
502 // causes various actors to break.
503 return {PRIVILEGEDABOUT_REMOTE_TYPE
};
504 case IsolationBehavior::Extension
:
505 if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
506 return {EXTENSION_REMOTE_TYPE
};
508 return {NOT_REMOTE_TYPE
};
509 case IsolationBehavior::File
:
510 if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
511 return {FILE_REMOTE_TYPE
};
513 return {WEB_REMOTE_TYPE
};
514 case IsolationBehavior::PrivilegedMozilla
:
515 return {PRIVILEGEDMOZILLA_REMOTE_TYPE
};
516 case IsolationBehavior::Parent
:
517 return {NOT_REMOTE_TYPE
};
518 case IsolationBehavior::Anywhere
:
519 return {nsCString(aCurrentRemoteType
)};
520 case IsolationBehavior::Inherit
:
521 MOZ_DIAGNOSTIC_ASSERT(aParentWindow
);
522 return {nsCString(aParentWindow
->GetRemoteType())};
524 case IsolationBehavior::Error
:
525 return Err(NS_ERROR_UNEXPECTED
);
528 MOZ_ASSERT_UNREACHABLE();
529 return Err(NS_ERROR_UNEXPECTED
);
533 enum class WebProcessType
{
541 Result
<NavigationIsolationOptions
, nsresult
> IsolationOptionsForNavigation(
542 CanonicalBrowsingContext
* aTopBC
, WindowGlobalParent
* aParentWindow
,
543 nsIURI
* aChannelCreationURI
, nsIChannel
* aChannel
,
544 const nsACString
& aCurrentRemoteType
, bool aHasCOOPMismatch
,
545 bool aForNewTab
, uint32_t aLoadStateLoadType
,
546 const Maybe
<uint64_t>& aChannelId
,
547 const Maybe
<nsCString
>& aRemoteTypeOverride
) {
548 // Get the final principal, used to select which process to load into.
549 nsCOMPtr
<nsIPrincipal
> resultPrincipal
;
550 nsresult rv
= nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
551 aChannel
, getter_AddRefs(resultPrincipal
));
553 MOZ_LOG(gProcessIsolationLog
, LogLevel::Error
,
554 ("failed to get channel result principal"));
559 gProcessIsolationLog
, LogLevel::Verbose
,
560 ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s",
561 OriginString(resultPrincipal
).get(),
562 aChannelCreationURI
->GetSpecOrDefault().get(),
563 aParentWindow
? aParentWindow
->GetDocumentURI()->GetSpecOrDefault().get()
566 // If we're loading a null principal, we can't easily make a process
567 // selection decision off ot it. Instead, we'll use our null principal's
568 // precursor principal to make process selection decisions.
569 bool isNullPrincipalPrecursor
= false;
570 nsCOMPtr
<nsIPrincipal
> resultOrPrecursor(resultPrincipal
);
571 if (nsCOMPtr
<nsIPrincipal
> precursor
=
572 resultOrPrecursor
->GetPrecursorPrincipal()) {
573 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
574 ("using null principal precursor origin %s",
575 OriginString(precursor
).get()));
576 resultOrPrecursor
= precursor
;
577 isNullPrincipalPrecursor
= true;
580 NavigationIsolationOptions options
;
581 options
.mReplaceBrowsingContext
= aHasCOOPMismatch
;
583 // Check if this load has an explicit remote type override. This is used to
584 // perform an about:blank load within a specific content process.
585 if (aRemoteTypeOverride
) {
586 MOZ_DIAGNOSTIC_ASSERT(
587 NS_IsAboutBlank(aChannelCreationURI
),
588 "Should only have aRemoteTypeOverride for about:blank URIs");
589 if (NS_WARN_IF(!resultPrincipal
->GetIsNullPrincipal())) {
590 MOZ_LOG(gProcessIsolationLog
, LogLevel::Error
,
591 ("invalid remote type override on non-null principal"));
592 return Err(NS_ERROR_DOM_SECURITY_ERR
);
595 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
596 ("using remote type override (%s) for load",
597 aRemoteTypeOverride
->get()));
598 options
.mRemoteType
= *aRemoteTypeOverride
;
602 // First, check for any special cases which should be handled using the
603 // channel creation URI, and handle them.
604 auto behavior
= IsolationBehaviorForURI(aChannelCreationURI
, aParentWindow
,
605 /* aForChannelCreationURI */ true);
606 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
607 ("Channel Creation Isolation Behavior: %s",
608 IsolationBehaviorName(behavior
)));
610 // In the about:reader special case, we want to fetch the relevant information
611 // from the URI, an then treat it as a normal web content load.
612 if (behavior
== IsolationBehavior::AboutReader
) {
613 if (RefPtr
<BasePrincipal
> readerURIPrincipal
= GetAboutReaderURLPrincipal(
614 aChannelCreationURI
, resultOrPrecursor
->OriginAttributesRef())) {
615 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
616 ("using about:reader's url origin %s",
617 OriginString(readerURIPrincipal
).get()));
618 resultOrPrecursor
= readerURIPrincipal
;
620 behavior
= IsolationBehavior::WebContent
;
621 // If loading an about:reader page in a BrowsingContext which shares a
622 // BrowsingContextGroup with other toplevel documents, replace the
623 // BrowsingContext to destroy any references.
625 // With SHIP we can apply this to all about:reader loads, but otherwise
626 // do it at least where there are opener/group relationships.
627 if (mozilla::SessionHistoryInParent() ||
628 aTopBC
->Group()->Toplevels().Length() > 1) {
629 options
.mReplaceBrowsingContext
= true;
633 // If we're running in a test which is requesting that system-triggered
634 // about:blank documents load within the current process, override the
635 // behaviour for loads which meet the requirements.
636 if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() &&
637 NS_IsAboutBlank(aChannelCreationURI
)) {
638 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
639 if (loadInfo
->TriggeringPrincipal()->IsSystemPrincipal() &&
640 resultOrPrecursor
->GetIsNullPrincipal()) {
641 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
642 ("Forcing system-principal triggered about:blank load to "
643 "complete in the current process"));
644 behavior
= IsolationBehavior::Anywhere
;
648 #ifdef MOZ_WIDGET_ANDROID
649 // If we're loading an error page on android, it must complete within the same
650 // process as the errored page load would complete in due to code expecting
651 // that behavior. See bug 1673763.
652 if (aLoadStateLoadType
== LOAD_ERROR_PAGE
) {
653 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
654 ("Forcing error page load to complete in the current process"));
655 behavior
= IsolationBehavior::Anywhere
;
659 // If we're loading for a specific extension, we'll need to perform a
660 // BCG-switching load to get our toplevel extension window in the correct
661 // BrowsingContextGroup.
662 if (auto* addonPolicy
=
663 BasePrincipal::Cast(resultOrPrecursor
)->AddonPolicy()) {
665 // As a temporary measure, extension iframes must be loaded within the
666 // same process as their parent document.
667 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
668 ("Loading extension subframe in same process as parent"));
669 behavior
= IsolationBehavior::Inherit
;
672 gProcessIsolationLog
, LogLevel::Verbose
,
673 ("Found extension frame with addon policy. Will use group id %" PRIx64
674 " (currentId: %" PRIx64
")",
675 addonPolicy
->GetBrowsingContextGroupId(), aTopBC
->Group()->Id()));
676 behavior
= IsolationBehavior::Extension
;
677 if (aTopBC
->Group()->Id() != addonPolicy
->GetBrowsingContextGroupId()) {
678 options
.mReplaceBrowsingContext
= true;
679 options
.mSpecificGroupId
= addonPolicy
->GetBrowsingContextGroupId();
684 // Do a second run of `GetIsolationBehavior`, this time using the
685 // principal's URI to handle additional special cases such as the file and
686 // privilegedmozilla content process.
687 if (behavior
== IsolationBehavior::WebContent
) {
688 if (resultOrPrecursor
->IsSystemPrincipal()) {
689 // We're loading something with a system principal which isn't caught in
690 // one of our other edge-cases. If the load started in the parent process,
691 // and it's safe for it to end in the parent process, we should finish the
693 bool isUIResource
= false;
694 if (aCurrentRemoteType
.IsEmpty() &&
695 (aChannelCreationURI
->SchemeIs("about") ||
696 (NS_SUCCEEDED(NS_URIChainHasFlags(
697 aChannelCreationURI
, nsIProtocolHandler::URI_IS_UI_RESOURCE
,
700 behavior
= IsolationBehavior::Parent
;
702 // In general, we don't want to load documents with a system principal
703 // in a content process, however we need to in some cases, such as when
704 // loading blob: URLs created by system code. We can force the load to
705 // finish in a content process instead.
706 behavior
= IsolationBehavior::ForceWebRemoteType
;
708 } else if (nsCOMPtr
<nsIURI
> principalURI
= resultOrPrecursor
->GetURI()) {
709 behavior
= IsolationBehaviorForURI(principalURI
, aParentWindow
,
710 /* aForChannelCreationURI */ false);
714 // If we're currently loaded in the extension process, and are going to switch
715 // to some other remote type, make sure we leave the extension's BCG which we
716 // may have entered earlier to separate extension and non-extension BCGs from
718 if (!aParentWindow
&& aCurrentRemoteType
== EXTENSION_REMOTE_TYPE
&&
719 behavior
!= IsolationBehavior::Extension
&&
720 behavior
!= IsolationBehavior::Anywhere
) {
721 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
722 ("Forcing BC replacement to leave extension BrowsingContextGroup "
723 "%" PRIx64
" on navigation",
724 aTopBC
->Group()->Id()));
725 options
.mReplaceBrowsingContext
= true;
728 // We don't want to load documents with sandboxed null principals, like
729 // `data:` URIs, in the parent process, even if they were created by a
730 // document which would otherwise be loaded in the parent process.
731 if (behavior
== IsolationBehavior::Parent
&& isNullPrincipalPrecursor
) {
732 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
733 ("Ensuring sandboxed null-principal load doesn't occur in the "
735 behavior
= IsolationBehavior::ForceWebRemoteType
;
739 gProcessIsolationLog
, LogLevel::Debug
,
740 ("Using IsolationBehavior %s for %s (original uri %s)",
741 IsolationBehaviorName(behavior
), OriginString(resultOrPrecursor
).get(),
742 aChannelCreationURI
->GetSpecOrDefault().get()));
744 // Check if we can put the previous document into the BFCache.
745 if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 &&
746 !aForNewTab
&& !aParentWindow
&& !aTopBC
->HadOriginalOpener() &&
747 behavior
!= IsolationBehavior::Parent
&&
748 (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() ||
749 behavior
!= IsolationBehavior::Extension
) &&
750 !aCurrentRemoteType
.IsEmpty() &&
751 aTopBC
->GetHasLoadedNonInitialDocument() &&
752 (aLoadStateLoadType
== LOAD_NORMAL
||
753 aLoadStateLoadType
== LOAD_HISTORY
|| aLoadStateLoadType
== LOAD_LINK
||
754 aLoadStateLoadType
== LOAD_STOP_CONTENT
||
755 aLoadStateLoadType
== LOAD_STOP_CONTENT_AND_REPLACE
) &&
756 (!aTopBC
->GetActiveSessionHistoryEntry() ||
757 aTopBC
->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) {
758 if (nsCOMPtr
<nsIURI
> uri
= aTopBC
->GetCurrentURI()) {
759 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
760 ("current uri: %s", uri
->GetSpecOrDefault().get()));
762 options
.mTryUseBFCache
=
763 aTopBC
->AllowedInBFCache(aChannelId
, aChannelCreationURI
);
764 if (options
.mTryUseBFCache
) {
765 options
.mReplaceBrowsingContext
= true;
766 options
.mActiveSessionHistoryEntry
=
767 aTopBC
->GetActiveSessionHistoryEntry();
771 // If the load has any special remote type handling, do so at this point.
772 if (behavior
!= IsolationBehavior::WebContent
) {
775 SpecialBehaviorRemoteType(behavior
, aCurrentRemoteType
, aParentWindow
));
777 if (options
.mRemoteType
!= aCurrentRemoteType
&&
778 (options
.mRemoteType
.IsEmpty() || aCurrentRemoteType
.IsEmpty())) {
779 options
.mReplaceBrowsingContext
= true;
783 gProcessIsolationLog
, LogLevel::Debug
,
784 ("Selecting specific remote type (%s) due to a special case isolation "
786 options
.mRemoteType
.get(), IsolationBehaviorName(behavior
)));
790 // At this point we're definitely not going to be loading in the parent
791 // process anymore, so we're definitely going to be replacing BrowsingContext
792 // if we're in the parent process.
793 if (aCurrentRemoteType
.IsEmpty()) {
794 MOZ_ASSERT(!aParentWindow
);
795 options
.mReplaceBrowsingContext
= true;
798 nsAutoCString siteOriginNoSuffix
;
799 MOZ_TRY(resultOrPrecursor
->GetSiteOriginNoSuffix(siteOriginNoSuffix
));
801 // Check if we've already loaded a document with the given principal in some
802 // content process. We want to finish the load in the same process in that
805 // The exception to that is with extension loads and the system principal,
806 // where we may have multiple documents with the same principal in different
807 // processes. Those have been handled above, and will not be reaching here.
809 // If we're doing a replace load or opening a new tab, we won't be staying in
810 // the same BrowsingContextGroup, so ignore this step.
811 if (!options
.mReplaceBrowsingContext
&& !aForNewTab
) {
812 // Helper for efficiently determining if a given origin is same-site. This
813 // will attempt to do a fast equality check, and will only fall back to
814 // computing the site-origin for content principals.
815 auto principalIsSameSite
= [&](nsIPrincipal
* aDocumentPrincipal
) -> bool {
816 // If we're working with a null principal with a precursor, compare
817 // precursors, as `resultOrPrecursor` has already been stripped to its
819 nsCOMPtr
<nsIPrincipal
> documentPrincipal(aDocumentPrincipal
);
820 if (nsCOMPtr
<nsIPrincipal
> precursor
=
821 documentPrincipal
->GetPrecursorPrincipal()) {
822 documentPrincipal
= precursor
;
825 // First, attempt to use `Equals` to compare principals, and if that
826 // fails compare siteOrigins. Only compare siteOrigin for content
827 // principals, as non-content principals will never have siteOrigin !=
829 nsAutoCString documentSiteOrigin
;
830 return resultOrPrecursor
->Equals(documentPrincipal
) ||
831 (documentPrincipal
->GetIsContentPrincipal() &&
832 resultOrPrecursor
->GetIsContentPrincipal() &&
833 NS_SUCCEEDED(documentPrincipal
->GetSiteOriginNoSuffix(
834 documentSiteOrigin
)) &&
835 documentSiteOrigin
== siteOriginNoSuffix
);
838 // XXX: Consider also checking in-flight process switches to see if any have
839 // matching principals?
840 AutoTArray
<RefPtr
<BrowsingContext
>, 8> contexts
;
841 aTopBC
->Group()->GetToplevels(contexts
);
842 while (!contexts
.IsEmpty()) {
843 auto bc
= contexts
.PopLastElement();
844 for (const auto& wc
: bc
->GetWindowContexts()) {
845 WindowGlobalParent
* wgp
= wc
->Canonical();
847 // Check if this WindowGlobalParent has the given resultPrincipal, and
848 // if it does, we need to load in that process.
849 if (!wgp
->GetRemoteType().IsEmpty() &&
850 principalIsSameSite(wgp
->DocumentPrincipal())) {
851 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
852 ("Found existing frame with matching principal "
853 "(remoteType:(%s), origin:%s)",
854 PromiseFlatCString(wgp
->GetRemoteType()).get(),
855 OriginString(wgp
->DocumentPrincipal()).get()));
856 options
.mRemoteType
= wgp
->GetRemoteType();
860 // Also enumerate over this WindowContexts' subframes.
861 contexts
.AppendElements(wc
->Children());
866 nsAutoCString originSuffix
= OriginSuffixForRemoteType(resultOrPrecursor
);
868 WebProcessType webProcessType
= WebProcessType::Web
;
869 if (ShouldIsolateSite(resultOrPrecursor
, aTopBC
->UseRemoteSubframes())) {
870 webProcessType
= WebProcessType::WebIsolated
;
873 // Check if we should be loading in a webCOOP+COEP remote type due to our COOP
875 nsILoadInfo::CrossOriginOpenerPolicy coop
=
876 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE
;
878 coop
= aTopBC
->GetOpenerPolicy();
879 } else if (nsCOMPtr
<nsIHttpChannelInternal
> httpChannel
=
880 do_QueryInterface(aChannel
)) {
881 MOZ_ALWAYS_SUCCEEDS(httpChannel
->GetCrossOriginOpenerPolicy(&coop
));
884 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP
) {
885 webProcessType
= WebProcessType::WebCoopCoep
;
887 // If we're changing BrowsingContext, and are going to end up within a
888 // webCOOP+COEP group, ensure we use a cross-origin isolated BCG ID.
889 if (options
.mReplaceBrowsingContext
) {
890 MOZ_ASSERT(!options
.mSpecificGroupId
,
891 "overriding previously-specified BCG ID");
892 options
.mSpecificGroupId
= BrowsingContextGroup::CreateId(
893 /* aPotentiallyCrossOriginIsolated */ true);
897 switch (webProcessType
) {
898 case WebProcessType::Web
:
899 options
.mRemoteType
= WEB_REMOTE_TYPE
;
901 case WebProcessType::WebIsolated
:
902 options
.mRemoteType
=
903 FISSION_WEB_REMOTE_TYPE
"="_ns
+ siteOriginNoSuffix
+ originSuffix
;
905 case WebProcessType::WebCoopCoep
:
906 options
.mRemoteType
=
907 WITH_COOP_COEP_REMOTE_TYPE
"="_ns
+ siteOriginNoSuffix
+ originSuffix
;
913 Result
<WorkerIsolationOptions
, nsresult
> IsolationOptionsForWorker(
914 nsIPrincipal
* aPrincipal
, WorkerKind aWorkerKind
,
915 const nsACString
& aCurrentRemoteType
, bool aUseRemoteSubframes
) {
916 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
917 ("IsolationOptionsForWorker principal:%s, kind:%s, current:%s",
918 OriginString(aPrincipal
).get(), WorkerKindName(aWorkerKind
),
919 PromiseFlatCString(aCurrentRemoteType
).get()));
921 MOZ_ASSERT(NS_IsMainThread());
923 aWorkerKind
== WorkerKindService
|| aWorkerKind
== WorkerKindShared
,
924 "Unexpected remote worker kind");
926 if (aWorkerKind
== WorkerKindService
&&
927 !aPrincipal
->GetIsContentPrincipal()) {
928 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
929 ("Rejecting service worker with non-content principal"));
930 return Err(NS_ERROR_UNEXPECTED
);
933 if (aPrincipal
->GetIsExpandedPrincipal()) {
934 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
935 ("Rejecting remote worker with expanded principal"));
936 return Err(NS_ERROR_UNEXPECTED
);
939 // In some cases, such as for null principals without precursors, we will want
940 // to load a shared worker in a process based on the current process. This is
941 // not done for service workers - process selection for those should function
942 // the same in all processes.
944 // We only allow the current remote type to be used if it is not a COOP+COEP
945 // remote type, in order to avoid loading a shared worker in one of these
946 // processes. Currently process selection for workers occurs before response
947 // headers are available, so we will never select to load a shared worker in a
948 // COOP+COEP content process.
949 nsCString preferredRemoteType
= DEFAULT_REMOTE_TYPE
;
950 if (aWorkerKind
== WorkerKind::WorkerKindShared
&&
951 !StringBeginsWith(aCurrentRemoteType
,
952 WITH_COOP_COEP_REMOTE_TYPE_PREFIX
)) {
953 preferredRemoteType
= aCurrentRemoteType
;
956 WorkerIsolationOptions options
;
958 // If we're loading a null principal, we can't easily make a process
959 // selection decision off ot it. Instead, we'll use our null principal's
960 // precursor principal to make process selection decisions.
961 bool isNullPrincipalPrecursor
= false;
962 nsCOMPtr
<nsIPrincipal
> resultOrPrecursor(aPrincipal
);
963 if (nsCOMPtr
<nsIPrincipal
> precursor
=
964 resultOrPrecursor
->GetPrecursorPrincipal()) {
965 MOZ_LOG(gProcessIsolationLog
, LogLevel::Verbose
,
966 ("using null principal precursor origin %s",
967 OriginString(precursor
).get()));
968 resultOrPrecursor
= precursor
;
969 isNullPrincipalPrecursor
= true;
972 IsolationBehavior behavior
= IsolationBehavior::WebContent
;
973 if (resultOrPrecursor
->GetIsContentPrincipal()) {
974 nsCOMPtr
<nsIURI
> uri
= resultOrPrecursor
->GetURI();
975 behavior
= IsolationBehaviorForURI(uri
, /* aIsSubframe */ false,
976 /* aForChannelCreationURI */ false);
977 } else if (resultOrPrecursor
->IsSystemPrincipal()) {
978 MOZ_ASSERT(aWorkerKind
== WorkerKindShared
);
980 // Allow system principal shared workers to load within either the
981 // parent process or privilegedabout process, depending on the
982 // responsible process.
983 if (preferredRemoteType
== NOT_REMOTE_TYPE
) {
984 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
985 ("Loading system principal shared worker in parent process"));
986 behavior
= IsolationBehavior::Parent
;
987 } else if (preferredRemoteType
== PRIVILEGEDABOUT_REMOTE_TYPE
) {
988 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
989 ("Loading system principal shared worker in privilegedabout "
991 behavior
= IsolationBehavior::PrivilegedAbout
;
993 MOZ_LOG(gProcessIsolationLog
, LogLevel::Warning
,
994 ("Cannot load system-principal shared worker in "
995 "non-privilegedabout content process"));
996 return Err(NS_ERROR_UNEXPECTED
);
999 MOZ_ASSERT(resultOrPrecursor
->GetIsNullPrincipal());
1000 MOZ_ASSERT(aWorkerKind
== WorkerKindShared
);
1002 if (preferredRemoteType
== NOT_REMOTE_TYPE
) {
1003 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1004 ("Ensuring precursorless null principal shared worker loads in a "
1005 "content process"));
1006 behavior
= IsolationBehavior::ForceWebRemoteType
;
1008 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1009 ("Loading precursorless null principal shared worker within "
1010 "current remotetype: (%s)",
1011 preferredRemoteType
.get()));
1012 behavior
= IsolationBehavior::Anywhere
;
1016 if (behavior
== IsolationBehavior::Parent
&& isNullPrincipalPrecursor
) {
1017 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1018 ("Ensuring sandboxed null-principal shared worker doesn't load in "
1019 "the parent process"));
1020 behavior
= IsolationBehavior::ForceWebRemoteType
;
1023 if (behavior
!= IsolationBehavior::WebContent
) {
1025 options
.mRemoteType
,
1026 SpecialBehaviorRemoteType(behavior
, preferredRemoteType
, nullptr));
1029 gProcessIsolationLog
, LogLevel::Debug
,
1030 ("Selecting specific %s worker remote type (%s) due to a special case "
1031 "isolation behavior %s",
1032 WorkerKindName(aWorkerKind
), options
.mRemoteType
.get(),
1033 IsolationBehaviorName(behavior
)));
1037 // If we should be isolating this site, we can determine the correct fission
1038 // remote type from the principal's site-origin.
1039 if (ShouldIsolateSite(resultOrPrecursor
, aUseRemoteSubframes
)) {
1040 nsAutoCString siteOriginNoSuffix
;
1041 MOZ_TRY(resultOrPrecursor
->GetSiteOriginNoSuffix(siteOriginNoSuffix
));
1042 nsAutoCString originSuffix
= OriginSuffixForRemoteType(resultOrPrecursor
);
1044 nsCString prefix
= aWorkerKind
== WorkerKindService
1045 ? SERVICEWORKER_REMOTE_TYPE
1046 : FISSION_WEB_REMOTE_TYPE
;
1047 options
.mRemoteType
= prefix
+ "="_ns
+ siteOriginNoSuffix
+ originSuffix
;
1049 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1050 ("Isolating web content %s worker in remote type (%s)",
1051 WorkerKindName(aWorkerKind
), options
.mRemoteType
.get()));
1053 options
.mRemoteType
= WEB_REMOTE_TYPE
;
1055 MOZ_LOG(gProcessIsolationLog
, LogLevel::Debug
,
1056 ("Loading web content %s worker in shared web remote type",
1057 WorkerKindName(aWorkerKind
)));
1062 void AddHighValuePermission(nsIPrincipal
* aResultPrincipal
,
1063 const nsACString
& aPermissionType
) {
1064 RefPtr
<PermissionManager
> perms
= PermissionManager::GetInstance();
1065 if (NS_WARN_IF(!perms
)) {
1069 // We can't act on non-content principals, so if the load was sandboxed, try
1070 // to use the unsandboxed precursor principal to add the highValue permission.
1071 nsCOMPtr
<nsIPrincipal
> resultOrPrecursor(aResultPrincipal
);
1072 if (!aResultPrincipal
->GetIsContentPrincipal()) {
1073 resultOrPrecursor
= aResultPrincipal
->GetPrecursorPrincipal();
1074 if (!resultOrPrecursor
) {
1079 // Use the site-origin principal as we want to add the permission for the
1080 // entire site, rather than a specific subdomain, as process isolation acts on
1081 // a site granularity.
1082 nsAutoCString siteOrigin
;
1083 if (NS_FAILED(resultOrPrecursor
->GetSiteOrigin(siteOrigin
))) {
1087 nsCOMPtr
<nsIPrincipal
> sitePrincipal
=
1088 BasePrincipal::CreateContentPrincipal(siteOrigin
);
1089 if (!sitePrincipal
|| !sitePrincipal
->GetIsContentPrincipal()) {
1093 MOZ_LOG(dom::gProcessIsolationLog
, LogLevel::Verbose
,
1094 ("Adding %s Permission for site '%s'", aPermissionType
.BeginReading(),
1097 uint32_t expiration
= 0;
1098 if (aPermissionType
.Equals(mozilla::dom::kHighValueCOOPPermission
)) {
1099 expiration
= StaticPrefs::fission_highValue_coop_expiration();
1100 } else if (aPermissionType
.Equals(
1101 mozilla::dom::kHighValueHasSavedLoginPermission
) ||
1102 aPermissionType
.Equals(
1103 mozilla::dom::kHighValueIsLoggedInPermission
)) {
1104 expiration
= StaticPrefs::fission_highValue_login_expiration();
1106 MOZ_ASSERT_UNREACHABLE("Unknown permission type");
1109 // XXX: Would be nice if we could use `TimeStamp` here, but there's
1110 // unfortunately no convenient way to recover a time in milliseconds since the
1111 // unix epoch from `TimeStamp`.
1112 int64_t expirationTime
=
1113 (PR_Now() / PR_USEC_PER_MSEC
) + (int64_t(expiration
) * PR_MSEC_PER_SEC
);
1114 Unused
<< perms
->AddFromPrincipal(
1115 sitePrincipal
, aPermissionType
, nsIPermissionManager::ALLOW_ACTION
,
1116 nsIPermissionManager::EXPIRE_TIME
, expirationTime
);
1119 void AddHighValuePermission(const nsACString
& aOrigin
,
1120 const nsACString
& aPermissionType
) {
1121 nsIScriptSecurityManager
* ssm
= nsContentUtils::GetSecurityManager();
1122 nsCOMPtr
<nsIPrincipal
> principal
;
1124 ssm
->CreateContentPrincipalFromOrigin(aOrigin
, getter_AddRefs(principal
));
1125 if (NS_WARN_IF(NS_FAILED(rv
))) {
1129 AddHighValuePermission(principal
, aPermissionType
);
1132 bool IsIsolateHighValueSiteEnabled() {
1133 return mozilla::FissionAutostart() &&
1134 WebContentIsolationStrategy(
1135 StaticPrefs::fission_webContentIsolationStrategy()) ==
1136 WebContentIsolationStrategy::IsolateHighValue
;
1139 } // namespace mozilla::dom