toolkit/components/antitracking/AntiTrackingCommon.cpp
author Andrea Marchesini <amarchesini@mozilla.com>
Sun, 17 Mar 2019 06:55:50 +0000
changeset 464493 8ee97c045359ecd958e1032b0d6617741f20cf00
parent 463113 3cddc7cd4da5fcd26b93becb55a028621ab68f64
child 468331 c3bddd2b3f0a697179c98cb1562c6e21d57e93d8
permissions -rw-r--r--
Bug 1535799 - nsIHttpChannel.isTrackingResource should be a method, r=Ehsan Differential Revision: https://phabricator.services.mozilla.com/D23765

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 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 "AntiTrackingCommon.h"

#include "mozilla/dom/ContentChild.h"
#include "mozilla/ipc/MessageChannel.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Logging.h"
#include "mozilla/MruCache.h"
#include "mozilla/Pair.h"
#include "mozilla/StaticPrefs.h"
#include "mozIThirdPartyUtil.h"
#include "nsContentUtils.h"
#include "nsGlobalWindowInner.h"
#include "nsCookiePermission.h"
#include "nsICookieService.h"
#include "nsIDocShell.h"
#include "nsIHttpChannelInternal.h"
#include "nsIIOService.h"
#include "nsIParentChannel.h"
#include "nsIPermission.h"
#include "nsPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsIURI.h"
#include "nsIURIFixup.h"
#include "nsIURL.h"
#include "nsIWebProgressListener.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsPrintfCString.h"
#include "nsScriptSecurityManager.h"
#include "nsSandboxFlags.h"
#include "prtime.h"

#define ANTITRACKING_PERM_KEY "3rdPartyStorage"

using namespace mozilla;
using mozilla::dom::ContentChild;
using mozilla::dom::Document;

static LazyLogModule gAntiTrackingLog("AntiTracking");
static const nsCString::size_type sMaxSpecLength = 128;
static const uint32_t kMaxConsoleOutputDelayMs = 100;

#define LOG(format) MOZ_LOG(gAntiTrackingLog, mozilla::LogLevel::Debug, format)

#define LOG_SPEC(format, uri)                                       \
  PR_BEGIN_MACRO                                                    \
  if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) {   \
    nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));           \
    _specStr.Truncate(std::min(_specStr.Length(), sMaxSpecLength)); \
    if (uri) {                                                      \
      _specStr = uri->GetSpecOrDefault();                           \
    }                                                               \
    const char* _spec = _specStr.get();                             \
    LOG(format);                                                    \
  }                                                                 \
  PR_END_MACRO

namespace {

UniquePtr<nsTArray<AntiTrackingCommon::AntiTrackingSettingsChangedCallback>>
    gSettingsChangedCallbacks;

bool GetParentPrincipalAndTrackingOrigin(
    nsGlobalWindowInner* a3rdPartyTrackingWindow,
    nsIPrincipal** aTopLevelStoragePrincipal, nsACString& aTrackingOrigin,
    nsIURI** aTrackingURI, nsIPrincipal** aTrackingPrincipal) {
  if (!nsContentUtils::IsThirdPartyTrackingResourceWindow(
          a3rdPartyTrackingWindow)) {
    return false;
  }

  Document* doc = a3rdPartyTrackingWindow->GetDocument();
  // Make sure storage access isn't disabled
  if (doc && (doc->StorageAccessSandboxed() ||
              nsContentUtils::IsInPrivateBrowsing(doc))) {
    return false;
  }

  // Now we need the principal and the origin of the parent window.
  nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal =
      a3rdPartyTrackingWindow->GetTopLevelStorageAreaPrincipal();
  if (!topLevelStoragePrincipal) {
    LOG(("No top-level storage area principal at hand"));
    return false;
  }

  // Let's take the principal and the origin of the tracker.
  nsCOMPtr<nsIPrincipal> trackingPrincipal =
      a3rdPartyTrackingWindow->GetPrincipal();
  if (NS_WARN_IF(!trackingPrincipal)) {
    return false;
  }

  nsCOMPtr<nsIURI> trackingURI;
  nsresult rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  rv = trackingPrincipal->GetOriginNoSuffix(aTrackingOrigin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  topLevelStoragePrincipal.forget(aTopLevelStoragePrincipal);
  if (aTrackingURI) {
    trackingURI.forget(aTrackingURI);
  }
  if (aTrackingPrincipal) {
    trackingPrincipal.forget(aTrackingPrincipal);
  }
  return true;
};

void CreatePermissionKey(const nsCString& aTrackingOrigin,
                         nsACString& aPermissionKey) {
  MOZ_ASSERT(aPermissionKey.IsEmpty());

  static const nsLiteralCString prefix =
      NS_LITERAL_CSTRING(ANTITRACKING_PERM_KEY "^");

  aPermissionKey.SetCapacity(prefix.Length() + aTrackingOrigin.Length());
  aPermissionKey.Append(prefix);
  aPermissionKey.Append(aTrackingOrigin);
}

void CreatePermissionKey(const nsCString& aTrackingOrigin,
                         const nsCString& aGrantedOrigin,
                         nsACString& aPermissionKey) {
  MOZ_ASSERT(aPermissionKey.IsEmpty());

  if (aTrackingOrigin == aGrantedOrigin) {
    CreatePermissionKey(aTrackingOrigin, aPermissionKey);
    return;
  }

  static const nsLiteralCString prefix =
      NS_LITERAL_CSTRING(ANTITRACKING_PERM_KEY "^");

  aPermissionKey.SetCapacity(prefix.Length() + 1 + aTrackingOrigin.Length() +
                             aGrantedOrigin.Length());
  aPermissionKey.Append(prefix);
  aPermissionKey.Append(aTrackingOrigin);
  aPermissionKey.AppendLiteral("^");
  aPermissionKey.Append(aGrantedOrigin);
}

// This internal method returns ACCESS_DENY if the access is denied,
// ACCESS_DEFAULT if unknown, some other access code if granted.
uint32_t CheckCookiePermissionForPrincipal(nsICookieSettings* aCookieSettings,
                                           nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(aCookieSettings);
  MOZ_ASSERT(aPrincipal);

  uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
  if (!aPrincipal->GetIsCodebasePrincipal()) {
    return cookiePermission;
  }

  nsresult rv =
      aCookieSettings->CookiePermission(aPrincipal, &cookiePermission);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nsICookiePermission::ACCESS_DEFAULT;
  }

  // If we have a custom cookie permission, let's use it.
  return cookiePermission;
}

int32_t CookiesBehavior(Document* aTopLevelDocument,
                        Document* a3rdPartyDocument) {
  MOZ_ASSERT(aTopLevelDocument);
  MOZ_ASSERT(a3rdPartyDocument);

  // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
  // (See Bug 1406675 for rationale).
  if (BasePrincipal::Cast(aTopLevelDocument->NodePrincipal())->AddonPolicy()) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  if (BasePrincipal::Cast(a3rdPartyDocument->NodePrincipal())->AddonPolicy()) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  return a3rdPartyDocument->CookieSettings()->GetCookieBehavior();
}

int32_t CookiesBehavior(nsILoadInfo* aLoadInfo,
                        nsIPrincipal* aTopLevelPrincipal,
                        nsIURI* a3rdPartyURI) {
  MOZ_ASSERT(aLoadInfo);
  MOZ_ASSERT(aTopLevelPrincipal);
  MOZ_ASSERT(a3rdPartyURI);

  // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
  // (See Bug 1406675 for rationale).
  if (BasePrincipal::Cast(aTopLevelPrincipal)->AddonPolicy()) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  // This is semantically equivalent to the principal having a AddonPolicy().
  bool is3rdPartyMozExt = false;
  if (NS_SUCCEEDED(
          a3rdPartyURI->SchemeIs("moz-extension", &is3rdPartyMozExt)) &&
      is3rdPartyMozExt) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  nsCOMPtr<nsICookieSettings> cookieSettings;
  nsresult rv = aLoadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nsICookieService::BEHAVIOR_REJECT;
  }

