/* -*- 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 "ChannelWrapper.h"

#include "jsapi.h"
#include "xpcpublic.h"

#include "mozilla/BasePrincipal.h"
#include "mozilla/SystemPrincipal.h"

#include "NSSErrorsService.h"
#include "nsITransportSecurityInfo.h"

#include "mozilla/AddonManagerWebAPI.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Components.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/EventBinding.h"
#include "mozilla/dom/BrowserHost.h"
#include "mozIThirdPartyUtil.h"
#include "nsContentUtils.h"
#include "nsIContentPolicy.h"
#include "nsIClassifiedChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILoadContext.h"
#include "nsIProxiedChannel.h"
#include "nsIProxyInfo.h"
#include "nsITraceableChannel.h"
#include "nsIWritablePropertyBag.h"
#include "nsIWritablePropertyBag2.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsPrintfCString.h"

using namespace mozilla::dom;
using namespace JS;

namespace mozilla {
namespace extensions {

#define CHANNELWRAPPER_PROP_KEY u"ChannelWrapper::CachedInstance"_ns

using CF = nsIClassifiedChannel::ClassificationFlags;
using MUC = MozUrlClassificationFlags;

struct ClassificationStruct {
  uint32_t mFlag;
  MozUrlClassificationFlags mValue;
};
static const ClassificationStruct classificationArray[] = {
    {CF::CLASSIFIED_FINGERPRINTING, MUC::Fingerprinting},
    {CF::CLASSIFIED_FINGERPRINTING_CONTENT, MUC::Fingerprinting_content},
    {CF::CLASSIFIED_CRYPTOMINING, MUC::Cryptomining},
    {CF::CLASSIFIED_CRYPTOMINING_CONTENT, MUC::Cryptomining_content},
    {CF::CLASSIFIED_EMAILTRACKING, MUC::Emailtracking},
    {CF::CLASSIFIED_EMAILTRACKING_CONTENT, MUC::Emailtracking_content},
    {CF::CLASSIFIED_TRACKING, MUC::Tracking},
    {CF::CLASSIFIED_TRACKING_AD, MUC::Tracking_ad},
    {CF::CLASSIFIED_TRACKING_ANALYTICS, MUC::Tracking_analytics},
    {CF::CLASSIFIED_TRACKING_SOCIAL, MUC::Tracking_social},
    {CF::CLASSIFIED_TRACKING_CONTENT, MUC::Tracking_content},
    {CF::CLASSIFIED_SOCIALTRACKING, MUC::Socialtracking},
    {CF::CLASSIFIED_SOCIALTRACKING_FACEBOOK, MUC::Socialtracking_facebook},
    {CF::CLASSIFIED_SOCIALTRACKING_LINKEDIN, MUC::Socialtracking_linkedin},
    {CF::CLASSIFIED_SOCIALTRACKING_TWITTER, MUC::Socialtracking_twitter},
    {CF::CLASSIFIED_ANY_BASIC_TRACKING, MUC::Any_basic_tracking},
    {CF::CLASSIFIED_ANY_STRICT_TRACKING, MUC::Any_strict_tracking},
    {CF::CLASSIFIED_ANY_SOCIAL_TRACKING, MUC::Any_social_tracking}};

/*****************************************************************************
 * Lifetimes
 *****************************************************************************/

namespace {
class ChannelListHolder : public LinkedList<ChannelWrapper> {
 public:
  ChannelListHolder() : LinkedList<ChannelWrapper>() {}

  ~ChannelListHolder();
};

}  // anonymous namespace

ChannelListHolder::~ChannelListHolder() {
  while (ChannelWrapper* wrapper = popFirst()) {
    wrapper->Die();
  }
}

static LinkedList<ChannelWrapper>* GetChannelList() {
  static UniquePtr<ChannelListHolder> sChannelList;
  if (!sChannelList && !PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) {
    sChannelList.reset(new ChannelListHolder());
    ClearOnShutdown(&sChannelList, ShutdownPhase::XPCOMShutdown);
  }
  return sChannelList.get();
}

NS_IMPL_CYCLE_COLLECTING_ADDREF(ChannelWrapper::ChannelWrapperStub)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ChannelWrapper::ChannelWrapperStub)

NS_IMPL_CYCLE_COLLECTION(ChannelWrapper::ChannelWrapperStub, mChannelWrapper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper::ChannelWrapperStub)
  NS_INTERFACE_MAP_ENTRY_TEAROFF(ChannelWrapper, mChannelWrapper)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

/*****************************************************************************
 * Initialization
 *****************************************************************************/

ChannelWrapper::ChannelWrapper(nsISupports* aParent, nsIChannel* aChannel)
    : ChannelHolder(aChannel), mParent(aParent) {
  mStub = new ChannelWrapperStub(this);

  if (auto* list = GetChannelList()) {
    list->insertBack(this);
  }
}

ChannelWrapper::~ChannelWrapper() {
  if (LinkedListElement<ChannelWrapper>::isInList()) {
    LinkedListElement<ChannelWrapper>::remove();
  }
}

void ChannelWrapper::Die() {
  if (mStub) {
    mStub->mChannelWrapper = nullptr;
  }
}

