Bug 1396856: Part 3 - Add a WebIDL wrapper class for necko channels. r?ehsan,mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 05 Sep 2017 22:46:06 -0700
changeset 659698 9ab476a07f9d2029782f2425eae73be6cee83a86
parent 659697 4aa4ab6fe8c46ed7aa54f227ea1e90c9975fde07
child 659699 119959bddf5ac5ac66259f1ec6f52eb28a1cc142
push id78171
push usermaglione.k@gmail.com
push dateWed, 06 Sep 2017 06:26:38 +0000
reviewersehsan, mixedpuppy
bugs1396856
milestone57.0a1
Bug 1396856: Part 3 - Add a WebIDL wrapper class for necko channels. r?ehsan,mixedpuppy Ehsan, can you please review the DOM bindings, and Shane the request logic? The bulk of the overhead WebRequest API is in its access to nsIChannel and friends through XPConnect. Since it's not really feasible to convert channels to use WebIDL bindings directly, this generic channel wrapper class serves the same purpose. MozReview-Commit-ID: 4mNP8HiKWK
dom/bindings/Bindings.conf
dom/webidl/ChannelWrapper.webidl
dom/webidl/moz.build
toolkit/components/extensions/webrequest/ChannelWrapper.cpp
toolkit/components/extensions/webrequest/ChannelWrapper.h
toolkit/components/extensions/webrequest/moz.build
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -130,16 +130,20 @@ DOMInterfaces = {
         'mozImageSmoothingEnabled': 'imageSmoothingEnabled'
     }
 },
 
 'CaretPosition' : {
     'nativeType': 'nsDOMCaretPosition',
 },
 
