summaryrefslogtreecommitdiffstats
path: root/dom/ipc/ProcessIsolation.cpp
blob: cd223e647f1a9c9d9d9611611a74c6b8885c754e (plain)
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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/ProcessIsolation.h"

#include "mozilla/Assertions.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/RemoteType.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ContentPrincipal.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/Logging.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/PermissionManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPtr.h"
#include "nsAboutProtocolUtils.h"
#include "nsDocShell.h"
#include "nsError.h"
#include "nsIChromeRegistry.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIProtocolHandler.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsSHistory.h"
#include "nsURLHelper.h"

namespace mozilla::dom {

mozilla::LazyLogModule gProcessIsolationLog{"ProcessIsolation"};

namespace {

// Strategy used to determine whether or not a particular site should load into
// a webIsolated content process. The particular strategy chosen is controlled
// by the `fission.webContentIsolationStrategy` pref, which must hold one of the
// following values.
enum class WebContentIsolationStrategy : uint32_t {
  // All web content is loaded into a shared `web` content process. This is
  // similar to the non-Fission behaviour, however remote subframes may still
  // be used for sites with special isolation behaviour, such as extension or
  // mozillaweb content processes.
  IsolateNothing = 0,
  // Web content is always isolated into its own `webIsolated` content process
  // based on site-origin, and will only load in a shared `web` content process
  // if site-origin could not be determined.
  IsolateEverything = 1,
  // Only isolates web content loaded by sites which are considered "high
  // value". A site is considered "high value" if it has been granted a
  // `highValue*` permission by the permission manager, which is done in
  // response to certain actions.
  IsolateHighValue = 2,
};

/**
 * Helper class for caching the result of splitting prefs which are represented
 * as a comma-separated list of strings.
 */
struct CommaSeparatedPref {
 public:
  explicit constexpr CommaSeparatedPref(nsLiteralCString aPrefName)
      : mPrefName(aPrefName) {}

  void OnChange() {
    if (mValues) {
      mValues->Clear();
      nsAutoCString prefValue;
      if (NS_SUCCEEDED(Preferences::GetCString(mPrefName.get(), prefValue))) {
        for (const auto& value :
             nsCCharSeparatedTokenizer(prefValue, ',').ToRange()) {
          mValues->EmplaceBack(value);
        }
      }
    }
  }

  const nsTArray<nsCString>& Get() {
    if (!mValues) {
      mValues = new nsTArray<nsCString>;
      Preferences::RegisterCallbackAndCall(
          [](const char*, void* aData) {
            static_cast<CommaSeparatedPref*>(aData)->OnChange();
          },
          mPrefName, this);
      RunOnShutdown([this] {
        delete this->mValues;
        this->mValues = nullptr;
      });
    }
    return *mValues;
  }

  auto begin() { return Get().cbegin(); }
  auto end() { return Get().cend(); }