/* static */
already_AddRefed<ChannelWrapper> ChannelWrapper::Get(const GlobalObject& global,
                                                     nsIChannel* channel) {
  RefPtr<ChannelWrapper> wrapper;

  nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel);
  if (props) {
    wrapper = do_GetProperty(props, CHANNELWRAPPER_PROP_KEY);
    if (wrapper) {
      // Assume cached attributes may have changed at this point.
      wrapper->ClearCachedAttributes();
    }
  }

  if (!wrapper) {
    wrapper = new ChannelWrapper(global.GetAsSupports(), channel);
    if (props) {
      Unused << props->SetPropertyAsInterface(CHANNELWRAPPER_PROP_KEY,
                                              wrapper->mStub);
    }
  }

  return wrapper.forget();
}

already_AddRefed<ChannelWrapper> ChannelWrapper::GetRegisteredChannel(
    const GlobalObject& global, uint64_t aChannelId,
    const WebExtensionPolicy& aAddon, nsIRemoteTab* aRemoteTab) {
  ContentParent* contentParent = nullptr;
  if (BrowserHost* host = BrowserHost::GetFrom(aRemoteTab)) {
    contentParent = host->GetActor()->Manager();
  }

  auto& webreq = WebRequestService::GetSingleton();

  nsCOMPtr<nsITraceableChannel> channel =
      webreq.GetTraceableChannel(aChannelId, aAddon.Id(), contentParent);
  if (!channel) {
    return nullptr;
  }
  nsCOMPtr<nsIChannel> chan(do_QueryInterface(channel));
  return ChannelWrapper::Get(global, chan);
}

void ChannelWrapper::SetChannel(nsIChannel* aChannel) {
  detail::ChannelHolder::SetChannel(aChannel);
  ClearCachedAttributes();
  ChannelWrapper_Binding::ClearCachedFinalURIValue(this);
  ChannelWrapper_Binding::ClearCachedFinalURLValue(this);
  mFinalURLInfo.reset();
  ChannelWrapper_Binding::ClearCachedProxyInfoValue(this);
}

void ChannelWrapper::ClearCachedAttributes() {
  ChannelWrapper_Binding::ClearCachedRemoteAddressValue(this);
  ChannelWrapper_Binding::ClearCachedStatusCodeValue(this);
  ChannelWrapper_Binding::ClearCachedStatusLineValue(this);
  ChannelWrapper_Binding::ClearCachedUrlClassificationValue(this);
  if (!mFiredErrorEvent) {
    ChannelWrapper_Binding::ClearCachedErrorStringValue(this);
  }

  ChannelWrapper_Binding::ClearCachedRequestSizeValue(this);
  ChannelWrapper_Binding::ClearCachedResponseSizeValue(this);
}

/*****************************************************************************
 * ...
 *****************************************************************************/

void ChannelWrapper::Cancel(uint32_t aResult, uint32_t aReason,
                            ErrorResult& aRv) {
  nsresult rv = NS_ERROR_UNEXPECTED;
  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
    nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo();
    if (aReason > 0 && loadInfo) {
      loadInfo->SetRequestBlockingReason(aReason);
    }
    rv = chan->Cancel(nsresult(aResult));
    ErrorCheck();
  }
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

void ChannelWrapper::RedirectTo(nsIURI* aURI, ErrorResult& aRv) {
  nsresult rv = NS_ERROR_UNEXPECTED;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    rv = chan->RedirectTo(aURI);
  }
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

void ChannelWrapper::UpgradeToSecure(ErrorResult& aRv) {
  nsresult rv = NS_ERROR_UNEXPECTED;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    rv = chan->UpgradeToSecure();
  }
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

void ChannelWrapper::Suspend(const nsCString& aProfileMarkerText,
                             ErrorResult& aRv) {
  if (!mSuspended) {
    nsresult rv = NS_ERROR_UNEXPECTED;
    if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
      rv = chan->Suspend();
    }
    if (NS_FAILED(rv)) {
      aRv.Throw(rv);
    } else {
      mSuspended = true;
      MOZ_ASSERT(mSuspendedMarkerText.IsVoid());
      mSuspendedMarkerText = aProfileMarkerText;
      PROFILER_MARKER_TEXT("Extension Suspend", NETWORK,
                           MarkerOptions(MarkerTiming::IntervalStart()),
                           mSuspendedMarkerText);
    }
  }
}

void ChannelWrapper::Resume(ErrorResult& aRv) {
  if (mSuspended) {
    nsresult rv = NS_ERROR_UNEXPECTED;
    if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
      rv = chan->Resume();
    }
    if (NS_FAILED(rv)) {
      aRv.Throw(rv);
    } else {
      mSuspended = false;
      PROFILER_MARKER_TEXT("Extension Suspend", NETWORK,
                           MarkerOptions(MarkerTiming::IntervalEnd()),
                           mSuspendedMarkerText);
      mSuspendedMarkerText = VoidCString();
    }
  }
}

void ChannelWrapper::GetContentType(nsCString& aContentType) const {
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->GetContentType(aContentType);
  }
}

void ChannelWrapper::SetContentType(const nsACString& aContentType) {
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->SetContentType(aContentType);
  }
}

/*****************************************************************************
 * Headers
 *****************************************************************************/

