toolkit/components/antitracking/AntiTrackingCommon.cpp
author Ehsan Akhgari <ehsan@mozilla.com>
Fri, 25 Jan 2019 17:03:23 -0500
changeset 515545 c418bc9e3fcef85a4de1b775e73dd4d08d0bd465
parent 515521 e4b9b1084292686d3eb50ba0cadd85950824c955
child 515566 65cf08e33fe2b12a90a505462f3246df204c64ad
permissions -rw-r--r--
Bug 1522370 - Part 2: Remove the changes for bug 1493361 which accidentally crept in here while rebasing

/* -*- 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/IntegerPrintfMacros.h"
#include "mozilla/Logging.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 "nsIPermissionManager.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 {

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,
                         const nsCString& aGrantedOrigin,
                         nsACString& aPermissionKey) {
  if (aTrackingOrigin == aGrantedOrigin) {
    aPermissionKey =
        nsPrintfCString(ANTITRACKING_PERM_KEY "^%s", aTrackingOrigin.get());
    return;
  }

  aPermissionKey = nsPrintfCString(ANTITRACKING_PERM_KEY "^%s^%s",
                                   aTrackingOrigin.get(), aGrantedOrigin.get());
}

// This internal method returns ACCESS_DENY if the access is denied,
// ACCESS_DEFAULT if unknown, some other access code if granted.
nsCookieAccess CheckCookiePermissionForPrincipal(nsIPrincipal* aPrincipal) {
  nsCookieAccess access = nsICookiePermission::ACCESS_DEFAULT;
  if (!aPrincipal->GetIsCodebasePrincipal()) {
    return access;
  }

  nsCOMPtr<nsICookiePermission> cps = nsCookiePermission::GetOrCreate();

  nsresult rv = cps->CanAccess(aPrincipal, &access);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nsICookiePermission::ACCESS_DEFAULT;
  }

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

// This internal method returns ACCESS_DENY if the access is denied,
// ACCESS_DEFAULT if unknown, some other access code if granted.
nsCookieAccess CheckCookiePermissionForURI(nsIURI* aURI) {
  nsCookieAccess access = nsICookiePermission::ACCESS_DEFAULT;

  nsCOMPtr<nsICookiePermission> cps = nsCookiePermission::GetOrCreate();

  nsresult rv = cps->CanAccessURI(aURI, &access);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nsICookiePermission::ACCESS_DEFAULT;
  }

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

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

  if (a3rdPartyPrincipal &&
      BasePrincipal::Cast(a3rdPartyPrincipal)->AddonPolicy()) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  return StaticPrefs::network_cookie_cookieBehavior();
}

int32_t CookiesBehavior(nsIPrincipal* aTopLevelPrincipal,
                        nsIURI* 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 (a3rdPartyURI &&
      NS_SUCCEEDED(
          a3rdPartyURI->SchemeIs("moz-extension", &is3rdPartyMozExt)) &&
      is3rdPartyMozExt) {
    return nsICookieService::BEHAVIOR_ACCEPT;
  }

  return StaticPrefs::network_cookie_cookieBehavior();
}

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) {
  nsPIDOMWindowOuter* top = aWindow->GetScriptableTop();
  if (top) {
    nsIURI* topWinURI = top->GetDocumentURI();
    Document* doc = top->GetExtantDoc();
    bool isPrivateBrowsing =
        doc ? nsContentUtils::IsInPrivateBrowsing(doc) : false;
    return CheckContentBlockingAllowList(topWinURI, isPrivateBrowsing);
  }

  LOG(
      ("Could not check the content blocking allow list because the top "
       "window wasn't accessible"));
  return false;
}

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

  LOG(
      ("Could not check the content blocking allow list because the top "
       "window wasn't accessible"));
  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_IdleDispatchToCurrentThread(
      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);
  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_IdleDispatchToCurrentThread(
      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);
  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(nsIPermissionManager* 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(nsIPermissionManager* 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;
  nsCOMPtr<nsIPermissionManager> 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.get());
  } 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;
}

}  // 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__);
  }

  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
  if (NS_WARN_IF(!pm)) {
    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 = pm->AddFromPrincipal(aTrackingPrincipal, "cookie",
                              nsICookiePermission::ACCESS_ALLOW, expirationType,
                              when);
  } else {
    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 = pm->AddFromPrincipal(aParentPrincipal, type.get(),
                              nsIPermissionManager::ALLOW_ACTION,
                              expirationType, when);

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

  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, 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);
  nsIPrincipal* windowPrincipal = innerWindow->GetPrincipal();
  if (!windowPrincipal) {
    LOG(("Our window has no principal"));
    return false;
  }

  nsIPrincipal* toplevelPrincipal = innerWindow->GetTopLevelPrincipal();
  if (!toplevelPrincipal) {
    // We are already the top-level principal. Let's use the window's principal.
    LOG(
        ("Our inner window lacks a top-level principal, use the window's "
         "principal instead"));
    toplevelPrincipal = windowPrincipal;
  }

  MOZ_ASSERT(toplevelPrincipal);

  nsCookieAccess access = CheckCookiePermissionForPrincipal(toplevelPrincipal);
  if (access != nsICookiePermission::ACCESS_DEFAULT) {
    LOG(
        ("CheckCookiePermissionForPrincipal() returned a non-default access "
         "code (%d) for top-level window's principal, returning %s",
         int(access),
         access != nsICookiePermission::ACCESS_DENY ? "success" : "failure"));
    if (access != nsICookiePermission::ACCESS_DENY) {
      return true;
    }

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

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

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

  int32_t behavior = CookiesBehavior(toplevelPrincipal, windowPrincipal);
  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;
  }

  nsGlobalWindowOuter* outerWindow =
      nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
  if (NS_WARN_IF(!outerWindow)) {
    LOG(("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;
  }

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

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

  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
  if (NS_WARN_IF(!pm)) {
    LOG(("Failed to obtain the permission manager"));
    return false;
  }

  uint32_t result = 0;
  rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &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;
  }

  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->GetLoadInfo();
  if (!loadInfo) {
    LOG(("No loadInfo, bail out early"));
    return true;
  }

  // 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) {
      nsIScriptSecurityManager* ssm =
          nsScriptSecurityManager::GetScriptSecurityManager();
      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;
  }

  nsCookieAccess access = CheckCookiePermissionForPrincipal(toplevelPrincipal);
  if (access != nsICookiePermission::ACCESS_DEFAULT) {
    LOG(
        ("CheckCookiePermissionForPrincipal() returned a non-default access "
         "code (%d) for top-level window's principal, returning %s",
         int(access),
         access != nsICookiePermission::ACCESS_DENY ? "success" : "failure"));
    if (access != nsICookiePermission::ACCESS_DENY) {
      return true;
    }

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

  if (NS_WARN_IF(NS_FAILED(rv) || !channelURI)) {
    LOG(("No channel principal, bail out early"));
    return false;
  }

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

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

  int32_t behavior = CookiesBehavior(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->GetIsThirdPartyTrackingResource()) {
    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);

  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
  if (NS_WARN_IF(!pm)) {
    LOG(("Failed to obtain the permission manager"));
    return false;
  }

  uint32_t result = 0;
  rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &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 = CheckCookiePermissionForPrincipal(aPrincipal);
  if (access != nsICookiePermission::ACCESS_DEFAULT) {
    return access != nsICookiePermission::ACCESS_DENY;
  }

  int32_t behavior =
      CookiesBehavior(aPrincipal, static_cast<nsIPrincipal*>(nullptr));
  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);

  if (StaticPrefs::network_cookie_cookieBehavior() !=
      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;
  }

  nsCOMPtr<nsIPrincipal> parentPrincipal =
      nsGlobalWindowInner::Cast(aFirstPartyWindow)->GetPrincipal();
  if (NS_WARN_IF(!parentPrincipal)) {
    LOG(("Failed to get the first party window's principal"));
    return false;
  }

  nsCookieAccess access = CheckCookiePermissionForPrincipal(parentPrincipal);
  if (access != nsICookiePermission::ACCESS_DEFAULT) {
    LOG(
        ("CheckCookiePermissionForPrincipal() returned a non-default access "
         "code (%d), returning %s",
         int(access),
         access != nsICookiePermission::ACCESS_DENY ? "success" : "failure"));
    return access != 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, origin, type);

  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
  if (NS_WARN_IF(!pm)) {
    LOG(("Failed to obtain the permission manager"));
    return false;
  }

  uint32_t result = 0;
  rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &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 << parentPrincipal->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);

  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
  NS_ENSURE_TRUE(permMgr, NS_ERROR_FAILURE);

  // Check both the normal mode and private browsing mode user override
  // permissions.
  Pair<const char*, bool> types[] = {{"trackingprotection", false},
                                     {"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 = permMgr->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(),
           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->NotifyTrackingCookieBlocked(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);

    nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
    if (NS_WARN_IF(!pm)) {
      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 = pm->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) {
  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
  if (NS_WARN_IF(!pm)) {
    return false;
  }

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

  return result == nsIPermissionManager::ALLOW_ACTION;
}

/* 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();
}