 private:
  nsLiteralCString mPrefName;
  nsTArray<nsCString>* MOZ_OWNING_REF mValues = nullptr;
};

CommaSeparatedPref sSeparatedMozillaDomains{
    "browser.tabs.remote.separatedMozillaDomains"_ns};

/**
 * Certain URIs have special isolation behaviour, and need to be loaded within
 * specific process types.
 */
enum class IsolationBehavior {
  // This URI loads web content and should be treated as a content load, being
  // isolated based on the response principal.
  WebContent,
  // Forcibly load in a process with the "web" remote type.
  ForceWebRemoteType,
  // Load this URI in the privileged about content process.
  PrivilegedAbout,
  // Load this URI in the extension process.
  Extension,
  // Load this URI in the file content process.
  File,
  // Load this URI in the priviliged mozilla content process.
  PrivilegedMozilla,
  // Load this URI explicitly in the parent process.
  Parent,
  // Load this URI wherever the browsing context is currently loaded. This is
  // generally used for error pages.
  Anywhere,
  // May only be returned for subframes. Inherits the remote type of the parent
  // document which is embedding this document.
  Inherit,
  // Special case for the `about:reader` URI which should be loaded in the same
  // process which would be used for the "url" query parameter.
  AboutReader,
  // There was a fatal error, and the load should be aborted.
  Error,
};

/**
 * Returns a static string with the name of the given isolation behaviour. For
 * use in logging code.
 */
static const char* IsolationBehaviorName(IsolationBehavior aBehavior) {
  switch (aBehavior) {
    case IsolationBehavior::WebContent:
      return "WebContent";
    case IsolationBehavior::ForceWebRemoteType:
      return "ForceWebRemoteType";
    case IsolationBehavior::PrivilegedAbout:
      return "PrivilegedAbout";
    case IsolationBehavior::Extension:
      return "Extension";
    case IsolationBehavior::File:
      return "File";
    case IsolationBehavior::PrivilegedMozilla:
      return "PrivilegedMozilla";
    case IsolationBehavior::Parent:
      return "Parent";
    case IsolationBehavior::Anywhere:
      return "Anywhere";
    case IsolationBehavior::Inherit:
      return "Inherit";
    case IsolationBehavior::AboutReader:
      return "AboutReader";
    case IsolationBehavior::Error:
      return "Error";
    default:
      return "Unknown";
  }
}

/**
 * Check if a given URI has specialized process isolation behaviour, such as
 * needing to be loaded within a specific type of content process.
 *
 * When handling a navigation, this method will be called twice: first with the
 * channel's creation URI, and then it will be called with a result principal's
 * URI.
 */
static IsolationBehavior IsolationBehaviorForURI(nsIURI* aURI, bool aIsSubframe,
                                                 bool aForChannelCreationURI) {
  nsAutoCString scheme;
  MOZ_ALWAYS_SUCCEEDS(aURI->GetScheme(scheme));

  if (scheme == "chrome"_ns) {
    // `chrome://` URIs are always loaded in the parent process, unless they
    // have opted in to loading in a content process. This is currently only
    // done in tests.
    //
    // FIXME: These flags should be removed from `chrome` URIs at some point.
    nsCOMPtr<nsIXULChromeRegistry> chromeReg =
        do_GetService("@mozilla.org/chrome/chrome-registry;1");
    bool mustLoadRemotely = false;
    if (NS_SUCCEEDED(chromeReg->MustLoadURLRemotely(aURI, &mustLoadRemotely)) &&
        mustLoadRemotely) {
      return IsolationBehavior::ForceWebRemoteType;
    }
    bool canLoadRemotely = false;
    if (NS_SUCCEEDED(chromeReg->CanLoadURLRemotely(aURI, &canLoadRemotely)) &&
        canLoadRemotely) {
      return IsolationBehavior::Anywhere;
    }
    return IsolationBehavior::Parent;
  }

  if (scheme == "about"_ns) {
    nsAutoCString path;
    MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path));

    // The `about:blank` and `about:srcdoc` pages are loaded by normal web
    // content, and should be allocated processes based on their simple content
    // principals.
    if (path == "blank"_ns || path == "srcdoc"_ns) {
      MOZ_ASSERT(NS_IsContentAccessibleAboutURI(aURI));
      return IsolationBehavior::WebContent;
    }

    MOZ_ASSERT(!NS_IsContentAccessibleAboutURI(aURI));
    // If we're loading an `about:reader` URI, perform isolation based on the
    // principal of the URI being loaded.
    if (path == "reader"_ns && aForChannelCreationURI) {
      return IsolationBehavior::AboutReader;
    }

    // Otherwise, we're going to be loading an about: page. Consult the module.
    nsCOMPtr<nsIAboutModule> aboutModule;
    if (NS_FAILED(NS_GetAboutModule(aURI, getter_AddRefs(aboutModule))) ||
        !aboutModule) {
      // If we don't know of an about: module for this load, it's going to end
      // up being a network error. Allow the load to finish as normal.
      return IsolationBehavior::WebContent;
    }

    // NOTE: about modules can be implemented in JS, so this may run script, and
    // therefore can spuriously fail.
    uint32_t flags = 0;
    if (NS_FAILED(aboutModule->GetURIFlags(aURI, &flags))) {
      NS_WARNING(
          "nsIAboutModule::GetURIFlags unexpectedly failed. Abort the load");
      return IsolationBehavior::Error;
    }

    if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
      return IsolationBehavior::Extension;
    }

    if (flags & nsIAboutModule::URI_MUST_LOAD_IN_CHILD) {
      if (flags & nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS) {
        return IsolationBehavior::PrivilegedAbout;
      }
      return IsolationBehavior::ForceWebRemoteType;
    }

    if (flags & nsIAboutModule::URI_CAN_LOAD_IN_CHILD) {
      return IsolationBehavior::Anywhere;
    }