namespace {

class MOZ_STACK_CLASS HeaderVisitor final : public nsIHttpHeaderVisitor {
 public:
  NS_DECL_NSIHTTPHEADERVISITOR

  explicit HeaderVisitor(nsTArray<dom::MozHTTPHeader>& aHeaders)
      : mHeaders(aHeaders) {}

  HeaderVisitor(nsTArray<dom::MozHTTPHeader>& aHeaders,
                const nsCString& aContentTypeHdr)
      : mHeaders(aHeaders), mContentTypeHdr(aContentTypeHdr) {}

  void VisitRequestHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv) {
    CheckResult(aChannel->VisitRequestHeaders(this), aRv);
  }

  void VisitResponseHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv) {
    CheckResult(aChannel->VisitResponseHeaders(this), aRv);
  }

  NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;

  // Stub AddRef/Release since this is a stack class.
  NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override {
    return ++mRefCnt;
  }

  NS_IMETHOD_(MozExternalRefCountType) Release(void) override {
    return --mRefCnt;
  }

  virtual ~HeaderVisitor() { MOZ_DIAGNOSTIC_ASSERT(mRefCnt == 0); }

 private:
  bool CheckResult(nsresult aNSRv, ErrorResult& aRv) {
    if (NS_FAILED(aNSRv)) {
      aRv.Throw(aNSRv);
      return false;
    }
    return true;
  }

  nsTArray<dom::MozHTTPHeader>& mHeaders;
  nsCString mContentTypeHdr = VoidCString();

  nsrefcnt mRefCnt = 0;
};

NS_IMETHODIMP
HeaderVisitor::VisitHeader(const nsACString& aHeader,
                           const nsACString& aValue) {
  auto dict = mHeaders.AppendElement(fallible);
  if (!dict) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  dict->mName = aHeader;

  if (!mContentTypeHdr.IsVoid() &&
      aHeader.LowerCaseEqualsLiteral("content-type")) {
    dict->mValue = mContentTypeHdr;
  } else {
    dict->mValue = aValue;
  }

  return NS_OK;
}

NS_IMPL_QUERY_INTERFACE(HeaderVisitor, nsIHttpHeaderVisitor)

}  // anonymous namespace

void ChannelWrapper::GetRequestHeaders(nsTArray<dom::MozHTTPHeader>& aRetVal,
                                       ErrorResult& aRv) const {
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    HeaderVisitor visitor(aRetVal);
    visitor.VisitRequestHeaders(chan, aRv);
  } else {
    aRv.Throw(NS_ERROR_UNEXPECTED);
  }
}

void ChannelWrapper::GetRequestHeader(const nsCString& aHeader,
                                      nsCString& aResult,
                                      ErrorResult& aRv) const {
  aResult.SetIsVoid(true);
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->GetRequestHeader(aHeader, aResult);
  } else {
    aRv.Throw(NS_ERROR_UNEXPECTED);
  }
}

void ChannelWrapper::GetResponseHeaders(nsTArray<dom::MozHTTPHeader>& aRetVal,
                                        ErrorResult& aRv) const {
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    HeaderVisitor visitor(aRetVal, mContentTypeHdr);
    visitor.VisitResponseHeaders(chan, aRv);
  } else {
    aRv.Throw(NS_ERROR_UNEXPECTED);
  }
}

void ChannelWrapper::SetRequestHeader(const nsCString& aHeader,
                                      const nsCString& aValue, bool aMerge,
                                      ErrorResult& aRv) {
  nsresult rv = NS_ERROR_UNEXPECTED;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    rv = chan->SetRequestHeader(aHeader, aValue, aMerge);
  }
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

void ChannelWrapper::SetResponseHeader(const nsCString& aHeader,
                                       const nsCString& aValue, bool aMerge,
                                       ErrorResult& aRv) {
  nsresult rv = NS_ERROR_UNEXPECTED;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    if (aHeader.LowerCaseEqualsLiteral("content-type")) {
      rv = chan->SetContentType(aValue);
      if (NS_SUCCEEDED(rv)) {
        mContentTypeHdr = aValue;
      }
    } else {
      rv = chan->SetResponseHeader(aHeader, aValue, aMerge);
    }
  }
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

/*****************************************************************************
 * LoadInfo
 *****************************************************************************/

already_AddRefed<nsILoadContext> ChannelWrapper::GetLoadContext() const {
  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
    nsCOMPtr<nsILoadContext> ctxt;
    NS_QueryNotificationCallbacks(chan, ctxt);
    return ctxt.forget();
  }
  return nullptr;
}

already_AddRefed<Element> ChannelWrapper::GetBrowserElement() const {
  if (nsCOMPtr<nsILoadContext> ctxt = GetLoadContext()) {
    RefPtr<Element> elem;
    if (NS_SUCCEEDED(ctxt->GetTopFrameElement(getter_AddRefs(elem)))) {
      return elem.forget();
    }
  }
  return nullptr;
}

bool ChannelWrapper::IsServiceWorkerScript() const {
  nsCOMPtr<nsIChannel> chan = MaybeChannel();
  return IsServiceWorkerScript(chan);
}