  return cookieSettings->GetCookieBehavior();
}

int32_t CookiesBehavior(nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(aPrincipal);

  // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
  // (See Bug 1406675 for rationale).
  if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  return StaticPrefs::network_cookie_cookieBehavior();
}

struct ContentBlockingAllowListKey {
  ContentBlockingAllowListKey() : mHash(mozilla::HashGeneric(uintptr_t(0))) {}

  // Ensure that we compute a different hash for window and channel pointers of
  // the same numeric value, in the off chance that we get unlucky and encounter
  // a case where the allocator reallocates a window object where a channel used
  // to live and vice versa.
  explicit ContentBlockingAllowListKey(nsPIDOMWindowInner* aWindow)
      : mHash(mozilla::AddToHash(uintptr_t(aWindow),
                                 mozilla::HashString("window"))) {}
  explicit ContentBlockingAllowListKey(nsIChannel* aChannel)
      : mHash(mozilla::AddToHash(uintptr_t(aChannel),
                                 mozilla::HashString("channel"))) {}

  ContentBlockingAllowListKey(const ContentBlockingAllowListKey& aRHS)
      : mHash(aRHS.mHash) {}

  bool operator==(const ContentBlockingAllowListKey& aRHS) const {
    return mHash == aRHS.mHash;
  }

  HashNumber GetHash() const { return mHash; }

 private:
  HashNumber mHash;
};

struct ContentBlockingAllowListEntry {
  ContentBlockingAllowListEntry() : mResult(false) {}
  ContentBlockingAllowListEntry(nsPIDOMWindowInner* aWindow, bool aResult)
      : mKey(aWindow), mResult(aResult) {}
  ContentBlockingAllowListEntry(nsIChannel* aChannel, bool aResult)
      : mKey(aChannel), mResult(aResult) {}

  ContentBlockingAllowListKey mKey;
  bool mResult;
};

struct ContentBlockingAllowListCache
    : MruCache<ContentBlockingAllowListKey, ContentBlockingAllowListEntry,
               ContentBlockingAllowListCache> {
  static HashNumber Hash(const ContentBlockingAllowListKey& aKey) {
    return aKey.GetHash();
  }
  static bool Match(const ContentBlockingAllowListKey& aKey,
                    const ContentBlockingAllowListEntry& aValue) {
    return aValue.mKey == aKey;
  }
};

ContentBlockingAllowListCache& GetContentBlockingAllowListCache() {
  static bool initialized = false;
  static ContentBlockingAllowListCache cache;
  if (!initialized) {
    AntiTrackingCommon::OnAntiTrackingSettingsChanged([&] {
      // Drop everything in the cache, since the result of content blocking
      // allow list checks may change past this point.
      cache.Clear();
    });
    initialized = true;
  }
  return cache;
}

bool CheckContentBlockingAllowList(nsIURI* aTopWinURI,
                                   bool aIsPrivateBrowsing) {
  bool isAllowed = false;
  nsresult rv = AntiTrackingCommon::IsOnContentBlockingAllowList(
      aTopWinURI, aIsPrivateBrowsing, AntiTrackingCommon::eStorageChecks,
      isAllowed);
  if (NS_SUCCEEDED(rv) && isAllowed) {
    LOG_SPEC(
        ("The top-level window (%s) is on the content blocking allow list, "
         "bail out early",
         _spec),
        aTopWinURI);
    return true;
  }
  if (NS_FAILED(rv)) {
    LOG_SPEC(
        ("Checking the content blocking allow list for %s failed with %" PRIx32,
         _spec, static_cast<uint32_t>(rv)),
        aTopWinURI);
  }
  return false;
}

bool CheckContentBlockingAllowList(nsPIDOMWindowInner* aWindow) {
  ContentBlockingAllowListKey cacheKey(aWindow);
  auto entry = GetContentBlockingAllowListCache().Lookup(cacheKey);
  if (entry) {
    // We've recently performed a content blocking allow list check for this
    // window, so let's quickly return the answer instead of continuing with the
    // rest of this potentially expensive computation.
    return entry.Data().mResult;
  }

  nsPIDOMWindowOuter* top = aWindow->GetScriptableTop();
  if (top) {
    nsIURI* topWinURI = top->GetDocumentURI();
    Document* doc = top->GetExtantDoc();
    bool isPrivateBrowsing =
        doc ? nsContentUtils::IsInPrivateBrowsing(doc) : false;

    const bool result =
        CheckContentBlockingAllowList(topWinURI, isPrivateBrowsing);

    entry.Set(ContentBlockingAllowListEntry(aWindow, result));

    return result;
  }

  LOG(
      ("Could not check the content blocking allow list because the top "
       "window wasn't accessible"));
  entry.Set(ContentBlockingAllowListEntry(aWindow, false));
  return false;
}

bool CheckContentBlockingAllowList(nsIHttpChannel* aChannel) {
  ContentBlockingAllowListKey cacheKey(aChannel);
  auto entry = GetContentBlockingAllowListCache().Lookup(cacheKey);
  if (entry) {
    // We've recently performed a content blocking allow list check for this
    // channel, so let's quickly return the answer instead of continuing with
    // the rest of this potentially expensive computation.
    return entry.Data().mResult;
  }

  nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel);
  if (chan) {
    nsCOMPtr<nsIURI> topWinURI;
    nsresult rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
    if (NS_SUCCEEDED(rv)) {
      const bool result = CheckContentBlockingAllowList(
          topWinURI, NS_UsePrivateBrowsing(aChannel));

      entry.Set(ContentBlockingAllowListEntry(aChannel, result));

      return result;
    }
  }

  LOG(
      ("Could not check the content blocking allow list because the top "
       "window wasn't accessible"));
  entry.Set(ContentBlockingAllowListEntry(aChannel, false));
  return false;
}

void ReportBlockingToConsole(nsPIDOMWindowOuter* aWindow, nsIURI* aURI,
                             uint32_t aRejectedReason) {
  MOZ_ASSERT(aWindow && aURI);
  MOZ_ASSERT(
      aRejectedReason == 0 ||
      aRejectedReason ==
          nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
      aRejectedReason ==
          nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
      aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
      aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);

  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
  if (NS_WARN_IF(!docShell)) {
    return;
  }

  RefPtr<Document> doc = docShell->GetDocument();
  if (NS_WARN_IF(!doc)) {
    return;
  }

  nsAutoString sourceLine;
  uint32_t lineNumber = 0, columnNumber = 0;
  JSContext* cx = nsContentUtils::GetCurrentJSContext();
  if (cx) {
    nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
  }

  nsCOMPtr<nsIURI> uri(aURI);

  nsresult rv = NS_DispatchToCurrentThreadQueue(
      NS_NewRunnableFunction(
          "ReportBlockingToConsoleDelayed",
          [doc, sourceLine, lineNumber, columnNumber, uri, aRejectedReason]() {
            const char* message = nullptr;
            nsAutoCString category;
            switch (aRejectedReason) {
              case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION:
                message = "CookieBlockedByPermission";
                category = NS_LITERAL_CSTRING("cookieBlockedPermission");
                break;

              case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER:
                message = "CookieBlockedTracker";
                category = NS_LITERAL_CSTRING("cookieBlockedTracker");
                break;

              case nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL:
                message = "CookieBlockedAll";
                category = NS_LITERAL_CSTRING("cookieBlockedAll");
                break;

              case nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN:
                message = "CookieBlockedForeign";
                category = NS_LITERAL_CSTRING("cookieBlockedForeign");
                break;

              default:
                return;
            }

            MOZ_ASSERT(message);

            // Strip the URL of any possible username/password and make it ready
            // to be presented in the UI.
            nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup();
            NS_ENSURE_TRUE_VOID(urifixup);
            nsCOMPtr<nsIURI> exposableURI;
            nsresult rv =
                urifixup->CreateExposableURI(uri, getter_AddRefs(exposableURI));
            NS_ENSURE_SUCCESS_VOID(rv);

            NS_ConvertUTF8toUTF16 spec(exposableURI->GetSpecOrDefault());
            const char16_t* params[] = {spec.get()};

            nsContentUtils::ReportToConsole(
                nsIScriptError::warningFlag, category, doc,
                nsContentUtils::eNECKO_PROPERTIES, message, params,
                ArrayLength(params), nullptr, sourceLine, lineNumber,
                columnNumber);
          }),
      kMaxConsoleOutputDelayMs, EventQueuePriority::Idle);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }
}

