diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/extensions/webrequest/ChannelWrapper.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/extensions/webrequest/ChannelWrapper.cpp')
-rw-r--r-- | toolkit/components/extensions/webrequest/ChannelWrapper.cpp | 1281 |
1 files changed, 1281 insertions, 0 deletions
diff --git a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp new file mode 100644 index 0000000000..b3e1bbcda6 --- /dev/null +++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp @@ -0,0 +1,1281 @@ +/* -*- 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/Try.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() = default; + + ~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_AMBIGUOUS(ChannelWrapper, EventTarget, + 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; + // Fetch() from Workers saves BrowsingContext/LoadContext information in + // nsILoadInfo.workerAssociatedBrowsingContext. So we can not use + // NS_QueryNotificationCallbacks to get LoadContext of the channel. + RefPtr<BrowsingContext> bc; + nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo(); + loadInfo->GetWorkerAssociatedBrowsingContext(getter_AddRefs(bc)); + if (bc) { + ctxt = bc.forget(); + return ctxt.forget(); + } + 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) { + RefPtr<BrowsingContext> bc = aLoadInfo->GetWorkerAssociatedBrowsingContext(); + if (!bc) { + bc = aLoadInfo->GetBrowsingContext(); + } + + if (!bc || bcID == bc->Top()->Id()) { + return 0; + } + return bcID; +} + +uint64_t ChannelWrapper::BrowsingContextId(nsILoadInfo* aLoadInfo) const { + auto frameID = aLoadInfo->GetFrameBrowsingContextID(); + if (!frameID) { + frameID = aLoadInfo->GetWorkerAssociatedBrowsingContextID(); + } + 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()) { + RefPtr<BrowsingContext> bc = loadInfo->GetWorkerAssociatedBrowsingContext(); + if (!bc) { + bc = loadInfo->GetBrowsingContext(); + } + if (bc) { + 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: + case ExtContentPolicy::TYPE_WEB_IDENTITY: + 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; +} + +NS_IMETHODIMP +ChannelWrapper::RequestListener::OnDataFinished(nsresult aStatus) { + MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mOrigStreamListener); + if (retargetableListener) { + return retargetableListener->OnDataFinished(aStatus); + } + + return NS_OK; +} + +/***************************************************************************** + * 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_CONCRETE(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 |