// static
bool ChannelWrapper::IsServiceWorkerScript(const nsCOMPtr<nsIChannel>& chan) {
  nsCOMPtr<nsILoadInfo> loadInfo;

  if (chan) {
    chan->GetLoadInfo(getter_AddRefs(loadInfo));
  }

  if (loadInfo) {
    // Not a script.
    if (loadInfo->GetExternalContentPolicyType() !=
        ExtContentPolicy::TYPE_SCRIPT) {
      return false;
    }

    // Service worker main script load.
    if (loadInfo->InternalContentPolicyType() ==
        nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) {
      return true;
    }

    // Service worker import scripts load.
    if (loadInfo->InternalContentPolicyType() ==
            nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
        loadInfo->InternalContentPolicyType() ==
            nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE) {
      nsLoadFlags loadFlags = 0;
      chan->GetLoadFlags(&loadFlags);
      return loadFlags & nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
    }
  }

  return false;
}

static inline bool IsSystemPrincipal(nsIPrincipal* aPrincipal) {
  return BasePrincipal::Cast(aPrincipal)->Is<SystemPrincipal>();
}

bool ChannelWrapper::IsSystemLoad() const {
  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    if (nsIPrincipal* prin = loadInfo->GetLoadingPrincipal()) {
      return IsSystemPrincipal(prin);
    }

    // loadingPrincipal is only non-null for top-level loads.
    // In practice we would never encounter a system principal for a top-level
    // load that passes through ChannelWrapper, at least not for HTTP channels.
    MOZ_ASSERT(Type() == MozContentPolicyType::Main_frame);
  }
  return false;
}

bool ChannelWrapper::CanModify() const {
  if (WebExtensionPolicy::IsRestrictedURI(FinalURLInfo())) {
    return false;
  }

  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    if (nsIPrincipal* prin = loadInfo->GetLoadingPrincipal()) {
      if (IsSystemPrincipal(prin)) {
        return false;
      }

      auto* docURI = DocumentURLInfo();
      if (docURI && WebExtensionPolicy::IsRestrictedURI(*docURI)) {
        return false;
      }
    }
  }
  return true;
}

already_AddRefed<nsIURI> ChannelWrapper::GetOriginURI() const {
  nsCOMPtr<nsIURI> uri;
  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) {
      if (prin->GetIsContentPrincipal()) {
        auto* basePrin = BasePrincipal::Cast(prin);
        Unused << basePrin->GetURI(getter_AddRefs(uri));
      }
    }
  }
  return uri.forget();
}

already_AddRefed<nsIURI> ChannelWrapper::GetDocumentURI() const {
  nsCOMPtr<nsIURI> uri;
  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    if (nsIPrincipal* prin = loadInfo->GetLoadingPrincipal()) {
      if (prin->GetIsContentPrincipal()) {
        auto* basePrin = BasePrincipal::Cast(prin);
        Unused << basePrin->GetURI(getter_AddRefs(uri));
      }
    }
  }
  return uri.forget();
}

void ChannelWrapper::GetOriginURL(nsCString& aRetVal) const {
  if (nsCOMPtr<nsIURI> uri = GetOriginURI()) {
    Unused << uri->GetSpec(aRetVal);
  }
}

void ChannelWrapper::GetDocumentURL(nsCString& aRetVal) const {
  if (nsCOMPtr<nsIURI> uri = GetDocumentURI()) {
    Unused << uri->GetSpec(aRetVal);
  }
}

const URLInfo& ChannelWrapper::FinalURLInfo() const {
  if (mFinalURLInfo.isNothing()) {
    ErrorResult rv;
    nsCOMPtr<nsIURI> uri = FinalURI();
    MOZ_ASSERT(uri);

    // If this is a view-source scheme, get the nested uri.
    while (uri && uri->SchemeIs("view-source")) {
      nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri);
      if (!nested) {
        break;
      }
      nested->GetInnerURI(getter_AddRefs(uri));
    }
    mFinalURLInfo.emplace(uri.get(), true);

    // If this is a WebSocket request, mangle the URL so that the scheme is
    // ws: or wss:, as appropriate.
    auto& url = mFinalURLInfo.ref();
    if (Type() == MozContentPolicyType::Websocket &&
        (url.Scheme() == nsGkAtoms::http || url.Scheme() == nsGkAtoms::https)) {
      nsAutoCString spec(url.CSpec());
      spec.Replace(0, 4, "ws"_ns);

      Unused << NS_NewURI(getter_AddRefs(uri), spec);
      MOZ_RELEASE_ASSERT(uri);
      mFinalURLInfo.reset();
      mFinalURLInfo.emplace(uri.get(), true);
    }
  }
  return mFinalURLInfo.ref();
}

const URLInfo* ChannelWrapper::DocumentURLInfo() const {
  if (mDocumentURLInfo.isNothing()) {
    nsCOMPtr<nsIURI> uri = GetDocumentURI();
    if (!uri) {
      return nullptr;
    }
    mDocumentURLInfo.emplace(uri.get(), true);
  }
  return &mDocumentURLInfo.ref();
}