void ReportUnblockingToConsole(
    nsPIDOMWindowInner* aWindow, const nsAString& aTrackingOrigin,
    const nsAString& aGrantedOrigin,
    AntiTrackingCommon::StorageAccessGrantedReason aReason) {
  nsCOMPtr<nsIPrincipal> principal =
      nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
  if (NS_WARN_IF(!principal)) {
    return;
  }

  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
  if (NS_WARN_IF(!docShell)) {
    return;
  }

  RefPtr<Document> doc = docShell->GetDocument();
  if (NS_WARN_IF(!doc)) {
    return;
  }

  nsAutoString trackingOrigin(aTrackingOrigin);
  nsAutoString grantedOrigin(aGrantedOrigin);

  nsAutoString sourceLine;
  uint32_t lineNumber = 0, columnNumber = 0;
  JSContext* cx = nsContentUtils::GetCurrentJSContext();
  if (cx) {
    nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
  }

  nsresult rv = NS_DispatchToCurrentThreadQueue(
      NS_NewRunnableFunction(
          "ReportUnblockingToConsoleDelayed",
          [doc, principal, trackingOrigin, grantedOrigin, sourceLine,
           lineNumber, columnNumber, aReason]() {
            nsAutoString origin;
            nsresult rv = nsContentUtils::GetUTFOrigin(principal, origin);
            if (NS_WARN_IF(NS_FAILED(rv))) {
              return;
            }

            const char16_t* params[] = {origin.BeginReading(),
                                        trackingOrigin.BeginReading(),
                                        grantedOrigin.BeginReading()};
            const char* messageWithDifferentOrigin = nullptr;
            const char* messageWithSameOrigin = nullptr;

            switch (aReason) {
              case AntiTrackingCommon::eStorageAccessAPI:
                messageWithDifferentOrigin =
                    "CookieAllowedForOriginOnTrackerByStorageAccessAPI";
                messageWithSameOrigin =
                    "CookieAllowedForTrackerByStorageAccessAPI";
                break;

              case AntiTrackingCommon::eOpenerAfterUserInteraction:
                MOZ_FALLTHROUGH;
              case AntiTrackingCommon::eOpener:
                messageWithDifferentOrigin =
                    "CookieAllowedForOriginOnTrackerByHeuristic";
                messageWithSameOrigin = "CookieAllowedForTrackerByHeuristic";
                break;
            }

            if (trackingOrigin == grantedOrigin) {
              nsContentUtils::ReportToConsole(
                  nsIScriptError::warningFlag,
                  NS_LITERAL_CSTRING("Content Blocking"), doc,
                  nsContentUtils::eNECKO_PROPERTIES, messageWithSameOrigin,
                  params, 2, nullptr, sourceLine, lineNumber, columnNumber);
            } else {
              nsContentUtils::ReportToConsole(
                  nsIScriptError::warningFlag,
                  NS_LITERAL_CSTRING("Content Blocking"), doc,
                  nsContentUtils::eNECKO_PROPERTIES, messageWithDifferentOrigin,
                  params, 3, nullptr, sourceLine, lineNumber, columnNumber);
            }
          }),
      kMaxConsoleOutputDelayMs, EventQueuePriority::Idle);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }
}

already_AddRefed<nsPIDOMWindowOuter> GetTopWindow(nsPIDOMWindowInner* aWindow) {
  Document* document = aWindow->GetExtantDoc();
  if (!document) {
    return nullptr;
  }

  nsIChannel* channel = document->GetChannel();
  if (!channel) {
    return nullptr;
  }

  nsCOMPtr<nsPIDOMWindowOuter> pwin;
  auto* outer = nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
  if (outer) {
    pwin = outer->GetScriptableTop();
  }

  if (!pwin) {
    return nullptr;
  }

  return pwin.forget();
}

class TemporaryAccessGrantObserver final : public nsIObserver {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

  static void Create(nsPermissionManager* aPM, nsIPrincipal* aPrincipal,
                     const nsACString& aType) {
    nsCOMPtr<nsITimer> timer;
    RefPtr<TemporaryAccessGrantObserver> observer =
        new TemporaryAccessGrantObserver(aPM, aPrincipal, aType);
    nsresult rv = NS_NewTimerWithObserver(getter_AddRefs(timer), observer,
                                          24 * 60 * 60 * 1000,  // 24 hours
                                          nsITimer::TYPE_ONE_SHOT);

    if (NS_SUCCEEDED(rv)) {
      observer->SetTimer(timer);
    } else {
      timer->Cancel();
    }
  }

  void SetTimer(nsITimer* aTimer) {
    mTimer = aTimer;
    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
    }
  }

 private:
  TemporaryAccessGrantObserver(nsPermissionManager* aPM,
                               nsIPrincipal* aPrincipal,
                               const nsACString& aType)
      : mPM(aPM), mPrincipal(aPrincipal), mType(aType) {
    MOZ_ASSERT(XRE_IsParentProcess(),
               "Enforcing temporary access grant lifetimes can only be done in "
               "the parent process");
  }

  ~TemporaryAccessGrantObserver() = default;

 private:
  nsCOMPtr<nsITimer> mTimer;
  RefPtr<nsPermissionManager> mPM;
  nsCOMPtr<nsIPrincipal> mPrincipal;
  nsCString mType;
};

NS_IMPL_ISUPPORTS(TemporaryAccessGrantObserver, nsIObserver)

NS_IMETHODIMP
TemporaryAccessGrantObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                      const char16_t* aData) {
  if (strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0) {
    Unused << mPM->RemoveFromPrincipal(mPrincipal, mType);
  } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
    }
    if (mTimer) {
      mTimer->Cancel();
      mTimer = nullptr;
    }
  }

  return NS_OK;
}

class SettingsChangeObserver final : public nsIObserver {
  ~SettingsChangeObserver() = default;

 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

  static void PrivacyPrefChanged(const char* aPref = nullptr, void* = nullptr);

 private:
  static void RunAntiTrackingSettingsChangedCallbacks();
};

NS_IMPL_ISUPPORTS(SettingsChangeObserver, nsIObserver)

NS_IMETHODIMP SettingsChangeObserver::Observe(nsISupports* aSubject,
                                              const char* aTopic,
                                              const char16_t* aData) {
  if (!strcmp(aTopic, "xpcom-shutdown")) {
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    if (obs) {
      obs->RemoveObserver(this, "perm-added");
      obs->RemoveObserver(this, "perm-changed");
      obs->RemoveObserver(this, "perm-cleared");
      obs->RemoveObserver(this, "perm-deleted");
      obs->RemoveObserver(this, "xpcom-shutdown");

      Preferences::UnregisterPrefixCallback(
          SettingsChangeObserver::PrivacyPrefChanged,
          "browser.contentblocking.");
      Preferences::UnregisterPrefixCallback(
          SettingsChangeObserver::PrivacyPrefChanged, "network.cookie.");
      Preferences::UnregisterPrefixCallback(
          SettingsChangeObserver::PrivacyPrefChanged, "privacy.");

      gSettingsChangedCallbacks = nullptr;
    }
  } else {
    nsCOMPtr<nsIPermission> perm = do_QueryInterface(aSubject);
    if (perm) {
      nsAutoCString type;
      nsresult rv = perm->GetType(type);
      if (NS_WARN_IF(NS_FAILED(rv)) || type.Equals(USER_INTERACTION_PERM)) {
        // Ignore failures or notifications that have been sent because of
        // user interactions.
        return NS_OK;
      }
    }

    RunAntiTrackingSettingsChangedCallbacks();
  }

  return NS_OK;
}

