Bug 1396856: Part 3 - Add a WebIDL wrapper class for necko channels. r=ehsan,mixedpuppy
authorKris Maglione <maglione.k@gmail.com>
Wed, 06 Sep 2017 14:38:23 -0700
changeset 379456 223a7c5a7c471f5fbf319029da2dcc5a093b8548
parent 379455 3b496e60f978d3ca2792c90ba01207cd7427aaf6
child 379457 30fe3c84b60ee8ae1ab865733041173902892e8f
push id50642
push userarchaeopteryx@coole-files.de
push dateThu, 07 Sep 2017 10:41:07 +0000
treeherderautoland@bd0ce93776fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, mixedpuppy
bugs1396856
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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/base/nsIContentPolicy.idl
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/base/nsIContentPolicy.idl
+++ b/dom/base/nsIContentPolicy.idl
@@ -335,18 +335,18 @@ interface nsIContentPolicy : nsISupports
    * This will be mapped to TYPE_SCRIPT before being passed to content policy
    * implementations.
    */
   const nsContentPolicyType TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS = 42;
 
   /* When adding new content types, please update nsContentBlocker,
    * NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
    * implementations, the static_assert in dom/cache/DBSchema.cpp,
-   * and other things that are not listed here that are related to
-   * nsIContentPolicy. */
+   * ChannelWrapper.webidl, ChannelWrapper.cpp, and other things that
+   * are not listed here that are related to nsIContentPolicy. */
 
   //////////////////////////////////////////////////////////////////////
 
   /**
    * Returned from shouldLoad or shouldProcess if the load or process request
    * is rejected based on details of the request.
    */
   const short REJECT_REQUEST = -1;
--- 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,153 @@
+/* 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;
+
+/**
+ * Load types that correspond to the external types in nsIContentPolicy.idl.
+ * Please also update that IDL when updating this list.
+ */
+enum MozContentPolicyType {
+  "main_frame",
+  "sub_frame",
+  "stylesheet",
+  "script",
+  "image",
+  "object",
+  "object_subrequest",
+  "xmlhttprequest",
+  "fetch",
+  "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,679 @@
+/* -*- 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_DIAGNOSTIC_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;
+
+  nsrefcnt 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_IMPL_QUERY_INTERFACE(HeaderVisitor, nsIHttpHeaderVisitor)
+
+} // 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
+ *****************************************************************************/
+
+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<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;
+}
+
+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->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()) {
+    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)
+{
+  // Note: Please keep this function in sync with the external types in
+  // nsIContentPolicy.idl
+  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")