bool ChannelWrapper::Matches(
    const dom::MozRequestFilter& aFilter, const WebExtensionPolicy* aExtension,
    const dom::MozRequestMatchOptions& aOptions) const {
  if (!HaveChannel()) {
    return false;
  }

  if (!aFilter.mTypes.IsNull() && !aFilter.mTypes.Value().Contains(Type())) {
    return false;
  }

  auto& urlInfo = FinalURLInfo();
  if (aFilter.mUrls && !aFilter.mUrls->Matches(urlInfo)) {
    return false;
  }

  nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo();
  bool isPrivate =
      loadInfo && loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
  if (!aFilter.mIncognito.IsNull() && aFilter.mIncognito.Value() != isPrivate) {
    return false;
  }

  if (aExtension) {
    // Verify extension access to private requests
    if (isPrivate && !aExtension->PrivateBrowsingAllowed()) {
      return false;
    }

    bool isProxy =
        aOptions.mIsProxy && aExtension->HasPermission(nsGkAtoms::proxy);
    // Proxies are allowed access to all urls, including restricted urls.
    if (!aExtension->CanAccessURI(urlInfo, false, !isProxy, true)) {
      return false;
    }

    // If this isn't the proxy phase of the request, check that the extension
    // has origin permissions for origin that originated the request.
    if (!isProxy) {
      if (IsSystemLoad()) {
        return false;
      }

      auto origin = DocumentURLInfo();
      // Extensions with the file:-permission may observe requests from file:
      // origins, because such documents can already be modified by content
      // scripts anyway.
      if (origin && !aExtension->CanAccessURI(*origin, false, true, true)) {
        return false;
      }
    }
  }

  return true;
}

int64_t NormalizeFrameID(nsILoadInfo* aLoadInfo, uint64_t bcID) {
  if (RefPtr<BrowsingContext> bc = aLoadInfo->GetBrowsingContext();
      !bc || bcID == bc->Top()->Id()) {
    return 0;
  }
  return bcID;
}

uint64_t ChannelWrapper::BrowsingContextId(nsILoadInfo* aLoadInfo) const {
  auto frameID = aLoadInfo->GetFrameBrowsingContextID();
  if (!frameID) {
    frameID = aLoadInfo->GetBrowsingContextID();
  }
  return frameID;
}

int64_t ChannelWrapper::FrameId() const {
  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    return NormalizeFrameID(loadInfo, BrowsingContextId(loadInfo));
  }
  return 0;
}

int64_t ChannelWrapper::ParentFrameId() const {
  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    if (RefPtr<BrowsingContext> bc = loadInfo->GetBrowsingContext()) {
      if (BrowsingContextId(loadInfo) == bc->Top()->Id()) {
        return -1;
      }

      uint64_t parentID = -1;
      if (loadInfo->GetFrameBrowsingContextID()) {
        parentID = loadInfo->GetBrowsingContextID();
      } else if (bc->GetParent()) {
        parentID = bc->GetParent()->Id();
      }
      return NormalizeFrameID(loadInfo, parentID);
    }
  }
  return -1;
}