// static
void SettingsChangeObserver::PrivacyPrefChanged(const char* aPref,
                                                void* aClosure) {
  RunAntiTrackingSettingsChangedCallbacks();
}

// static
void SettingsChangeObserver::RunAntiTrackingSettingsChangedCallbacks() {
  if (gSettingsChangedCallbacks) {
    for (auto& callback : *gSettingsChangedCallbacks) {
      callback();
    }
  }
}

}  // namespace

/* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
    nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aParentWindow,
    StorageAccessGrantedReason aReason,
    const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks) {
  MOZ_ASSERT(aParentWindow);

  nsCOMPtr<nsIURI> uri;
  aPrincipal->GetURI(getter_AddRefs(uri));
  if (NS_WARN_IF(!uri)) {
    LOG(("Can't get the URI from the principal"));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  nsAutoCString origin;
  nsresult rv = nsContentUtils::GetASCIIOrigin(uri, origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("Can't get the origin from the URI"));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  LOG(("Adding a first-party storage exception for %s...",
       PromiseFlatCString(origin).get()));

  if (StaticPrefs::network_cookie_cookieBehavior() !=
      nsICookieService::BEHAVIOR_REJECT_TRACKER) {
    LOG(
        ("Disabled by network.cookie.cookieBehavior pref (%d), bailing out "
         "early",
         StaticPrefs::network_cookie_cookieBehavior()));
    return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
  }

  if (CheckContentBlockingAllowList(aParentWindow)) {
    return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
  }

  nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal;
  nsCOMPtr<nsIURI> trackingURI;
  nsAutoCString trackingOrigin;
  nsCOMPtr<nsIPrincipal> trackingPrincipal;

  RefPtr<nsGlobalWindowInner> parentWindow =
      nsGlobalWindowInner::Cast(aParentWindow);
  nsGlobalWindowOuter* outerParentWindow =
      nsGlobalWindowOuter::Cast(parentWindow->GetOuterWindow());
  if (NS_WARN_IF(!outerParentWindow)) {
    LOG(("No outer window found for our parent window, bailing out early"));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  LOG(("The current resource is %s-party",
       outerParentWindow->IsTopLevelWindow() ? "first" : "third"));

  // We are a first party resource.
  if (outerParentWindow->IsTopLevelWindow()) {
    trackingOrigin = origin;
    trackingPrincipal = aPrincipal;
    rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      LOG(("Couldn't get the tracking principal URI"));
      return StorageAccessGrantPromise::CreateAndReject(false, __func__);
    }
    topLevelStoragePrincipal = parentWindow->GetPrincipal();
    if (NS_WARN_IF(!topLevelStoragePrincipal)) {
      LOG(("Top-level storage area principal not found, bailing out early"));
      return StorageAccessGrantPromise::CreateAndReject(false, __func__);
    }

    // We are a 3rd party source.
  } else if (!GetParentPrincipalAndTrackingOrigin(
                 parentWindow, getter_AddRefs(topLevelStoragePrincipal),
                 trackingOrigin, getter_AddRefs(trackingURI),
                 getter_AddRefs(trackingPrincipal))) {
    LOG(
        ("Error while computing the parent principal and tracking origin, "
         "bailing out early"));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow = outerParentWindow->GetTop();
  nsGlobalWindowOuter* topWindow = nsGlobalWindowOuter::Cast(topOuterWindow);
  if (NS_WARN_IF(!topWindow)) {
    LOG(("No top outer window."));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  nsPIDOMWindowInner* topInnerWindow = topWindow->GetCurrentInnerWindow();
  if (NS_WARN_IF(!topInnerWindow)) {
    LOG(("No top inner window."));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  // We hardcode this block reason since the first-party storage access
  // permission is granted for the purpose of blocking trackers.
  // Note that if aReason is eOpenerAfterUserInteraction and the
  // trackingPrincipal is not in a blacklist, we don't check the
  // user-interaction state, because it could be that the current process has
  // just sent the request to store the user-interaction permission into the
  // parent, without having received the permission itself yet.
  //
  // We define this as an enum, since without that MSVC fails to capturing this
  // name inside the lambda without the explicit capture and clang warns if
  // there is an explicit capture with -Wunused-lambda-capture.
  enum : uint32_t {
    blockReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
  };
  if ((aReason != eOpenerAfterUserInteraction ||
       nsContentUtils::IsURIInPrefList(trackingURI,
                                       "privacy.restrict3rdpartystorage."
                                       "userInteractionRequiredForHosts")) &&
      !HasUserInteraction(trackingPrincipal)) {
    LOG_SPEC(("Tracking principal (%s) hasn't been interacted with before, "
              "refusing to add a first-party storage permission to access it",
              _spec),
             trackingURI);
    NotifyBlockingDecision(aParentWindow, BlockingDecision::eBlock,
                           blockReason);
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  nsCOMPtr<nsPIDOMWindowOuter> pwin = GetTopWindow(parentWindow);
  if (!pwin) {
    LOG(("Couldn't get the top window"));
    return StorageAccessGrantPromise::CreateAndReject(false, __func__);
  }

  auto storePermission =
      [pwin, parentWindow, origin, trackingOrigin, trackingPrincipal,
       trackingURI, topInnerWindow, topLevelStoragePrincipal,
       aReason](int aAllowMode) -> RefPtr<StorageAccessGrantPromise> {
    nsAutoCString permissionKey;
    CreatePermissionKey(trackingOrigin, origin, permissionKey);

    // Let's store the permission in the current parent window.
    topInnerWindow->SaveStorageAccessGranted(permissionKey);

    // Let's inform the parent window.
    parentWindow->StorageAccessGranted();

    nsIChannel* channel =
        pwin->GetCurrentInnerWindow()->GetExtantDoc()->GetChannel();

    pwin->NotifyContentBlockingEvent(blockReason, channel, false, trackingURI);

    ReportUnblockingToConsole(parentWindow,
                              NS_ConvertUTF8toUTF16(trackingOrigin),
                              NS_ConvertUTF8toUTF16(origin), aReason);

    if (XRE_IsParentProcess()) {
      LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
           trackingOrigin.get(), origin.get()));

      return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(
                 topLevelStoragePrincipal, trackingPrincipal, trackingOrigin,
                 origin, aAllowMode)
          ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                 [](FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&&
                        aValue) {
                   if (aValue.IsResolve()) {
                     return StorageAccessGrantPromise::CreateAndResolve(
                         NS_SUCCEEDED(aValue.ResolveValue()) ? eAllowOnAnySite
                                                             : eAllow,
                         __func__);
                   }
                   return StorageAccessGrantPromise::CreateAndReject(false,
                                                                     __func__);
                 });
    }

    ContentChild* cc = ContentChild::GetSingleton();
    MOZ_ASSERT(cc);

    LOG(
        ("Asking the parent process to save the permission for us: "
         "trackingOrigin=%s, grantedOrigin=%s",
         trackingOrigin.get(), origin.get()));

    // This is not really secure, because here we have the content process
    // sending the request of storing a permission.
    return cc
        ->SendFirstPartyStorageAccessGrantedForOrigin(
            IPC::Principal(topLevelStoragePrincipal),
            IPC::Principal(trackingPrincipal), trackingOrigin, origin,
            aAllowMode)
        ->Then(GetCurrentThreadSerialEventTarget(), __func__,
               [](const ContentChild::
                      FirstPartyStorageAccessGrantedForOriginPromise::
                          ResolveOrRejectValue& aValue) {
                 if (aValue.IsResolve()) {
                   return StorageAccessGrantPromise::CreateAndResolve(
                       aValue.ResolveValue(), __func__);
                 }
                 return StorageAccessGrantPromise::CreateAndReject(false,
                                                                   __func__);
               });
  };

  if (aPerformFinalChecks) {
    return aPerformFinalChecks()->Then(
        GetCurrentThreadSerialEventTarget(), __func__,
        [storePermission](
            StorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
          if (aValue.IsResolve()) {
            return storePermission(aValue.ResolveValue());
          }
          return StorageAccessGrantPromise::CreateAndReject(false, __func__);
        });
  }
  return storePermission(false);
}

/* static */
RefPtr<mozilla::AntiTrackingCommon::FirstPartyStorageAccessGrantPromise>
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(
    nsIPrincipal* aParentPrincipal, nsIPrincipal* aTrackingPrincipal,
    const nsCString& aTrackingOrigin, const nsCString& aGrantedOrigin,
    int aAllowMode) {
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(aAllowMode == eAllow || aAllowMode == eAllowAutoGrant ||
             aAllowMode == eAllowOnAnySite);

  nsCOMPtr<nsIURI> parentPrincipalURI;
  Unused << aParentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
  LOG_SPEC(("Saving a first-party storage permission on %s for "
            "trackingOrigin=%s grantedOrigin=%s",
            _spec, aTrackingOrigin.get(), aGrantedOrigin.get()),
           parentPrincipalURI);

  if (NS_WARN_IF(!aParentPrincipal)) {
    // The child process is sending something wrong. Let's ignore it.
    LOG(("aParentPrincipal is null, bailing out early"));
    return FirstPartyStorageAccessGrantPromise::CreateAndReject(false,
                                                                __func__);
  }

  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
  if (NS_WARN_IF(!permManager)) {
    LOG(("Permission manager is null, bailing out early"));
    return FirstPartyStorageAccessGrantPromise::CreateAndReject(false,
                                                                __func__);
  }

  // Remember that this pref is stored in seconds!
  uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
  uint32_t expirationTime =
      StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000;
  int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;

  nsresult rv;
  if (aAllowMode == eAllowOnAnySite) {
    uint32_t privateBrowsingId = 0;
    rv = aTrackingPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
    if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
      // If we are coming from a private window, make sure to store a
      // session-only permission which won't get persisted to disk.
      expirationType = nsIPermissionManager::EXPIRE_SESSION;
      when = 0;
    }

    LOG(
        ("Setting 'any site' permission expiry: %u, proceeding to save in the "
         "permission manager",
         expirationTime));

    rv = permManager->AddFromPrincipal(
        aTrackingPrincipal, NS_LITERAL_CSTRING("cookie"),
        nsICookiePermission::ACCESS_ALLOW, expirationType, when);
    Unused << NS_WARN_IF(NS_FAILED(rv));
  }

  // We must grant the storage permission also if we allow it for any site
  // because the setting 'cookie' permission is not applied to existing
  // documents (See CookieSettings documentation).

  uint32_t privateBrowsingId = 0;
  rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
  if ((!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) ||
      (aAllowMode == eAllowAutoGrant)) {
    // If we are coming from a private window or are automatically granting a
    // permission, make sure to store a session-only permission which won't
    // get persisted to disk.
    expirationType = nsIPermissionManager::EXPIRE_SESSION;
    when = 0;
  }

  nsAutoCString type;
  CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);

  LOG(
      ("Computed permission key: %s, expiry: %u, proceeding to save in the "
       "permission manager",
       type.get(), expirationTime));

  rv = permManager->AddFromPrincipal(aParentPrincipal, type,
                                     nsIPermissionManager::ALLOW_ACTION,
                                     expirationType, when);
  Unused << NS_WARN_IF(NS_FAILED(rv));

  if (NS_SUCCEEDED(rv) && (aAllowMode == eAllowAutoGrant)) {
    // Make sure temporary access grants do not survive more than 24 hours.
    TemporaryAccessGrantObserver::Create(permManager, aParentPrincipal, type);
  }

  LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
  return FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__);
}