    return IsolationBehavior::Parent;
  }

  // If the test-only `dataUriInDefaultWebProcess` pref is enabled, dump all
  // `data:` URIs in a "web" content process, rather than loading them in
  // content processes based on their precursor origins.
  if (StaticPrefs::browser_tabs_remote_dataUriInDefaultWebProcess() &&
      scheme == "data"_ns) {
    return IsolationBehavior::ForceWebRemoteType;
  }

  // Make sure to unwrap nested URIs before we early return for channel creation
  // URI. The checks past this point are intended to operate on the principal,
  // which has it's origin constructed from the innermost URI.
  nsCOMPtr<nsIURI> inner;
  if (nsCOMPtr<nsINestedURI> nested = do_QueryInterface(aURI);
      nested && NS_SUCCEEDED(nested->GetInnerURI(getter_AddRefs(inner)))) {
    return IsolationBehaviorForURI(inner, aIsSubframe, aForChannelCreationURI);
  }

  // If we're doing the initial check based on the channel creation URI, stop
  // here as we want to only perform the following checks on the true channel
  // result principal.
  if (aForChannelCreationURI) {
    return IsolationBehavior::WebContent;
  }

  // Protocols used by Thunderbird to display email messages.
  if (scheme == "imap"_ns || scheme == "mailbox"_ns || scheme == "news"_ns ||
      scheme == "nntp"_ns || scheme == "snews"_ns) {
    return IsolationBehavior::Parent;
  }

  // There is more handling for extension content processes in the caller, but
  // they should load in an extension content process unless we're loading a
  // subframe.
  if (scheme == "moz-extension"_ns) {
    if (aIsSubframe) {
      // As a temporary measure, extension iframes must be loaded within the
      // same process as their parent document.
      return IsolationBehavior::Inherit;
    }
    return IsolationBehavior::Extension;
  }

  if (scheme == "file"_ns) {
    return IsolationBehavior::File;
  }

  // Check if the URI is listed as a privileged mozilla content process.
  if (scheme == "https"_ns &&
      StaticPrefs::
          browser_tabs_remote_separatePrivilegedMozillaWebContentProcess()) {
    nsAutoCString host;
    if (NS_SUCCEEDED(aURI->GetAsciiHost(host))) {
      for (const auto& separatedDomain : sSeparatedMozillaDomains) {
        // If the domain exactly matches our host, or our host ends with "." +
        // separatedDomain, we consider it matching.
        if (separatedDomain == host ||
            (separatedDomain.Length() < host.Length() &&
             host.CharAt(host.Length() - separatedDomain.Length() - 1) == '.' &&
             StringEndsWith(host, separatedDomain))) {
          return IsolationBehavior::PrivilegedMozilla;
        }
      }
    }
  }

  nsCOMPtr<nsIScriptSecurityManager> secMan =
      nsContentUtils::GetSecurityManager();
  bool inFileURIAllowList = false;
  if (NS_SUCCEEDED(secMan->InFileURIAllowlist(aURI, &inFileURIAllowList)) &&
      inFileURIAllowList) {
    return IsolationBehavior::File;
  }

  return IsolationBehavior::WebContent;
}

/**
 * Helper method for logging the origin of a principal as a string.
 */
static nsAutoCString OriginString(nsIPrincipal* aPrincipal) {
  nsAutoCString origin;
  aPrincipal->GetOrigin(origin);
  return origin;
}

/**
 * Given an about:reader URI, extract the "url" query parameter, and use it to
 * construct a principal which should be used for process selection.
 */
static already_AddRefed<BasePrincipal> GetAboutReaderURLPrincipal(
    nsIURI* aURI, const OriginAttributes& aAttrs) {
#ifdef DEBUG
  MOZ_ASSERT(aURI->SchemeIs("about"));
  nsAutoCString path;
  MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path));
  MOZ_ASSERT(path == "reader"_ns);
#endif

  nsAutoCString query;
  MOZ_ALWAYS_SUCCEEDS(aURI->GetQuery(query));

  // Extract the "url" parameter from the `about:reader`'s query parameters,
  // and recover a content principal from it.
  nsAutoString readerSpec;
  if (URLParams::Extract(query, u"url"_ns, readerSpec)) {
    nsCOMPtr<nsIURI> readerUri;
    if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri), readerSpec))) {
      return BasePrincipal::CreateContentPrincipal(readerUri, aAttrs);
    }
  }
  return nullptr;
}

/**
 * Returns `true` if loads for this site should be isolated on a per-site basis.
 * If `aTopBC` is nullptr, this is being called to check if a shared or service
 * worker should be isolated.
 */