void ChannelWrapper::GetFrameAncestors(
    dom::Nullable<nsTArray<dom::MozFrameAncestorInfo>>& aFrameAncestors,
    ErrorResult& aRv) const {
  nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo();
  if (!loadInfo || BrowsingContextId(loadInfo) == 0) {
    aFrameAncestors.SetNull();
    return;
  }

  nsresult rv = GetFrameAncestors(loadInfo, aFrameAncestors.SetValue());
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

nsresult ChannelWrapper::GetFrameAncestors(
    nsILoadInfo* aLoadInfo,
    nsTArray<dom::MozFrameAncestorInfo>& aFrameAncestors) const {
  const nsTArray<nsCOMPtr<nsIPrincipal>>& ancestorPrincipals =
      aLoadInfo->AncestorPrincipals();
  const nsTArray<uint64_t>& ancestorBrowsingContextIDs =
      aLoadInfo->AncestorBrowsingContextIDs();
  uint32_t size = ancestorPrincipals.Length();
  MOZ_DIAGNOSTIC_ASSERT(size == ancestorBrowsingContextIDs.Length());
  if (size != ancestorBrowsingContextIDs.Length()) {
    return NS_ERROR_UNEXPECTED;
  }

  bool subFrame = aLoadInfo->GetExternalContentPolicyType() ==
                  ExtContentPolicy::TYPE_SUBDOCUMENT;
  if (!aFrameAncestors.SetCapacity(subFrame ? size : size + 1, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  // The immediate parent is always the first element in the ancestor arrays,
  // however SUBDOCUMENTs do not have their immediate parent included, so we
  // inject it here. This will force wrapper.parentBrowsingContextId ==
  // wrapper.frameAncestors[0].frameId to always be true.  All ather requests
  // already match this way.
  if (subFrame) {
    auto ancestor = aFrameAncestors.AppendElement();
    GetDocumentURL(ancestor->mUrl);
    ancestor->mFrameId = ParentFrameId();
  }

  for (uint32_t i = 0; i < size; ++i) {
    auto ancestor = aFrameAncestors.AppendElement();
    MOZ_TRY(ancestorPrincipals[i]->GetAsciiSpec(ancestor->mUrl));
    ancestor->mFrameId =
        NormalizeFrameID(aLoadInfo, ancestorBrowsingContextIDs[i]);
  }
  return NS_OK;
}

/*****************************************************************************
 * Response filtering
 *****************************************************************************/

void ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon,
                                              nsIRemoteTab* aBrowserParent) {
  // We can't attach new listeners after the response has started, so don't
  // bother registering anything.
  if (mResponseStarted || !CanModify()) {
    return;
  }

  mAddonEntries.InsertOrUpdate(aAddon.Id(), aBrowserParent);
  if (!mChannelEntry) {
    mChannelEntry = WebRequestService::GetSingleton().RegisterChannel(this);
    CheckEventListeners();
  }
}

already_AddRefed<nsITraceableChannel> ChannelWrapper::GetTraceableChannel(
    nsAtom* aAddonId, dom::ContentParent* aContentParent) const {
  nsCOMPtr<nsIRemoteTab> remoteTab;
  if (mAddonEntries.Get(aAddonId, getter_AddRefs(remoteTab))) {
    ContentParent* contentParent = nullptr;
    if (remoteTab) {
      contentParent =
          BrowserHost::GetFrom(remoteTab.get())->GetActor()->Manager();
    }

    if (contentParent == aContentParent) {
      nsCOMPtr<nsITraceableChannel> chan = QueryChannel();
      return chan.forget();
    }
  }
  return nullptr;
}

/*****************************************************************************
 * ...
 *****************************************************************************/

MozContentPolicyType GetContentPolicyType(ExtContentPolicyType aType) {
  // Note: Please keep this function in sync with the external types in
  // nsIContentPolicy.idl
  switch (aType) {
    case ExtContentPolicy::TYPE_DOCUMENT:
      return MozContentPolicyType::Main_frame;
    case ExtContentPolicy::TYPE_SUBDOCUMENT:
      return MozContentPolicyType::Sub_frame;
    case ExtContentPolicy::TYPE_STYLESHEET:
      return MozContentPolicyType::Stylesheet;
    case ExtContentPolicy::TYPE_SCRIPT:
      return MozContentPolicyType::Script;
    case ExtContentPolicy::TYPE_IMAGE:
      return MozContentPolicyType::Image;
    case ExtContentPolicy::TYPE_OBJECT:
      return MozContentPolicyType::Object;
    case ExtContentPolicy::TYPE_OBJECT_SUBREQUEST:
      return MozContentPolicyType::Object_subrequest;
    case ExtContentPolicy::TYPE_XMLHTTPREQUEST:
      return MozContentPolicyType::Xmlhttprequest;
    // TYPE_FETCH returns xmlhttprequest for cross-browser compatibility.
    case ExtContentPolicy::TYPE_FETCH:
      return MozContentPolicyType::Xmlhttprequest;
    case ExtContentPolicy::TYPE_XSLT:
      return MozContentPolicyType::Xslt;
    case ExtContentPolicy::TYPE_PING:
      return MozContentPolicyType::Ping;
    case ExtContentPolicy::TYPE_BEACON:
      return MozContentPolicyType::Beacon;
    case ExtContentPolicy::TYPE_DTD:
      return MozContentPolicyType::Xml_dtd;
    case ExtContentPolicy::TYPE_FONT:
    case ExtContentPolicy::TYPE_UA_FONT:
      return MozContentPolicyType::Font;
    case ExtContentPolicy::TYPE_MEDIA:
      return MozContentPolicyType::Media;
    case ExtContentPolicy::TYPE_WEBSOCKET:
      return MozContentPolicyType::Websocket;
    case ExtContentPolicy::TYPE_CSP_REPORT:
      return MozContentPolicyType::Csp_report;
    case ExtContentPolicy::TYPE_IMAGESET:
      return MozContentPolicyType::Imageset;
    case ExtContentPolicy::TYPE_WEB_MANIFEST:
      return MozContentPolicyType::Web_manifest;
    case ExtContentPolicy::TYPE_SPECULATIVE:
      return MozContentPolicyType::Speculative;
    case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
    case ExtContentPolicy::TYPE_INVALID:
    case ExtContentPolicy::TYPE_OTHER:
    case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD:
    case ExtContentPolicy::TYPE_WEB_TRANSPORT:
      break;
      // Do not add default: so that compilers can catch the missing case.
  }
  return MozContentPolicyType::Other;
}

MozContentPolicyType ChannelWrapper::Type() const {
  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
    return GetContentPolicyType(loadInfo->GetExternalContentPolicyType());
  }
  return MozContentPolicyType::Other;
}

void ChannelWrapper::GetMethod(nsCString& aMethod) const {
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->GetRequestMethod(aMethod);
  }
}

/*****************************************************************************
 * ...
 *****************************************************************************/

uint32_t ChannelWrapper::StatusCode() const {
  uint32_t result = 0;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->GetResponseStatus(&result);
  }
  return result;
}

void ChannelWrapper::GetStatusLine(nsCString& aRetVal) const {
  nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel();
  nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(chan);

  if (internal) {
    nsAutoCString statusText;
    uint32_t major, minor, status;
    if (NS_FAILED(chan->GetResponseStatus(&status)) ||
        NS_FAILED(chan->GetResponseStatusText(statusText)) ||
        NS_FAILED(internal->GetResponseVersion(&major, &minor))) {
      return;
    }

    aRetVal = nsPrintfCString("HTTP/%u.%u %u %s", major, minor, status,
                              statusText.get());
  }
}

uint64_t ChannelWrapper::ResponseSize() const {
  uint64_t result = 0;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->GetTransferSize(&result);
  }
  return result;
}