// static
bool AntiTrackingCommon::IsStorageAccessPermission(nsIPermission* aPermission,
                                                   nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(aPermission);
  MOZ_ASSERT(aPrincipal);

  nsAutoCString origin;
  nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  // The permission key may belong either to a tracking origin on the same
  // origin as the granted origin, or on another origin as the granted origin
  // (for example when a tracker in a third-party context uses window.open to
  // open another origin where that second origin would be the granted origin.)
  // But even in the second case, the type of the permission would still be
  // formed by concatenating the granted origin to the end of the type name
  // (see CreatePermissionKey).  Therefore, we pass in the same argument to
  // both tracking origin and granted origin here in order to compute the
  // shorter permission key and will then do a prefix match on the type of the
  // input permission to see if it is a storage access permission or not.
  nsAutoCString permissionKey;
  CreatePermissionKey(origin, permissionKey);

  nsAutoCString type;
  rv = aPermission->GetType(type);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  return StringBeginsWith(type, permissionKey);
}

bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
    nsPIDOMWindowInner* aWindow, nsIURI* aURI, uint32_t* aRejectedReason) {
  MOZ_ASSERT(aWindow);
  MOZ_ASSERT(aURI);

  // Let's avoid a null check on aRejectedReason everywhere else.
  uint32_t rejectedReason = 0;
  if (!aRejectedReason) {
    aRejectedReason = &rejectedReason;
  }

  LOG_SPEC(("Computing whether window %p has access to URI %s", aWindow, _spec),
           aURI);

  nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow);
  Document* document = innerWindow->GetExtantDoc();
  if (!document) {
    LOG(("Our window has no document"));
    return false;
  }

  nsGlobalWindowOuter* outerWindow =
      nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
  if (!outerWindow) {
    LOG(("Our window has no outer window"));
    return false;
  }

  nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow = outerWindow->GetTop();
  nsGlobalWindowOuter* topWindow = nsGlobalWindowOuter::Cast(topOuterWindow);
  if (NS_WARN_IF(!topWindow)) {
    LOG(("No top outer window"));
    return false;
  }

  nsPIDOMWindowInner* topInnerWindow = topWindow->GetCurrentInnerWindow();
  if (NS_WARN_IF(!topInnerWindow)) {
    LOG(("No top inner window."));
    return false;
  }

  Document* toplevelDocument = topInnerWindow->GetExtantDoc();
  if (!toplevelDocument) {
    LOG(("No top level document."));
    return false;
  }

  MOZ_ASSERT(toplevelDocument);

  uint32_t cookiePermission = CheckCookiePermissionForPrincipal(
      document->CookieSettings(), document->NodePrincipal());
  if (cookiePermission != nsICookiePermission::ACCESS_DEFAULT) {
    LOG(
        ("CheckCookiePermissionForPrincipal() returned a non-default access "
         "code (%d) for window's principal, returning %s",
         int(cookiePermission),
         cookiePermission != nsICookiePermission::ACCESS_DENY ? "success"
                                                              : "failure"));
    if (cookiePermission != nsICookiePermission::ACCESS_DENY) {
      return true;
    }

    *aRejectedReason =
        nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION;
    return false;
  }

  int32_t behavior = CookiesBehavior(toplevelDocument, document);
  if (behavior == nsICookieService::BEHAVIOR_ACCEPT) {
    LOG(("The cookie behavior pref mandates accepting all cookies!"));
    return true;
  }

  if (CheckContentBlockingAllowList(aWindow)) {
    return true;
  }

  if (behavior == nsICookieService::BEHAVIOR_REJECT) {
    LOG(("The cookie behavior pref mandates rejecting all cookies!"));
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL;
    return false;
  }

  // As a performance optimization, we only perform this check for
  // BEHAVIOR_REJECT_FOREIGN and BEHAVIOR_LIMIT_FOREIGN.  For
  // BEHAVIOR_REJECT_TRACKER, third-partiness is implicily checked later below.
  if (behavior != nsICookieService::BEHAVIOR_REJECT_TRACKER) {
    // Let's check if this is a 3rd party context.
    if (!nsContentUtils::IsThirdPartyWindowOrChannel(aWindow, nullptr, aURI)) {
      LOG(("Our window isn't a third-party window"));
      return true;
    }
  }

  if (behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
      behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
    // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
    // simply rejecting the request to use the storage. In the future, if we
    // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
    // for non-cookie storage types, this may change.
    LOG(("Nothing more to do due to the behavior code %d", int(behavior)));
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
    return false;
  }

  MOZ_ASSERT(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER);

  if (!nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow)) {
    LOG(("Our window isn't a third-party tracking window"));
    return true;
  }