static bool ShouldIsolateSite(nsIPrincipal* aPrincipal,
                              CanonicalBrowsingContext* aTopBC) {
  // If Fission is disabled, we never want to isolate. We check the toplevel BC
  // if it's available, or the global pref if checking for shared or service
  // workers.
  if (aTopBC && !aTopBC->UseRemoteSubframes()) {
    return false;
  }
  if (!aTopBC && !mozilla::FissionAutostart()) {
    return false;
  }

  // non-content principals currently can't have webIsolated remote types
  // assigned to them, so should not be isolated.
  if (!aPrincipal->GetIsContentPrincipal()) {
    return false;
  }

  switch (WebContentIsolationStrategy(
      StaticPrefs::fission_webContentIsolationStrategy())) {
    case WebContentIsolationStrategy::IsolateNothing:
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("Not isolating '%s' as isolation is disabled",
               OriginString(aPrincipal).get()));
      return false;
    case WebContentIsolationStrategy::IsolateEverything:
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("Isolating '%s' as isolation is enabled for all sites",
               OriginString(aPrincipal).get()));
      return true;
    case WebContentIsolationStrategy::IsolateHighValue: {
      RefPtr<PermissionManager> perms = PermissionManager::GetInstance();
      if (NS_WARN_IF(!perms)) {
        // If we somehow have no permission manager, fall back to the safest
        // option, and try to isolate.
        MOZ_ASSERT_UNREACHABLE("Permission manager is missing");
        return true;
      }

      static constexpr nsLiteralCString kHighValuePermissions[] = {
          mozilla::dom::kHighValueCOOPPermission,
          mozilla::dom::kHighValueHasSavedLoginPermission,
          mozilla::dom::kHighValueIsLoggedInPermission,
      };

      for (const auto& type : kHighValuePermissions) {
        uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
        if (NS_SUCCEEDED(perms->TestPermissionFromPrincipal(aPrincipal, type,
                                                            &permission)) &&
            permission == nsIPermissionManager::ALLOW_ACTION) {
          MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
                  ("Isolating '%s' due to high-value permission '%s'",
                   OriginString(aPrincipal).get(), type.get()));
          return true;
        }
      }
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("Not isolating '%s' as it is not high-value",
               OriginString(aPrincipal).get()));
      return false;
    }
    default:
      // An invalid pref value was used. Fall back to the safest option and
      // isolate everything.
      NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy");
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("Isolating '%s' due to unknown strategy pref value",
               OriginString(aPrincipal).get()));
      return true;
  }
}

enum class WebProcessType {
  Web,
  WebIsolated,
  WebCoopCoep,
};

}  // namespace