+'ChannelWrapper': {
+    'nativeType': 'mozilla::extensions::ChannelWrapper',
+},
+
 'CharacterData': {
     'nativeType': 'nsGenericDOMDataNode',
     'concrete': False
 },
 
 'ChromeUtils': {
     # The codegen is dumb, and doesn't understand that this interface is only a
     # collection of static methods, so we have this `concrete: False` hack.
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ChannelWrapper.webidl
@@ -0,0 +1,148 @@
+/* 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/. */
+
+interface LoadInfo;
+interface MozChannel;
+interface URI;
+interface nsISupports;
+
+enum MozContentPolicyType {
+  "main_frame",
+  "sub_frame",
+  "stylesheet",
+  "script",
+  "image",
+  "object",
+  "object_subrequest",
+  "xmlhttprequest",
+  "xbl",
+  "xslt",
+  "ping",
+  "beacon",
+  "xml_dtd",
+  "font",
+  "media",
+  "websocket",
+  "csp_report",
+  "imageset",
+  "web_manifest",
+  "other"
+};
+
+/**
+ * A thin wrapper around nsIChannel and nsIHttpChannel that allows JS
+ * callers to access them without XPConnect overhead.
+ */
+[ChromeOnly, Exposed=System]
+interface ChannelWrapper {
+  /**
+   * Returns the wrapper instance for the given channel. The same wrapper is
+   * always returned for a given channel.
+   */
+  static ChannelWrapper get(MozChannel channel);
+
+  [Constant, StoreInSlot]
+  readonly attribute unsigned long long id;
+
+  // Not technically pure, since it's backed by a weak reference, but if JS
+  // has a reference to the previous value, we can depend on it not being
+  // collected.
+  [Pure]
+  attribute MozChannel? channel;
+
+
+  [Throws]
+  void cancel(unsigned long result);
+
+  [Throws]
+  void redirectTo(URI url);
+
+
+  [Pure]
+  attribute ByteString contentType;
+
+
+  [Cached, Pure]
+  readonly attribute ByteString method;
+
+  [Cached, Pure]
+  readonly attribute MozContentPolicyType type;
+
+
+  [Pure, SetterThrows]
+  attribute boolean suspended;
+
+
+  [Cached, GetterThrows, Pure]
+  readonly attribute URI finalURI;
+
+  [Cached, GetterThrows, Pure]
+  readonly attribute ByteString finalURL;
+
+
+  [Cached, Pure]
+  readonly attribute unsigned long statusCode;
+
+  [Cached, Pure]
+  readonly attribute ByteString statusLine;
+
+
+  [Cached, Frozen, GetterThrows, Pure]
+  readonly attribute MozProxyInfo? proxyInfo;
+
+  [Cached, Pure]
+  readonly attribute ByteString? remoteAddress;
+
+
+  [Cached, Pure]
+  readonly attribute LoadInfo? loadInfo;
+
+  [Cached, Pure]
+  readonly attribute boolean isSystemLoad;
+
+  [Cached, Pure]
+  readonly attribute ByteString? originURL;
+
+  [Cached, Pure]
+  readonly attribute ByteString? documentURL;
+
+
+  [Cached, GetterThrows, Pure]
+  readonly attribute boolean canModify;
+
+
+  [Cached, Constant]
+  readonly attribute long long windowId;
+
+  [Cached, Constant]
+  readonly attribute long long parentWindowId;
+
+  [Cached, Pure]
+  readonly attribute nsISupports? browserElement;
+
+
+  [Throws]
+  object getRequestHeaders();
+
+  [Throws]
+  object getResponseHeaders();
+
+  [Throws]
+  void setRequestHeader(ByteString header, ByteString value);
+
+  [Throws]
+  void setResponseHeader(ByteString header, ByteString value);
+};
+
+dictionary MozProxyInfo {
+  required ByteString host;
+  required long port;
+  required ByteString type;
+
+  required boolean proxyDNS;
+
+  ByteString? username = null;
+
+  unsigned long failoverTimeout;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -427,16 +427,17 @@ WEBIDL_FILES = [
     'Cache.webidl',
     'CacheStorage.webidl',
     'CanvasCaptureMediaStream.webidl',
     'CanvasRenderingContext2D.webidl',
     'CaretPosition.webidl',
     'CDATASection.webidl',
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
+    'ChannelWrapper.webidl',
     'CharacterData.webidl',
     'CheckerboardReportService.webidl',
     'ChildNode.webidl',
     'ChromeNodeList.webidl',
     'ChromeUtils.webidl',
     'Client.webidl',
     'Clients.webidl',
     'ClipboardEvent.webidl',
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -0,0 +1,702 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "SystemPrincipal.h"
+
+#include "mozilla/AddonManagerWebAPI.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Unused.h"
+#include "nsIContentPolicy.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla::dom;
+using namespace JS;
+
+namespace mozilla {
+namespace extensions {
+
+#define CHANNELWRAPPER_PROP_KEY NS_LITERAL_STRING("ChannelWrapper::CachedInstance")
+
+/*****************************************************************************
+ * Initialization
+ *****************************************************************************/
+
+/* static */
+
+already_AddRefed<ChannelWrapper>
+ChannelWrapper::Get(const GlobalObject& global, nsIChannel* channel)
+{
+  RefPtr<ChannelWrapper> wrapper;
+
+  nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel);
+  if (props) {
+    Unused << props->GetPropertyAsInterface(CHANNELWRAPPER_PROP_KEY,
+                                            NS_GET_IID(ChannelWrapper),
+                                            getter_AddRefs(wrapper));
+
+    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);
+    }
+  }
+
+  return wrapper.forget();
+}
+
+void
+ChannelWrapper::SetChannel(nsIChannel* aChannel)
+{
+  detail::ChannelHolder::SetChannel(aChannel);
+  ClearCachedAttributes();
+}
+
+void
+ChannelWrapper::ClearCachedAttributes()
+{
+  ChannelWrapperBinding::ClearCachedFinalURIValue(this);
+  ChannelWrapperBinding::ClearCachedFinalURLValue(this);
+  ChannelWrapperBinding::ClearCachedProxyInfoValue(this);
+  ChannelWrapperBinding::ClearCachedRemoteAddressValue(this);
+  ChannelWrapperBinding::ClearCachedStatusCodeValue(this);
+  ChannelWrapperBinding::ClearCachedStatusLineValue(this);
+}
+
+/*****************************************************************************
+ * ...
+ *****************************************************************************/
+
+void
+ChannelWrapper::Cancel(uint32_t aResult, ErrorResult& aRv)
+{
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
+    rv = chan->Cancel(nsresult(aResult));
+  }
+  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::SetSuspended(bool aSuspended, ErrorResult& aRv)
+{
+  if (aSuspended != mSuspended) {
+    nsresult rv = NS_ERROR_UNEXPECTED;
+    if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
+      if (aSuspended) {
+        rv = chan->Suspend();
+      } else {
+        rv = chan->Resume();
+      }
+    }
+    if (NS_FAILED(rv)) {
+      aRv.Throw(rv);
+    } else {
+      mSuspended = aSuspended;
+    }
+  }
+}
+
+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(JSContext* aCx)
+    : mCx(aCx)
+    , mMap(aCx)
+  {}
+
+  JSObject* VisitRequestHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv)
+  {
+    if (!Init()) {
+      return nullptr;
+    }
+    if (!CheckResult(aChannel->VisitRequestHeaders(this), aRv)) {
+      return nullptr;
+    }
+    return mMap;
+  }
+
+  JSObject* VisitResponseHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv)
+  {
+    if (!Init()) {
+      return nullptr;
+    }
+    if (!CheckResult(aChannel->VisitResponseHeaders(this), aRv)) {
+      return nullptr;
+    }
+    return mMap;
+  }
+
+  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_ASSERT(mRefCnt == 0);
+  }
+
+private:
+  bool Init()
+  {
+    mMap = NewMapObject(mCx);
+    return mMap;
+  }
+
+  bool CheckResult(nsresult aNSRv, ErrorResult& aRv)
+  {
+    if (JS_IsExceptionPending(mCx)) {
+      aRv.NoteJSContextException(mCx);
+      return false;
+    }
+    if (NS_FAILED(aNSRv)) {
+      aRv.Throw(aNSRv);
+      return false;
+    }
+    return true;
+  }
+
+  JSContext* mCx;
+  RootedObject mMap;
+
+  uint32_t mRefCnt = 0;
+};
+
+NS_IMETHODIMP
+HeaderVisitor::VisitHeader(const nsACString& aHeader, const nsACString& aValue)
+{
+  RootedValue header(mCx);
+  RootedValue value(mCx);
+
+  if (!xpc::NonVoidStringToJsval(mCx, NS_ConvertUTF8toUTF16(aHeader), &header) ||
+      !xpc::NonVoidStringToJsval(mCx, NS_ConvertUTF8toUTF16(aValue), &value) ||
+      !MapSet(mCx, mMap, header, value)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
+}
+
+NS_INTERFACE_MAP_BEGIN(HeaderVisitor)
+  NS_INTERFACE_MAP_ENTRY(nsIHttpHeaderVisitor)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+} // anonymous namespace
+
+
+void
+ChannelWrapper::GetRequestHeaders(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv) const
+{
+  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
+    HeaderVisitor visitor(cx);
+    aRetVal.set(visitor.VisitRequestHeaders(chan, aRv));
+  } else {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+  }
+}
+
+void
+ChannelWrapper::GetResponseHeaders(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv) const
+{
+  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
+    HeaderVisitor visitor(cx);
+    aRetVal.set(visitor.VisitResponseHeaders(chan, aRv));
+  } else {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+  }
+}
+
+void
+ChannelWrapper::SetRequestHeader(const nsCString& aHeader, const nsCString& aValue, ErrorResult& aRv)
+{
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
+    rv = chan->SetRequestHeader(aHeader, aValue, false);
+  }
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+void
+ChannelWrapper::SetResponseHeader(const nsCString& aHeader, const nsCString& aValue, ErrorResult& aRv)
+{
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
+    rv = chan->SetResponseHeader(aHeader, aValue, false);
+  }
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+/*****************************************************************************
+ * LoadInfo
+ *****************************************************************************/
+
+template<typename T>
+already_AddRefed<nsILoadContext>
+FindLoadContext(const nsCOMPtr<T>& aObj)
+{
+  nsCOMPtr<nsILoadContext> ctxt;
+
+  nsCOMPtr<nsIInterfaceRequestor> req;
+  if (NS_SUCCEEDED(aObj->GetNotificationCallbacks(getter_AddRefs(req)))) {
+    ctxt = do_GetInterface(req);
+  }
+  return ctxt.forget();
+}
+
+already_AddRefed<nsILoadContext>
+ChannelWrapper::GetLoadContext() const
+{
+  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
+    nsCOMPtr<nsILoadContext> ctxt = FindLoadContext(chan);
+    if (!ctxt) {
+      nsCOMPtr<nsILoadGroup> group;
+      if (NS_SUCCEEDED(chan->GetLoadGroup(getter_AddRefs(group))) && group) {
+        ctxt = FindLoadContext(group);
+      }
+    }
+    return ctxt.forget();
+  }
+  return nullptr;
+}
+
+already_AddRefed<nsIDOMElement>
+ChannelWrapper::GetBrowserElement() const
+{
+  if (nsCOMPtr<nsILoadContext> ctxt = GetLoadContext()) {
+    nsCOMPtr<nsIDOMElement> elem;
+    if (NS_SUCCEEDED(ctxt->GetTopFrameElement(getter_AddRefs(elem)))) {
+      return elem.forget();
+    }
+  }
+  return nullptr;
+}
+
+bool
+IsSystemPrincipal(nsIPrincipal* aPrincipal)
+{
+  return BasePrincipal::Cast(aPrincipal)->Is<SystemPrincipal>();
+}
+
+bool
+ChannelWrapper::IsSystemLoad() const
+{
+  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
+    if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) {
+      return IsSystemPrincipal(prin);
+    }
+
+    if (loadInfo->GetOuterWindowID() == loadInfo->GetTopOuterWindowID()) {
+      return false;
+    }
+
+    if (nsIPrincipal* prin = loadInfo->PrincipalToInherit()) {
+      return IsSystemPrincipal(prin);
+    }
+    if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) {
+      return IsSystemPrincipal(prin);
+    }
+  }
+  return false;
+}
+
+bool
+ChannelWrapper::GetCanModify(ErrorResult& aRv) const
+{
+  nsCOMPtr<nsIURI> uri = GetFinalURI(aRv);
+  nsAutoCString spec;
+  if (uri) {
+    uri->GetSpec(spec);
+  }
+  if (!uri || AddonManagerWebAPI::IsValidSite(uri)) {
+    return false;
+  }
+
+  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
+    if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) {
+      if (IsSystemPrincipal(prin)) {
+        return false;
+      }
+
+      if (prin->GetIsCodebasePrincipal() &&
+          (NS_FAILED(prin->GetURI(getter_AddRefs(uri))) ||
+           AddonManagerWebAPI::IsValidSite(uri))) {
+          return false;
+      }
+    }
+  }
+  return true;
+}
+
+void
+ChannelWrapper::GetOriginURL(nsCString& aRetVal) const
+{
+  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
+    if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) {
+      nsCOMPtr<nsIURI> uri;
+      if (prin->GetIsCodebasePrincipal() &&
+          NS_SUCCEEDED(prin->GetURI(getter_AddRefs(uri)))) {
+        Unused << uri->GetSpec(aRetVal);
+      }
+    }
+  }
+}
+
+void
+ChannelWrapper::GetDocumentURL(nsCString& aRetVal) const
+{
+  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
+    if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) {
+      nsCOMPtr<nsIURI> uri;
+      if (prin->GetIsCodebasePrincipal() &&
+          NS_SUCCEEDED(prin->GetURI(getter_AddRefs(uri)))) {
+        Unused << uri->GetSpec(aRetVal);
+      }
+    }
+  }
+}
+
+int64_t
+NormalizeWindowID(nsILoadInfo* aLoadInfo, uint64_t windowID)
+{
+  if (windowID == aLoadInfo->GetTopOuterWindowID()) {
+    return 0;
+  }
+  return windowID;
+}
+
+uint64_t
+ChannelWrapper::WindowId(nsILoadInfo* aLoadInfo) const
+{
+  auto frameID = aLoadInfo->GetFrameOuterWindowID();
+  if (!frameID) {
+    frameID = aLoadInfo->GetOuterWindowID();
+  }
+  return frameID;
+}
+
+int64_t
+ChannelWrapper::WindowId() const
+{
+  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
+    auto frameID = loadInfo->GetFrameOuterWindowID();
+    if (!frameID) {
+      frameID = loadInfo->GetOuterWindowID();
+    }
+    return NormalizeWindowID(loadInfo, WindowId(loadInfo));
+  }
+  return 0;
+}
+
+int64_t
+ChannelWrapper::ParentWindowId() const
+{
+  if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
+    if (WindowId(loadInfo) == loadInfo->GetTopOuterWindowID()) {
+      return -1;
+    }
+
+    uint64_t parentID;
+    if (loadInfo->GetFrameOuterWindowID()) {
+      parentID = loadInfo->GetOuterWindowID();
+    } else {
+      parentID = loadInfo->GetParentOuterWindowID();
+    }
+    return NormalizeWindowID(loadInfo, parentID);
+  }
+  return -1;
+}
+
+/*****************************************************************************
+ * ...
+ *****************************************************************************/
+
+MozContentPolicyType
+GetContentPolicyType(uint32_t aType)
+{
+  switch (aType) {
+  case nsIContentPolicy::TYPE_DOCUMENT:
+    return MozContentPolicyType::Main_frame;
+  case nsIContentPolicy::TYPE_SUBDOCUMENT:
+    return MozContentPolicyType::Sub_frame;
+  case nsIContentPolicy::TYPE_STYLESHEET:
+    return MozContentPolicyType::Stylesheet;
+  case nsIContentPolicy::TYPE_SCRIPT:
+    return MozContentPolicyType::Script;
+  case nsIContentPolicy::TYPE_IMAGE:
+    return MozContentPolicyType::Image;
+  case nsIContentPolicy::TYPE_OBJECT:
+    return MozContentPolicyType::Object;
+  case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
+    return MozContentPolicyType::Object_subrequest;
+  case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
+    return MozContentPolicyType::Xmlhttprequest;
+  // TYPE_FETCH returns xmlhttprequest for cross-browser compatibility.
+  case nsIContentPolicy::TYPE_FETCH:
+    return MozContentPolicyType::Xmlhttprequest;
+  case nsIContentPolicy::TYPE_XBL:
+    return MozContentPolicyType::Xbl;
+  case nsIContentPolicy::TYPE_XSLT:
+    return MozContentPolicyType::Xslt;
+  case nsIContentPolicy::TYPE_PING:
+    return MozContentPolicyType::Ping;
+  case nsIContentPolicy::TYPE_BEACON:
+    return MozContentPolicyType::Beacon;
+  case nsIContentPolicy::TYPE_DTD:
+    return MozContentPolicyType::Xml_dtd;
+  case nsIContentPolicy::TYPE_FONT:
+    return MozContentPolicyType::Font;
+  case nsIContentPolicy::TYPE_MEDIA:
+    return MozContentPolicyType::Media;
+  case nsIContentPolicy::TYPE_WEBSOCKET:
+    return MozContentPolicyType::Websocket;
+  case nsIContentPolicy::TYPE_CSP_REPORT:
+    return MozContentPolicyType::Csp_report;
+  case nsIContentPolicy::TYPE_IMAGESET:
+    return MozContentPolicyType::Imageset;
+  case nsIContentPolicy::TYPE_WEB_MANIFEST:
+    return MozContentPolicyType::Web_manifest;
+  default:
+    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());
+  }
+}
+
+/*****************************************************************************
+ * ...
+ *****************************************************************************/
+
+already_AddRefed<nsIURI>
+ChannelWrapper::GetFinalURI(ErrorResult& aRv) const
+{
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  nsCOMPtr<nsIURI> uri;
+  if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
+    rv = NS_GetFinalChannelURI(chan, getter_AddRefs(uri));
+  }
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+  return uri.forget();;
+}
+
+void
+ChannelWrapper::GetFinalURL(nsCString& aRetVal, ErrorResult& aRv) const
+{
+  nsCOMPtr<nsIURI> uri = GetFinalURI(aRv);
+  if (uri) {
+    Unused << uri->GetSpec(aRetVal);
+  }
+}
+
+/*****************************************************************************
+ * ...
+ *****************************************************************************/
+
+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->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(Move(result));
+    }
+  }
+}
+
+void
+ChannelWrapper::GetRemoteAddress(nsCString& aRetVal) const
+{
+  aRetVal.SetIsVoid(true);
+  if (nsCOMPtr<nsIHttpChannelInternal> internal = QueryChannel()) {
+    Unused << internal->GetRemoteAddress(aRetVal);
+  }
+}
+
+/*****************************************************************************
+ * Glue
+ *****************************************************************************/
+
+JSObject*
+ChannelWrapper::WrapObject(JSContext* aCx, HandleObject aGivenProto)
+{
+  return ChannelWrapperBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ChannelWrapper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(ChannelWrapper)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ChannelWrapper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ChannelWrapper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(ChannelWrapper)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ChannelWrapper)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ChannelWrapper)
+
+} // namespace extensions
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.h
@@ -0,0 +1,229 @@
+/* -*-  Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_extensions_ChannelWrapper_h
+#define mozilla_extensions_ChannelWrapper_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChannelWrapperBinding.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsWeakPtr.h"
+#include "nsWrapperCache.h"
+
+#define NS_CHANNELWRAPPER_IID \
+{ 0xc06162d2, 0xb803, 0x43b4, \
+  { 0xaa, 0x31, 0xcf, 0x69, 0x7f, 0x93, 0x68, 0x1c } }
+
+class nsIDOMElement;
+class nsILoadContext;
+
+namespace mozilla {
+namespace extensions {
+
+namespace detail {
+
+  // We need to store our wrapped channel as a weak reference, since channels
+  // are not cycle collected, and we're going to be hanging this wrapper
+  // instance off the channel in order to ensure the same channel always has
+  // the same wrapper.
+  //
+  // But since performance matters here, and we don't want to have to
+  // QueryInterface the channel every time we touch it, we store separate
+  // nsIChannel and nsIHttpChannel weak references, and check that the WeakPtr
+  // is alive before returning it.
+  //
+  // This holder class prevents us from accidentally touching the weak pointer
+  // members directly from our ChannelWrapper class.
+  struct ChannelHolder
+  {
+    explicit ChannelHolder(nsIChannel* aChannel)
+      : mChannel(do_GetWeakReference(aChannel))
+      , mWeakChannel(aChannel)
+    {}
+
+    bool HaveChannel() const { return mChannel->IsAlive(); }
+
+    void SetChannel(nsIChannel* aChannel)
+    {
+      mChannel = do_GetWeakReference(aChannel);
+      mWeakChannel = aChannel;
+      mWeakHttpChannel.reset();
+    }
+
+    already_AddRefed<nsIChannel> MaybeChannel() const
+    {
+      if (!mChannel->IsAlive()) {
+        mWeakChannel = nullptr;
+      }
+      return do_AddRef(mWeakChannel);
+    }
+
+    already_AddRefed<nsIHttpChannel> MaybeHttpChannel() const
+    {
+      if (mWeakHttpChannel.isNothing()) {
+        nsCOMPtr<nsIHttpChannel> chan = QueryChannel();
+        mWeakHttpChannel.emplace(chan.get());
+      }
+
+      if (!mChannel->IsAlive()) {
+        mWeakHttpChannel.ref() = nullptr;
+      }
+      return do_AddRef(mWeakHttpChannel.value());
+    }
+
+    const nsQueryReferent QueryChannel() const { return do_QueryReferent(mChannel); }
+
+  private:
+    nsWeakPtr mChannel;
+
+    mutable nsIChannel* MOZ_NON_OWNING_REF mWeakChannel;
+    mutable Maybe<nsIHttpChannel*> MOZ_NON_OWNING_REF mWeakHttpChannel;
+  };
+}
+
+class ChannelWrapper final : public nsISupports
+                           , public nsWrapperCache
+                           , private detail::ChannelHolder
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ChannelWrapper)
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_CHANNELWRAPPER_IID)
+
+  static already_AddRefed<extensions::ChannelWrapper> Get(const dom::GlobalObject& global, nsIChannel* channel);
+
+
+  uint64_t Id() const { return mId; }
+
+  already_AddRefed<nsIChannel> GetChannel() const { return MaybeChannel(); }
+
+  void SetChannel(nsIChannel* aChannel);
+
+
+  void Cancel(uint32_t result, ErrorResult& aRv);
+
+  void RedirectTo(nsIURI* uri, ErrorResult& aRv);
+
+
+  bool Suspended() const { return mSuspended; }
+
+  void SetSuspended(bool aSuspended, ErrorResult& aRv);
+
+
+  void GetContentType(nsCString& aContentType) const;
+  void SetContentType(const nsACString& aContentType);
+
+
+  void GetMethod(nsCString& aRetVal) const;
+
+  dom::MozContentPolicyType Type() const;
+
+
+  uint32_t StatusCode() const;
+
+  void GetStatusLine(nsCString& aRetVal) const;
+
+
+  already_AddRefed<nsIURI> GetFinalURI(ErrorResult& aRv) const;
+
+  void GetFinalURL(nsCString& aRetVal, ErrorResult& aRv) const;
+
+
+  already_AddRefed<nsILoadInfo> GetLoadInfo() const
+  {
+    nsCOMPtr<nsIChannel> chan = MaybeChannel();
+    if (chan) {
+      return chan->GetLoadInfo();
+    }
+    return nullptr;
+  }
+
+  int64_t WindowId() const;
+
+  int64_t ParentWindowId() const;
+
+  bool IsSystemLoad() const;
+
+  void GetOriginURL(nsCString& aRetVal) const;
+
+  void GetDocumentURL(nsCString& aRetVal) const;
+
+
+  already_AddRefed<nsILoadContext> GetLoadContext() const;
+
+  already_AddRefed<nsIDOMElement> GetBrowserElement() const;
+
+
+  bool GetCanModify(ErrorResult& aRv) const;
+
+
+  void GetProxyInfo(dom::Nullable<dom::MozProxyInfo>& aRetVal, ErrorResult& aRv) const;
+
+  void GetRemoteAddress(nsCString& aRetVal) const;
+
+
+  void GetRequestHeaders(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv) const;
+
+  void GetResponseHeaders(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv) const;
+
+  void SetRequestHeader(const nsCString& header, const nsCString& value, ErrorResult& aRv);
+
+  void SetResponseHeader(const nsCString& header, const nsCString& value, ErrorResult& aRv);
+
+
+  nsISupports* GetParentObject() const { return mParent; }
+
+  JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
+protected:
+  ~ChannelWrapper() = default;
+
+private:
+  ChannelWrapper(nsISupports* aParent, nsIChannel* aChannel)
+    : ChannelHolder(aChannel)
+    , mParent(aParent)
+  {}
+
+  void ClearCachedAttributes();
+
+  bool CheckAlive(ErrorResult& aRv) const
+  {
+    if (!HaveChannel()) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return false;
+    }
+    return true;
+  }
+
+  uint64_t WindowId(nsILoadInfo* aLoadInfo) const;
+
+  static uint64_t GetNextId()
+  {
+    static uint64_t sNextId = 1;
+    return ++sNextId;
+  }
+
+  const uint64_t mId = GetNextId();
+  nsCOMPtr<nsISupports> mParent;
+
+  bool mSuspended = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ChannelWrapper,
+                              NS_CHANNELWRAPPER_IID)
+
+} // namespace extensions
+} // namespace mozilla
+
+#endif // mozilla_extensions_ChannelWrapper_h
--- a/toolkit/components/extensions/webrequest/moz.build
+++ b/toolkit/components/extensions/webrequest/moz.build
@@ -7,16 +7,17 @@
 XPIDL_SOURCES += [
     'mozIWebRequestService.idl',
     'nsIWebRequestListener.idl',
 ]
 
 XPIDL_MODULE = 'webextensions'
 
 UNIFIED_SOURCES += [
+    'ChannelWrapper.cpp',
     'nsWebRequestListener.cpp',
     'StreamFilter.cpp',
     'StreamFilterChild.cpp',
     'StreamFilterEvents.cpp',
     'StreamFilterParent.cpp',
     'WebRequestService.cpp',
 ]
 
@@ -28,21 +29,26 @@ EXPORTS += [
     'nsWebRequestListener.h',
 ]
 
 EXPORTS.mozilla += [
     'WebRequestService.h',
 ]
 
 EXPORTS.mozilla.extensions += [
+    'ChannelWrapper.h',
     'StreamFilter.h',
     'StreamFilterBase.h',
     'StreamFilterChild.h',
     'StreamFilterEvents.h',
     'StreamFilterParent.h',
 ]
 
+LOCAL_INCLUDES += [
+    '/caps',
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 with Files("**"):
     BUG_COMPONENT = ("Toolkit", "WebExtensions: Request Handling")