#ifdef DEBUG
  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
  if (thirdPartyUtil) {
    bool thirdParty = false;
    nsresult rv = thirdPartyUtil->IsThirdPartyWindow(aWindow->GetOuterWindow(),
                                                     aURI, &thirdParty);
    // The result of this assertion depends on whether IsThirdPartyWindow
    // succeeds, because otherwise IsThirdPartyWindowOrChannel artificially
    // fails.
    MOZ_ASSERT_IF(NS_SUCCEEDED(rv), nsContentUtils::IsThirdPartyWindowOrChannel(
                                        aWindow, nullptr, aURI) == thirdParty);
  }
#endif

  nsCOMPtr<nsIPrincipal> parentPrincipal;
  nsCOMPtr<nsIURI> parentPrincipalURI;
  nsCOMPtr<nsIURI> trackingURI;
  nsAutoCString trackingOrigin;
  if (!GetParentPrincipalAndTrackingOrigin(
          nsGlobalWindowInner::Cast(aWindow), getter_AddRefs(parentPrincipal),
          trackingOrigin, getter_AddRefs(trackingURI), nullptr)) {
    LOG(("Failed to obtain the parent principal and the tracking origin"));
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
    return false;
  }
  Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));

  nsAutoCString grantedOrigin;
  nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, grantedOrigin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
    return false;
  }

  nsAutoCString type;
  CreatePermissionKey(trackingOrigin, grantedOrigin, type);

  if (topInnerWindow->HasStorageAccessGranted(type)) {
    LOG(("Permission stored in the window. All good."));
    return true;
  }

  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
  if (NS_WARN_IF(!permManager)) {
    LOG(("Failed to obtain the permission manager"));
    return false;
  }

  uint32_t result = 0;
  rv = permManager->TestPermissionFromPrincipal(parentPrincipal, type, &result);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("Failed to test the permission"));
    return false;
  }

  LOG_SPEC(
      ("Testing permission type %s for %s resulted in %d (%s)", type.get(),
       _spec, int(result),
       result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"),
      parentPrincipalURI);

  if (result != nsIPermissionManager::ALLOW_ACTION) {
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
    return false;
  }

  return true;
}

bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
    nsIHttpChannel* aChannel, nsIURI* aURI, uint32_t* aRejectedReason) {
  MOZ_ASSERT(aURI);
  MOZ_ASSERT(aChannel);

  // Let's avoid a null check on aRejectedReason everywhere else.
  uint32_t rejectedReason = 0;
  if (!aRejectedReason) {
    aRejectedReason = &rejectedReason;
  }

  nsIScriptSecurityManager* ssm =
      nsScriptSecurityManager::GetScriptSecurityManager();
  MOZ_ASSERT(ssm);

  nsCOMPtr<nsIURI> channelURI;
  nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
  if (NS_FAILED(rv)) {
    LOG(("Failed to get the channel final URI, bail out early"));
    return true;
  }
  LOG_SPEC(
      ("Computing whether channel %p has access to URI %s", aChannel, _spec),
      channelURI);

  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
  // We need to find the correct principal to check the cookie permission. For
  // third-party contexts, we want to check if the top-level window has a custom
  // cookie permission.
  nsCOMPtr<nsIPrincipal> toplevelPrincipal = loadInfo->GetTopLevelPrincipal();

  // If this is already the top-level window, we should use the loading
  // principal.
  if (!toplevelPrincipal) {
    LOG(
        ("Our loadInfo lacks a top-level principal, use the loadInfo's loading "
         "principal instead"));
    toplevelPrincipal = loadInfo->LoadingPrincipal();
  }

  // If we don't have a loading principal and this is a document channel, we are
  // a top-level window!
  if (!toplevelPrincipal) {
    LOG(
        ("We don't have a loading principal, let's see if this is a document "
         "channel"
         " that belongs to a top-level window"));
    bool isDocument = false;
    rv = aChannel->GetIsMainDocumentChannel(&isDocument);
    if (NS_SUCCEEDED(rv) && isDocument) {
      rv = ssm->GetChannelResultPrincipal(aChannel,
                                          getter_AddRefs(toplevelPrincipal));
      if (NS_SUCCEEDED(rv)) {
        LOG(("Yes, we guessed right!"));
      } else {
        LOG(
            ("Yes, we guessed right, but minting the channel result principal "
             "failed"));
      }
    } else {
      LOG(("No, we guessed wrong!"));
    }
  }

  // Let's use the triggering principal then.
  if (!toplevelPrincipal) {
    LOG(
        ("Our loadInfo lacks a top-level principal, use the loadInfo's "
         "triggering principal instead"));
    toplevelPrincipal = loadInfo->TriggeringPrincipal();
  }

  if (NS_WARN_IF(!toplevelPrincipal)) {
    LOG(("No top-level principal! Bail out early"));
    return false;
  }

  nsCOMPtr<nsICookieSettings> cookieSettings;
  rv = loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(
        ("Failed to get the cookie settings from the loadinfo, bail out "
         "early"));
    return true;
  }

  nsCOMPtr<nsIPrincipal> channelPrincipal;
  rv = ssm->GetChannelResultPrincipal(aChannel,
                                      getter_AddRefs(channelPrincipal));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("No channel principal, bail out early"));
    return false;
  }

  uint32_t cookiePermission =
      CheckCookiePermissionForPrincipal(cookieSettings, channelPrincipal);
  if (cookiePermission != nsICookiePermission::ACCESS_DEFAULT) {
    LOG(
        ("CheckCookiePermissionForPrincipal() returned a non-default access "
         "code (%d) for channel's principal, returning %s",
         int(cookiePermission),
         cookiePermission != nsICookiePermission::ACCESS_DENY ? "success"
                                                              : "failure"));
    if (cookiePermission != nsICookiePermission::ACCESS_DENY) {
      return true;
    }

    *aRejectedReason =
        nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION;
    return false;
  }

  if (!channelURI) {
    LOG(("No channel uri, bail out early"));
    return false;
  }

  int32_t behavior = CookiesBehavior(loadInfo, toplevelPrincipal, channelURI);
  if (behavior == nsICookieService::BEHAVIOR_ACCEPT) {
    LOG(("The cookie behavior pref mandates accepting all cookies!"));
    return true;
  }

  if (CheckContentBlockingAllowList(aChannel)) {
    return true;
  }

  if (behavior == nsICookieService::BEHAVIOR_REJECT) {
    LOG(("The cookie behavior pref mandates rejecting all cookies!"));
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL;
    return false;
  }

  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
  if (!thirdPartyUtil) {
    LOG(("No thirdPartyUtil, bail out early"));
    return true;
  }

  bool thirdParty = false;
  rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &thirdParty);
  // Grant if it's not a 3rd party.
  // Be careful to check the return value of IsThirdPartyChannel, since
  // IsThirdPartyChannel() will fail if the channel's loading principal is the
  // system principal...
  if (NS_SUCCEEDED(rv) && !thirdParty) {
    LOG(("Our channel isn't a third-party channel"));
    return true;
  }

  if (behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
      behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
    // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
    // simply rejecting the request to use the storage. In the future, if we
    // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
    // for non-cookie storage types, this may change.
    LOG(("Nothing more to do due to the behavior code %d", int(behavior)));
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
    return false;
  }

  MOZ_ASSERT(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER);

  // Not a tracker.
  if (!aChannel->IsThirdPartyTrackingResource()) {
    LOG(("Our channel isn't a third-party tracking channel"));
    return true;
  }

  nsIPrincipal* parentPrincipal = loadInfo->GetTopLevelStorageAreaPrincipal();
  if (!parentPrincipal) {
    LOG(("No top-level storage area principal at hand"));

    // parentPrincipal can be null if the parent window is not the top-level
    // window.
    if (loadInfo->GetTopLevelPrincipal()) {
      LOG(("Parent window is the top-level window, bail out early"));
      *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
      return false;
    }

    parentPrincipal = toplevelPrincipal;
    if (NS_WARN_IF(!parentPrincipal)) {
      LOG(
          ("No triggering principal, this shouldn't be happening! Bail out "
           "early"));
      // Why we are here?!?
      return true;
    }
  }

  nsCOMPtr<nsIURI> parentPrincipalURI;
  Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));

  // Let's see if we have to grant the access for this particular channel.

  nsCOMPtr<nsIURI> trackingURI;
  rv = aChannel->GetURI(getter_AddRefs(trackingURI));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("Failed to get the channel URI"));
    return true;
  }

  nsAutoCString trackingOrigin;
  rv = nsContentUtils::GetASCIIOrigin(trackingURI, trackingOrigin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG_SPEC(("Failed to compute the origin from %s", _spec), trackingURI);
    return false;
  }

  nsAutoCString origin;
  rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
    return false;
  }

  nsAutoCString type;
  CreatePermissionKey(trackingOrigin, origin, type);

  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
  if (NS_WARN_IF(!permManager)) {
    LOG(("Failed to obtain the permission manager"));
    return false;
  }

  uint32_t result = 0;
  rv = permManager->TestPermissionWithoutDefaultsFromPrincipal(parentPrincipal,
                                                               type, &result);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("Failed to test the permission"));
    return false;
  }

  LOG_SPEC(
      ("Testing permission type %s for %s resulted in %d (%s)", type.get(),
       _spec, int(result),
       result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"),
      parentPrincipalURI);

  if (result != nsIPermissionManager::ALLOW_ACTION) {
    *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
    return false;
  }

  return true;
}

bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
    nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(aPrincipal);

  nsCookieAccess access = nsICookiePermission::ACCESS_DEFAULT;
  if (aPrincipal->GetIsCodebasePrincipal()) {
    nsCOMPtr<nsICookiePermission> cps = nsCookiePermission::GetOrCreate();
    Unused << NS_WARN_IF(NS_FAILED(cps->CanAccess(aPrincipal, &access)));
  }

  if (access != nsICookiePermission::ACCESS_DEFAULT) {
    return access != nsICookiePermission::ACCESS_DENY;
  }

  int32_t behavior = CookiesBehavior(aPrincipal);
  return behavior != nsICookieService::BEHAVIOR_REJECT;
}

/* static */
bool AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(
    nsPIDOMWindowInner* aFirstPartyWindow, nsIURI* aURI) {
  MOZ_ASSERT(aFirstPartyWindow);
  MOZ_ASSERT(aURI);

  LOG_SPEC(
      ("Computing a best guess as to whether window %p has access to URI %s",
       aFirstPartyWindow, _spec),
      aURI);

  Document* parentDocument =
      nsGlobalWindowInner::Cast(aFirstPartyWindow)->GetExtantDoc();
  if (NS_WARN_IF(!parentDocument)) {
    LOG(("Failed to get the first party window's document"));
    return false;
  }

  if (parentDocument->CookieSettings()->GetCookieBehavior() !=
      nsICookieService::BEHAVIOR_REJECT_TRACKER) {
    LOG(("Disabled by the pref (%d), bail out early",
         StaticPrefs::network_cookie_cookieBehavior()));
    return true;
  }

  if (CheckContentBlockingAllowList(aFirstPartyWindow)) {
    return true;
  }

  if (!nsContentUtils::IsThirdPartyWindowOrChannel(aFirstPartyWindow, nullptr,
                                                   aURI)) {
    LOG(("Our window isn't a third-party window"));
    return true;
  }

  uint32_t cookiePermission = CheckCookiePermissionForPrincipal(
      parentDocument->CookieSettings(), parentDocument->NodePrincipal());
  if (cookiePermission != nsICookiePermission::ACCESS_DEFAULT) {
    LOG(
        ("CheckCookiePermissionForPrincipal() returned a non-default access "
         "code (%d), returning %s",
         int(cookiePermission),
         cookiePermission != nsICookiePermission::ACCESS_DENY ? "success"
                                                              : "failure"));
    return cookiePermission != nsICookiePermission::ACCESS_DENY;
  }

  nsAutoCString origin;
  nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
    return false;
  }

  nsAutoCString type;
  CreatePermissionKey(origin, type);

  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
  if (NS_WARN_IF(!permManager)) {
    LOG(("Failed to obtain the permission manager"));
    return false;
  }

  uint32_t result = 0;
  rv = permManager->TestPermissionWithoutDefaultsFromPrincipal(
      parentDocument->NodePrincipal(), type, &result);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("Failed to test the permission"));
    return false;
  }

  if (MOZ_LOG_TEST(gAntiTrackingLog, LogLevel::Debug)) {
    nsCOMPtr<nsIURI> parentPrincipalURI;
    Unused << parentDocument->NodePrincipal()->GetURI(
        getter_AddRefs(parentPrincipalURI));
    LOG_SPEC(
        ("Testing permission type %s for %s resulted in %d (%s)", type.get(),
         _spec, int(result),
         result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"),
        parentPrincipalURI);
  }

  return result == nsIPermissionManager::ALLOW_ACTION;
}

nsresult AntiTrackingCommon::IsOnContentBlockingAllowList(
    nsIURI* aTopWinURI, bool aIsPrivateBrowsing,
    AntiTrackingCommon::ContentBlockingAllowListPurpose aPurpose,
    bool& aIsAllowListed) {
  aIsAllowListed = false;

  // For storage checks, check the storage pref, and for annotations checks,
  // check the corresponding pref as well.  This allows each set of checks to
  // be disabled individually if needed.
  if ((aPurpose == eStorageChecks &&
       !StaticPrefs::browser_contentblocking_allowlist_storage_enabled()) ||
      (aPurpose == eTrackingAnnotations &&
       !StaticPrefs::browser_contentblocking_allowlist_annotations_enabled()) ||
      (aPurpose == eFingerprinting &&
       !StaticPrefs::privacy_trackingprotection_fingerprinting_enabled()) ||
      (aPurpose == eCryptomining &&
       !StaticPrefs::privacy_trackingprotection_cryptomining_enabled())) {
    LOG(
        ("Attempting to check the content blocking allow list aborted because "
         "the third-party cookies UI has been disabled."));
    return NS_OK;
  }

  LOG_SPEC(("Deciding whether the user has overridden content blocking for %s",
            _spec),
           aTopWinURI);

  // Take the host/port portion so we can allowlist by site. Also ignore the
  // scheme, since users who put sites on the allowlist probably don't expect
  // allowlisting to depend on scheme.
  nsAutoCString escaped(NS_LITERAL_CSTRING("https://"));
  nsAutoCString temp;
  nsresult rv = aTopWinURI ? aTopWinURI->GetHostPort(temp) : NS_ERROR_FAILURE;
  // GetHostPort returns an empty string (with a success error code) for file://
  // URIs.
  if (NS_FAILED(rv) || temp.IsEmpty()) {
    return rv;  // normal for some loads, no need to print a warning
  }
  escaped.Append(temp);

  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
  NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);

  // Check both the normal mode and private browsing mode user override
  // permissions.
  Pair<const nsLiteralCString, bool> types[] = {
      {NS_LITERAL_CSTRING("trackingprotection"), false},
      {NS_LITERAL_CSTRING("trackingprotection-pb"), true}};

  auto topWinURI = PromiseFlatCString(escaped);
  for (size_t i = 0; i < ArrayLength(types); ++i) {
    if (aIsPrivateBrowsing != types[i].second()) {
      continue;
    }

    uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
    rv = permManager->TestPermissionOriginNoSuffix(topWinURI, types[i].first(),
                                                   &permissions);
    NS_ENSURE_SUCCESS(rv, rv);

    if (permissions == nsIPermissionManager::ALLOW_ACTION) {
      aIsAllowListed = true;
      LOG(("Found user override type %s for %s", types[i].first().get(),
           topWinURI.get()));
      // Stop checking the next permisson type if we decided to override.
      break;
    }
  }

  if (!aIsAllowListed) {
    LOG(("No user override found"));
  }

  return NS_OK;
}