Result<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation(
    CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow,
    nsIURI* aChannelCreationURI, nsIChannel* aChannel,
    const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch,
    bool aForNewTab, uint32_t aLoadStateLoadType,
    const Maybe<uint64_t>& aChannelId,
    const Maybe<nsCString>& aRemoteTypeOverride) {
  // Get the final principal, used to select which process to load into.
  nsCOMPtr<nsIPrincipal> resultPrincipal;
  nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
      aChannel, getter_AddRefs(resultPrincipal));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
            ("failed to get channel result principal"));
    return Err(rv);
  }

  MOZ_LOG(
      gProcessIsolationLog, LogLevel::Verbose,
      ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s",
       OriginString(resultPrincipal).get(),
       aChannelCreationURI->GetSpecOrDefault().get(),
       aParentWindow ? aParentWindow->GetDocumentURI()->GetSpecOrDefault().get()
                     : ""));

  // If we're loading a null principal, we can't easily make a process
  // selection decision off ot it. Instead, we'll use our null principal's
  // precursor principal to make process selection decisions.
  bool principalIsSandboxed = false;
  nsCOMPtr<nsIPrincipal> resultOrPrecursor(resultPrincipal);
  if (nsCOMPtr<nsIPrincipal> precursor =
          resultOrPrecursor->GetPrecursorPrincipal()) {
    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
            ("using null principal precursor origin %s",
             OriginString(precursor).get()));
    resultOrPrecursor = precursor;
    principalIsSandboxed = true;
  }

  NavigationIsolationOptions options;
  options.mReplaceBrowsingContext = aHasCOOPMismatch;

  // Check if this load has an explicit remote type override. This is used to
  // perform an about:blank load within a specific content process.
  if (aRemoteTypeOverride) {
    MOZ_DIAGNOSTIC_ASSERT(
        NS_IsAboutBlank(aChannelCreationURI),
        "Should only have aRemoteTypeOverride for about:blank URIs");
    if (NS_WARN_IF(!resultPrincipal->GetIsNullPrincipal())) {
      MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
              ("invalid remote type override on non-null principal"));
      return Err(NS_ERROR_DOM_SECURITY_ERR);
    }

    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
            ("using remote type override (%s) for load",
             aRemoteTypeOverride->get()));
    options.mRemoteType = *aRemoteTypeOverride;
    return options;
  }

  // First, check for any special cases which should be handled using the
  // channel creation URI, and handle them.
  auto behavior = IsolationBehaviorForURI(aChannelCreationURI, aParentWindow,
                                          /* aForChannelCreationURI */ true);
  MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
          ("Channel Creation Isolation Behavior: %s",
           IsolationBehaviorName(behavior)));

  // In the about:reader special case, we want to fetch the relevant information
  // from the URI, an then treat it as a normal web content load.
  if (behavior == IsolationBehavior::AboutReader) {
    if (RefPtr<BasePrincipal> readerURIPrincipal = GetAboutReaderURLPrincipal(
            aChannelCreationURI, resultOrPrecursor->OriginAttributesRef())) {
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("using about:reader's url origin %s",
               OriginString(readerURIPrincipal).get()));
      resultOrPrecursor = readerURIPrincipal;
    }
    behavior = IsolationBehavior::WebContent;
    // If loading an about:reader page in a BrowsingContext which shares a
    // BrowsingContextGroup with other toplevel documents, replace the
    // BrowsingContext to destroy any references.
    //
    // With SHIP we can apply this to all about:reader loads, but otherwise
    // do it at least where there are opener/group relationships.
    if (mozilla::SessionHistoryInParent() ||
        aTopBC->Group()->Toplevels().Length() > 1) {
      options.mReplaceBrowsingContext = true;
    }
  }

  // If we're running in a test which is requesting that system-triggered
  // about:blank documents load within the current process, override the
  // behaviour for loads which meet the requirements.
  if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() &&
      NS_IsAboutBlank(aChannelCreationURI)) {
    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
        resultOrPrecursor->GetIsNullPrincipal()) {
      MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
              ("Forcing system-principal triggered about:blank load to "
               "complete in the current process"));
      behavior = IsolationBehavior::Anywhere;
    }
  }

  // If we're loading for a specific extension, we'll need to perform a
  // BCG-switching load to get our toplevel extension window in the correct
  // BrowsingContextGroup.
  if (auto* addonPolicy =
          BasePrincipal::Cast(resultOrPrecursor)->AddonPolicy()) {
    if (aParentWindow) {
      // As a temporary measure, extension iframes must be loaded within the
      // same process as their parent document.
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("Loading extension subframe in same process as parent"));
      behavior = IsolationBehavior::Inherit;
    } else {
      MOZ_LOG(
          gProcessIsolationLog, LogLevel::Verbose,
          ("Found extension frame with addon policy. Will use group id %" PRIx64
           " (currentId: %" PRIx64 ")",
           addonPolicy->GetBrowsingContextGroupId(), aTopBC->Group()->Id()));
      behavior = IsolationBehavior::Extension;
      if (aTopBC->Group()->Id() != addonPolicy->GetBrowsingContextGroupId()) {
        options.mReplaceBrowsingContext = true;
        options.mSpecificGroupId = addonPolicy->GetBrowsingContextGroupId();
      }
    }
  }

  // Do a second run of `GetIsolationBehavior`, this time using the
  // principal's URI to handle additional special cases such as the file and
  // privilegedmozilla content process.
  if (behavior == IsolationBehavior::WebContent) {
    if (resultOrPrecursor->IsSystemPrincipal()) {
      // We're loading something with a system principal which isn't caught in
      // one of our other edge-cases. If the load started in the parent process,
      // and it's safe for it to end in the parent process, we should finish the
      // load there.
      bool isUIResource = false;
      if (aCurrentRemoteType.IsEmpty() &&
          (aChannelCreationURI->SchemeIs("about") ||
           (NS_SUCCEEDED(NS_URIChainHasFlags(
                aChannelCreationURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                &isUIResource)) &&
            isUIResource))) {
        behavior = IsolationBehavior::Parent;
      } else {
        // In general, we don't want to load documents with a system principal
        // in a content process, however we need to in some cases, such as when
        // loading blob: URLs created by system code. We can force the load to
        // finish in a content process instead.
        behavior = IsolationBehavior::ForceWebRemoteType;
      }
    } else if (nsCOMPtr<nsIURI> principalURI = resultOrPrecursor->GetURI()) {
      behavior = IsolationBehaviorForURI(principalURI, aParentWindow,
                                         /* aForChannelCreationURI */ false);
    }
  }

  // If we're currently loaded in the extension process, and are going to switch
  // to some other remote type, make sure we leave the extension's BCG which we
  // may have entered earlier to separate extension and non-extension BCGs from
  // each-other.
  if (!aParentWindow && aCurrentRemoteType == EXTENSION_REMOTE_TYPE &&
      behavior != IsolationBehavior::Extension &&
      behavior != IsolationBehavior::Anywhere) {
    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
            ("Forcing BC replacement to leave extension BrowsingContextGroup "
             "%" PRIx64 " on navigation",
             aTopBC->Group()->Id()));
    options.mReplaceBrowsingContext = true;
  }

  // We don't want to load documents with sandboxed null principals, like
  // `data:` URIs, in the parent process, even if they were created by a
  // document which would otherwise be loaded in the parent process.
  if (behavior == IsolationBehavior::Parent && principalIsSandboxed) {
    MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
            ("Ensuring sandboxed null-principal load doesn't occur in the "
             "parent process"));
    behavior = IsolationBehavior::ForceWebRemoteType;
  }

  MOZ_LOG(
      gProcessIsolationLog, LogLevel::Debug,
      ("Using IsolationBehavior %s for %s (original uri %s)",
       IsolationBehaviorName(behavior), OriginString(resultOrPrecursor).get(),
       aChannelCreationURI->GetSpecOrDefault().get()));

  // Check if we can put the previous document into the BFCache.
  if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 &&
      !aForNewTab && !aParentWindow && !aTopBC->HadOriginalOpener() &&
      behavior != IsolationBehavior::Parent &&
      (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() ||
       behavior != IsolationBehavior::Extension) &&
      !aCurrentRemoteType.IsEmpty() &&
      aTopBC->GetHasLoadedNonInitialDocument() &&
      (aLoadStateLoadType == LOAD_NORMAL ||
       aLoadStateLoadType == LOAD_HISTORY || aLoadStateLoadType == LOAD_LINK ||
       aLoadStateLoadType == LOAD_STOP_CONTENT ||
       aLoadStateLoadType == LOAD_STOP_CONTENT_AND_REPLACE) &&
      (!aTopBC->GetActiveSessionHistoryEntry() ||
       aTopBC->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) {
    if (nsCOMPtr<nsIURI> uri = aTopBC->GetCurrentURI()) {
      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
              ("current uri: %s", uri->GetSpecOrDefault().get()));
    }
    options.mTryUseBFCache =
        aTopBC->AllowedInBFCache(aChannelId, aChannelCreationURI);
    if (options.mTryUseBFCache) {
      options.mReplaceBrowsingContext = true;
      options.mActiveSessionHistoryEntry =
          aTopBC->GetActiveSessionHistoryEntry();
    }
  }

  // If the load has any special remote type handling, do so at this point.
  if (behavior != IsolationBehavior::WebContent) {
    switch (behavior) {
      case IsolationBehavior::ForceWebRemoteType:
        options.mRemoteType = WEB_REMOTE_TYPE;
        break;
      case IsolationBehavior::PrivilegedAbout:
        // The privileged about: content process cannot be disabled, as it
        // causes various actors to break.
        options.mRemoteType = PRIVILEGEDABOUT_REMOTE_TYPE;
        break;
      case IsolationBehavior::Extension:
        if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
          options.mRemoteType = EXTENSION_REMOTE_TYPE;
        } else {
          options.mRemoteType = NOT_REMOTE_TYPE;
        }
        break;
      case IsolationBehavior::File:
        if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
          options.mRemoteType = FILE_REMOTE_TYPE;
        } else {
          options.mRemoteType = WEB_REMOTE_TYPE;
        }
        break;
      case IsolationBehavior::PrivilegedMozilla:
        options.mRemoteType = PRIVILEGEDMOZILLA_REMOTE_TYPE;
        break;
      case IsolationBehavior::Parent:
        options.mRemoteType = NOT_REMOTE_TYPE;
        break;
      case IsolationBehavior::Anywhere:
        options.mRemoteType = aCurrentRemoteType;
        break;
      case IsolationBehavior::Inherit:
        MOZ_DIAGNOSTIC_ASSERT(aParentWindow);
        options.mRemoteType = aParentWindow->GetRemoteType();
        break;

      case IsolationBehavior::WebContent:
      case IsolationBehavior::AboutReader:
        MOZ_ASSERT_UNREACHABLE();
        return Err(NS_ERROR_UNEXPECTED);

      case IsolationBehavior::Error:
        return Err(NS_ERROR_UNEXPECTED);
    }

    if (options.mRemoteType != aCurrentRemoteType &&
        (options.mRemoteType.IsEmpty() || aCurrentRemoteType.IsEmpty())) {
      options.mReplaceBrowsingContext = true;
    }

    MOZ_LOG(
        gProcessIsolationLog, LogLevel::Debug,
        ("Selecting specific remote type (%s) due to a special case isolation "
         "behavior %s",
         options.mRemoteType.get(), IsolationBehaviorName(behavior)));
    return options;
  }

  // At this point we're definitely not going to be loading in the parent
  // process anymore, so we're definitely going to be replacing BrowsingContext
  // if we're in the parent process.
  if (aCurrentRemoteType.IsEmpty()) {
    MOZ_ASSERT(!aParentWindow);
    options.mReplaceBrowsingContext = true;
  }

  nsAutoCString siteOriginNoSuffix;
  MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix));

  // Check if we've already loaded a document with the given principal in some
  // content process. We want to finish the load in the same process in that
  // case.
  //
  // The exception to that is with extension loads and the system principal,
  // where we may have multiple documents with the same principal in different
  // processes. Those have been handled above, and will not be reaching here.
  //
  // If we're doing a replace load or opening a new tab, we won't be staying in
  // the same BrowsingContextGroup, so ignore this step.
  if (!options.mReplaceBrowsingContext && !aForNewTab) {
    // Helper for efficiently determining if a given origin is same-site. This
    // will attempt to do a fast equality check, and will only fall back to
    // computing the site-origin for content principals.
    auto principalIsSameSite = [&](nsIPrincipal* aDocumentPrincipal) -> bool {
      // If we're working with a null principal with a precursor, compare
      // precursors, as `resultOrPrecursor` has already been stripped to its
      // precursor.
      nsCOMPtr<nsIPrincipal> documentPrincipal(aDocumentPrincipal);
      if (nsCOMPtr<nsIPrincipal> precursor =
              documentPrincipal->GetPrecursorPrincipal()) {
        documentPrincipal = precursor;
      }

      // First, attempt to use `Equals` to compare principals, and if that
      // fails compare siteOrigins. Only compare siteOrigin for content
      // principals, as non-content principals will never have siteOrigin !=
      // origin.
      nsAutoCString documentSiteOrigin;
      return resultOrPrecursor->Equals(documentPrincipal) ||
             (documentPrincipal->GetIsContentPrincipal() &&
              resultOrPrecursor->GetIsContentPrincipal() &&
              NS_SUCCEEDED(documentPrincipal->GetSiteOriginNoSuffix(
                  documentSiteOrigin)) &&
              documentSiteOrigin == siteOriginNoSuffix);
    };

    // XXX: Consider also checking in-flight process switches to see if any have
    // matching principals?
    AutoTArray<RefPtr<BrowsingContext>, 8> contexts;
    aTopBC->Group()->GetToplevels(contexts);
    while (!contexts.IsEmpty()) {
      auto bc = contexts.PopLastElement();
      for (const auto& wc : bc->GetWindowContexts()) {
        WindowGlobalParent* wgp = wc->Canonical();

        // Check if this WindowGlobalParent has the given resultPrincipal, and
        // if it does, we need to load in that process.
        if (!wgp->GetRemoteType().IsEmpty() &&
            principalIsSameSite(wgp->DocumentPrincipal())) {
          MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
                  ("Found existing frame with matching principal "
                   "(remoteType:(%s), origin:%s)",
                   PromiseFlatCString(wgp->GetRemoteType()).get(),
                   OriginString(wgp->DocumentPrincipal()).get()));
          options.mRemoteType = wgp->GetRemoteType();
          return options;
        }

        // Also enumerate over this WindowContexts' subframes.
        contexts.AppendElements(wc->Children());
      }
    }
  }

  nsAutoCString originSuffix;
  OriginAttributes attrs = resultOrPrecursor->OriginAttributesRef();
  attrs.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN |
                        OriginAttributes::STRIP_PARITION_KEY);
  attrs.CreateSuffix(originSuffix);

  WebProcessType webProcessType = WebProcessType::Web;
  if (ShouldIsolateSite(resultOrPrecursor, aTopBC)) {
    webProcessType = WebProcessType::WebIsolated;
  }

  // Check if we should be loading in a webCOOP+COEP remote type due to our COOP
  // status.
  nsILoadInfo::CrossOriginOpenerPolicy coop =
      nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
  if (aParentWindow) {
    coop = aTopBC->GetOpenerPolicy();
  } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
                 do_QueryInterface(aChannel)) {
    MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
  }
  if (coop ==
      nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) {
    webProcessType = WebProcessType::WebCoopCoep;

    // If we're changing BrowsingContext, and are going to end up within a
    // webCOOP+COEP group, ensure we use a cross-origin isolated BCG ID.
    if (options.mReplaceBrowsingContext) {
      MOZ_ASSERT(!options.mSpecificGroupId,
                 "overriding previously-specified BCG ID");
      options.mSpecificGroupId = BrowsingContextGroup::CreateId(
          /* aPotentiallyCrossOriginIsolated */ true);
    }
  }

  switch (webProcessType) {
    case WebProcessType::Web:
      options.mRemoteType = WEB_REMOTE_TYPE;
      break;
    case WebProcessType::WebIsolated:
      options.mRemoteType =
          FISSION_WEB_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix;
      break;
    case WebProcessType::WebCoopCoep:
      options.mRemoteType =
          WITH_COOP_COEP_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix;
      break;
  }
  return options;
}