uint64_t ChannelWrapper::RequestSize() const {
  uint64_t result = 0;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    Unused << chan->GetRequestSize(&result);
  }
  return result;
}

/*****************************************************************************
 * ...
 *****************************************************************************/

already_AddRefed<nsIURI> ChannelWrapper::FinalURI() const {
  nsCOMPtr<nsIURI> uri;
  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
    NS_GetFinalChannelURI(chan, getter_AddRefs(uri));
  }
  return uri.forget();
}

void ChannelWrapper::GetFinalURL(nsString& aRetVal) const {
  if (HaveChannel()) {
    aRetVal = FinalURLInfo().Spec();
  }
}

/*****************************************************************************
 * ...
 *****************************************************************************/

nsresult FillProxyInfo(MozProxyInfo& aDict, nsIProxyInfo* aProxyInfo) {
  MOZ_TRY(aProxyInfo->GetHost(aDict.mHost));
  MOZ_TRY(aProxyInfo->GetPort(&aDict.mPort));
  MOZ_TRY(aProxyInfo->GetType(aDict.mType));
  MOZ_TRY(aProxyInfo->GetUsername(aDict.mUsername));
  MOZ_TRY(
      aProxyInfo->GetProxyAuthorizationHeader(aDict.mProxyAuthorizationHeader));
  MOZ_TRY(aProxyInfo->GetConnectionIsolationKey(aDict.mConnectionIsolationKey));
  MOZ_TRY(aProxyInfo->GetFailoverTimeout(&aDict.mFailoverTimeout.Construct()));

  uint32_t flags;
  MOZ_TRY(aProxyInfo->GetFlags(&flags));
  aDict.mProxyDNS = flags & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;

  return NS_OK;
}

void ChannelWrapper::GetProxyInfo(dom::Nullable<MozProxyInfo>& aRetVal,
                                  ErrorResult& aRv) const {
  nsCOMPtr<nsIProxyInfo> proxyInfo;
  if (nsCOMPtr<nsIProxiedChannel> proxied = QueryChannel()) {
    Unused << proxied->GetProxyInfo(getter_AddRefs(proxyInfo));
  }
  if (proxyInfo) {
    MozProxyInfo result;

    nsresult rv = FillProxyInfo(result, proxyInfo);
    if (NS_FAILED(rv)) {
      aRv.Throw(rv);
    } else {
      aRetVal.SetValue(std::move(result));
    }
  }
}

void ChannelWrapper::GetRemoteAddress(nsCString& aRetVal) const {
  aRetVal.SetIsVoid(true);
  if (nsCOMPtr<nsIHttpChannelInternal> internal = QueryChannel()) {
    Unused << internal->GetRemoteAddress(aRetVal);
  }
}

void FillClassification(
    Sequence<mozilla::dom::MozUrlClassificationFlags>& classifications,
    uint32_t classificationFlags, ErrorResult& aRv) {
  if (classificationFlags == 0) {
    return;
  }
  for (const auto& entry : classificationArray) {
    if (classificationFlags & entry.mFlag) {
      if (!classifications.AppendElement(entry.mValue, mozilla::fallible)) {
        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
        return;
      }
    }
  }
}

void ChannelWrapper::GetUrlClassification(
    dom::Nullable<dom::MozUrlClassification>& aRetVal, ErrorResult& aRv) const {
  MozUrlClassification classification;
  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
    nsCOMPtr<nsIClassifiedChannel> classified = do_QueryInterface(chan);
    MOZ_DIAGNOSTIC_ASSERT(
        classified,
        "Must be an object inheriting from both nsIHttpChannel and "
        "nsIClassifiedChannel");
    uint32_t classificationFlags;
    classified->GetFirstPartyClassificationFlags(&classificationFlags);
    FillClassification(classification.mFirstParty, classificationFlags, aRv);
    if (aRv.Failed()) {
      return;
    }
    classified->GetThirdPartyClassificationFlags(&classificationFlags);
    FillClassification(classification.mThirdParty, classificationFlags, aRv);
  }
  aRetVal.SetValue(std::move(classification));
}

bool ChannelWrapper::ThirdParty() const {
  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
      components::ThirdPartyUtil::Service();
  if (NS_WARN_IF(!thirdPartyUtil)) {
    return true;
  }

  nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel();
  if (!chan) {
    return false;
  }

  bool thirdParty = false;
  nsresult rv = thirdPartyUtil->IsThirdPartyChannel(chan, nullptr, &thirdParty);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return true;
  }

  return thirdParty;
}

/*****************************************************************************
 * Error handling
 *****************************************************************************/

void ChannelWrapper::GetErrorString(nsString& aRetVal) const {
  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
    nsCOMPtr<nsITransportSecurityInfo> securityInfo;
    Unused << chan->GetSecurityInfo(getter_AddRefs(securityInfo));
    if (securityInfo) {
      int32_t errorCode = 0;
      securityInfo->GetErrorCode(&errorCode);
      if (psm::IsNSSErrorCode(errorCode)) {
        nsCOMPtr<nsINSSErrorsService> nsserr =
            do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);

        nsresult rv = psm::GetXPCOMFromNSSError(errorCode);
        if (nsserr && NS_SUCCEEDED(nsserr->GetErrorMessage(rv, aRetVal))) {
          return;
        }
      }
    }

    nsresult status;
    if (NS_SUCCEEDED(chan->GetStatus(&status)) && NS_FAILED(status)) {
      nsAutoCString name;
      GetErrorName(status, name);
      AppendUTF8toUTF16(name, aRetVal);
    } else {
      aRetVal.SetIsVoid(true);
    }
  } else {
    aRetVal.AssignLiteral("NS_ERROR_UNEXPECTED");
  }
}