/* static */
void AntiTrackingCommon::NotifyBlockingDecision(nsIChannel* aChannel,
                                                BlockingDecision aDecision,
                                                uint32_t aRejectedReason) {
  MOZ_ASSERT(
      aRejectedReason == 0 ||
      aRejectedReason ==
          nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
      aRejectedReason ==
          nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
      aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
      aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
  MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
             aDecision == BlockingDecision::eAllow);

  if (!aChannel) {
    return;
  }

  // Can be called in EITHER the parent or child process.
  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsIParentChannel> parentChannel;
    NS_QueryNotificationCallbacks(aChannel, parentChannel);
    if (parentChannel) {
      // This channel is a parent-process proxy for a child process request.
      // Tell the child process channel to do this instead.
      if (aDecision == BlockingDecision::eBlock) {
        parentChannel->NotifyCookieBlocked(aRejectedReason);
      } else {
        parentChannel->NotifyCookieAllowed();
      }
    }
    return;
  }

  MOZ_ASSERT(XRE_IsContentProcess());

  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
  if (!thirdPartyUtil) {
    return;
  }

  nsCOMPtr<nsIURI> uriBeingLoaded = MaybeGetDocumentURIBeingLoaded(aChannel);
  nsCOMPtr<mozIDOMWindowProxy> win;
  nsresult rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, uriBeingLoaded,
                                                       getter_AddRefs(win));
  NS_ENSURE_SUCCESS_VOID(rv);

  nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(win);
  if (!pwin) {
    return;
  }

  nsCOMPtr<nsIURI> uri;
  aChannel->GetURI(getter_AddRefs(uri));

  if (aDecision == BlockingDecision::eBlock) {
    pwin->NotifyContentBlockingEvent(aRejectedReason, aChannel, true, uri);

    ReportBlockingToConsole(pwin, uri, aRejectedReason);
  }

  pwin->NotifyContentBlockingEvent(nsIWebProgressListener::STATE_COOKIES_LOADED,
                                   aChannel, false, uri);
}

/* static */
void AntiTrackingCommon::NotifyBlockingDecision(nsPIDOMWindowInner* aWindow,
                                                BlockingDecision aDecision,
                                                uint32_t aRejectedReason) {
  MOZ_ASSERT(aWindow);
  MOZ_ASSERT(
      aRejectedReason == 0 ||
      aRejectedReason ==
          nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
      aRejectedReason ==
          nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
      aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
      aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
  MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
             aDecision == BlockingDecision::eAllow);

  nsCOMPtr<nsPIDOMWindowOuter> pwin = GetTopWindow(aWindow);
  if (!pwin) {
    return;
  }

  nsPIDOMWindowInner* inner = pwin->GetCurrentInnerWindow();
  if (!inner) {
    return;
  }
  Document* pwinDoc = inner->GetExtantDoc();
  if (!pwinDoc) {
    return;
  }
  nsIChannel* channel = pwinDoc->GetChannel();
  if (!channel) {
    return;
  }

  Document* document = aWindow->GetExtantDoc();
  if (!document) {
    return;
  }
  nsIURI* uri = document->GetDocumentURI();

  if (aDecision == BlockingDecision::eBlock) {
    pwin->NotifyContentBlockingEvent(aRejectedReason, channel, true, uri);

    ReportBlockingToConsole(pwin, uri, aRejectedReason);
  }

  pwin->NotifyContentBlockingEvent(nsIWebProgressListener::STATE_COOKIES_LOADED,
                                   channel, false, uri);
}

/* static */
void AntiTrackingCommon::StoreUserInteractionFor(nsIPrincipal* aPrincipal) {
  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsIURI> uri;
    Unused << aPrincipal->GetURI(getter_AddRefs(uri));
    LOG_SPEC(("Saving the userInteraction for %s", _spec), uri);

    nsPermissionManager* permManager = nsPermissionManager::GetInstance();
    if (NS_WARN_IF(!permManager)) {
      LOG(("Permission manager is null, bailing out early"));
      return;
    }

    // Remember that this pref is stored in seconds!
    uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
    uint32_t expirationTime =
        StaticPrefs::privacy_userInteraction_expiration() * 1000;
    int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;

    uint32_t privateBrowsingId = 0;
    nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
    if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
      // If we are coming from a private window, make sure to store a
      // session-only permission which won't get persisted to disk.
      expirationType = nsIPermissionManager::EXPIRE_SESSION;
      when = 0;
    }

    rv = permManager->AddFromPrincipal(aPrincipal, USER_INTERACTION_PERM,
                                       nsIPermissionManager::ALLOW_ACTION,
                                       expirationType, when);
    Unused << NS_WARN_IF(NS_FAILED(rv));
    return;
  }

  ContentChild* cc = ContentChild::GetSingleton();
  MOZ_ASSERT(cc);

  nsCOMPtr<nsIURI> uri;
  Unused << aPrincipal->GetURI(getter_AddRefs(uri));
  LOG_SPEC(("Asking the parent process to save the user-interaction for us: %s",
            _spec),
           uri);
  cc->SendStoreUserInteractionAsPermission(IPC::Principal(aPrincipal));
}

/* static */
bool AntiTrackingCommon::HasUserInteraction(nsIPrincipal* aPrincipal) {
  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
  if (NS_WARN_IF(!permManager)) {
    return false;
  }

  uint32_t result = 0;
  nsresult rv = permManager->TestPermissionWithoutDefaultsFromPrincipal(
      aPrincipal, USER_INTERACTION_PERM, &result);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  return result == nsIPermissionManager::ALLOW_ACTION;
}

// static
void AntiTrackingCommon::OnAntiTrackingSettingsChanged(
    const AntiTrackingCommon::AntiTrackingSettingsChangedCallback& aCallback) {
  static bool initialized = false;
  if (!initialized) {
    // It is possible that while we have some data in our cache, something
    // changes in our environment that causes the anti-tracking checks below to
    // change their response.  Therefore, we need to clear our cache when we
    // detect a related change.
    Preferences::RegisterPrefixCallback(
        SettingsChangeObserver::PrivacyPrefChanged, "browser.contentblocking.");
    Preferences::RegisterPrefixCallback(
        SettingsChangeObserver::PrivacyPrefChanged, "network.cookie.");
    Preferences::RegisterPrefixCallback(
        SettingsChangeObserver::PrivacyPrefChanged, "privacy.");

    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    if (obs) {
      RefPtr<SettingsChangeObserver> observer = new SettingsChangeObserver();
      obs->AddObserver(observer, "perm-added", false);
      obs->AddObserver(observer, "perm-changed", false);
      obs->AddObserver(observer, "perm-cleared", false);
      obs->AddObserver(observer, "perm-deleted", false);
      obs->AddObserver(observer, "xpcom-shutdown", false);
    }

    gSettingsChangedCallbacks =
        MakeUnique<nsTArray<AntiTrackingSettingsChangedCallback>>();

    initialized = true;
  }

  gSettingsChangedCallbacks->AppendElement(aCallback);
}

/* static */
already_AddRefed<nsIURI> AntiTrackingCommon::MaybeGetDocumentURIBeingLoaded(
    nsIChannel* aChannel) {
  nsCOMPtr<nsIURI> uriBeingLoaded;
  nsLoadFlags loadFlags = 0;
  nsresult rv = aChannel->GetLoadFlags(&loadFlags);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }
  if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
    // If the channel being loaded is a document channel, this call may be
    // coming from an OnStopRequest notification, which might mean that our
    // document may still be in the loading process, so we may need to pass in
    // the uriBeingLoaded argument explicitly.
    rv = aChannel->GetURI(getter_AddRefs(uriBeingLoaded));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return nullptr;
    }
  }
  return uriBeingLoaded.forget();
}