void AddHighValuePermission(nsIPrincipal* aResultPrincipal,
                            const nsACString& aPermissionType) {
  RefPtr<PermissionManager> perms = PermissionManager::GetInstance();
  if (NS_WARN_IF(!perms)) {
    return;
  }

  // We can't act on non-content principals, so if the load was sandboxed, try
  // to use the unsandboxed precursor principal to add the highValue permission.
  nsCOMPtr<nsIPrincipal> resultOrPrecursor(aResultPrincipal);
  if (!aResultPrincipal->GetIsContentPrincipal()) {
    resultOrPrecursor = aResultPrincipal->GetPrecursorPrincipal();
    if (!resultOrPrecursor) {
      return;
    }
  }

  // Use the site-origin principal as we want to add the permission for the
  // entire site, rather than a specific subdomain, as process isolation acts on
  // a site granularity.
  nsAutoCString siteOrigin;
  if (NS_FAILED(resultOrPrecursor->GetSiteOrigin(siteOrigin))) {
    return;
  }

  nsCOMPtr<nsIPrincipal> sitePrincipal =
      BasePrincipal::CreateContentPrincipal(siteOrigin);
  if (!sitePrincipal || !sitePrincipal->GetIsContentPrincipal()) {
    return;
  }

  MOZ_LOG(dom::gProcessIsolationLog, LogLevel::Verbose,
          ("Adding %s Permission for site '%s'", aPermissionType.BeginReading(),
           siteOrigin.get()));

  uint32_t expiration = 0;
  if (aPermissionType.Equals(mozilla::dom::kHighValueCOOPPermission)) {
    expiration = StaticPrefs::fission_highValue_coop_expiration();
  } else if (aPermissionType.Equals(
                 mozilla::dom::kHighValueHasSavedLoginPermission) ||
             aPermissionType.Equals(
                 mozilla::dom::kHighValueIsLoggedInPermission)) {
    expiration = StaticPrefs::fission_highValue_login_expiration();
  } else {
    MOZ_ASSERT_UNREACHABLE("Unknown permission type");
  }

  // XXX: Would be nice if we could use `TimeStamp` here, but there's
  // unfortunately no convenient way to recover a time in milliseconds since the
  // unix epoch from `TimeStamp`.
  int64_t expirationTime =
      (PR_Now() / PR_USEC_PER_MSEC) + (int64_t(expiration) * PR_MSEC_PER_SEC);
  Unused << perms->AddFromPrincipal(
      sitePrincipal, aPermissionType, nsIPermissionManager::ALLOW_ACTION,
      nsIPermissionManager::EXPIRE_TIME, expirationTime);
}

void AddHighValuePermission(const nsACString& aOrigin,
                            const nsACString& aPermissionType) {
  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
  nsCOMPtr<nsIPrincipal> principal;
  nsresult rv =
      ssm->CreateContentPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  AddHighValuePermission(principal, aPermissionType);
}

bool IsIsolateHighValueSiteEnabled() {
  return mozilla::FissionAutostart() &&
         WebContentIsolationStrategy(
             StaticPrefs::fission_webContentIsolationStrategy()) ==
             WebContentIsolationStrategy::IsolateHighValue;
}

}  // namespace mozilla::dom