void ChannelWrapper::ErrorCheck() {
  if (!mFiredErrorEvent) {
    nsAutoString error;
    GetErrorString(error);
    if (error.Length()) {
      mChannelEntry = nullptr;
      mFiredErrorEvent = true;
      ChannelWrapper_Binding::ClearCachedErrorStringValue(this);
      FireEvent(u"error"_ns);
    }
  }
}

/*****************************************************************************
 * nsIWebRequestListener
 *****************************************************************************/

NS_IMPL_ISUPPORTS(ChannelWrapper::RequestListener, nsIStreamListener,
                  nsIMultiPartChannelListener, nsIRequestObserver,
                  nsIThreadRetargetableStreamListener)

ChannelWrapper::RequestListener::~RequestListener() {
  NS_ReleaseOnMainThread("RequestListener::mChannelWrapper",
                         mChannelWrapper.forget());
}

nsresult ChannelWrapper::RequestListener::Init() {
  if (nsCOMPtr<nsITraceableChannel> chan = mChannelWrapper->QueryChannel()) {
    return chan->SetNewListener(this, false,
                                getter_AddRefs(mOrigStreamListener));
  }
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP
ChannelWrapper::RequestListener::OnStartRequest(nsIRequest* request) {
  MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener");

  mChannelWrapper->mChannelEntry = nullptr;
  mChannelWrapper->mResponseStarted = true;
  mChannelWrapper->ErrorCheck();
  mChannelWrapper->FireEvent(u"start"_ns);

  return mOrigStreamListener->OnStartRequest(request);
}

NS_IMETHODIMP
ChannelWrapper::RequestListener::OnStopRequest(nsIRequest* request,
                                               nsresult aStatus) {
  MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener");

  mChannelWrapper->mChannelEntry = nullptr;
  mChannelWrapper->ErrorCheck();
  mChannelWrapper->FireEvent(u"stop"_ns);

  return mOrigStreamListener->OnStopRequest(request, aStatus);
}

NS_IMETHODIMP
ChannelWrapper::RequestListener::OnDataAvailable(nsIRequest* request,
                                                 nsIInputStream* inStr,
                                                 uint64_t sourceOffset,
                                                 uint32_t count) {
  MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener");
  return mOrigStreamListener->OnDataAvailable(request, inStr, sourceOffset,
                                              count);
}

NS_IMETHODIMP
ChannelWrapper::RequestListener::OnAfterLastPart(nsresult aStatus) {
  MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener");
  if (nsCOMPtr<nsIMultiPartChannelListener> listener =
          do_QueryInterface(mOrigStreamListener)) {
    return listener->OnAfterLastPart(aStatus);
  }
  return NS_OK;
}

NS_IMETHODIMP
ChannelWrapper::RequestListener::CheckListenerChain() {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread!");
  nsresult rv;
  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
      do_QueryInterface(mOrigStreamListener, &rv);
  if (retargetableListener) {
    return retargetableListener->CheckListenerChain();
  }
  return rv;
}

/*****************************************************************************
 * Event dispatching
 *****************************************************************************/

void ChannelWrapper::FireEvent(const nsAString& aType) {
  EventInit init;
  init.mBubbles = false;
  init.mCancelable = false;

  RefPtr<Event> event = Event::Constructor(this, aType, init);
  event->SetTrusted(true);

  DispatchEvent(*event);
}

void ChannelWrapper::CheckEventListeners() {
  if (!mAddedStreamListener &&
      (HasListenersFor(nsGkAtoms::onerror) ||
       HasListenersFor(nsGkAtoms::onstart) ||
       HasListenersFor(nsGkAtoms::onstop) || mChannelEntry)) {
    auto listener = MakeRefPtr<RequestListener>(this);
    if (!NS_WARN_IF(NS_FAILED(listener->Init()))) {
      mAddedStreamListener = true;
    }
  }
}

void ChannelWrapper::EventListenerAdded(nsAtom* aType) {
  CheckEventListeners();
}

void ChannelWrapper::EventListenerRemoved(nsAtom* aType) {
  CheckEventListeners();
}

/*****************************************************************************
 * Glue
 *****************************************************************************/

JSObject* ChannelWrapper::WrapObject(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
  return ChannelWrapper_Binding::Wrap(aCx, this, aGivenProto);
}

NS_IMPL_CYCLE_COLLECTION_CLASS(ChannelWrapper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper)
  NS_INTERFACE_MAP_ENTRY(ChannelWrapper)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ChannelWrapper,
                                                DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStub)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ChannelWrapper,
                                                  DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStub)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_ADDREF_INHERITED(ChannelWrapper, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(ChannelWrapper, DOMEventTargetHelper)

}  // namespace extensions
}  // namespace mozilla
