Bug 1305237 Expose frameAncestors to webextensions, r=bz,kmag
authorShane Caraveo <scaraveo@mozilla.com>
Tue, 10 Oct 2017 09:54:22 -0700
changeset 385359 cbe84780624c45b29d8846fe95e7bbb9b64ddba3
parent 385358 ebd2ef6abc6a7034a0a41f5954aec6ea3793b53f
child 385360 f1ecd5c26948c4f62149565efbfcc28abb656fa4
child 385385 b3109db72d872bdf8ab0622a9234db0a02ec99fc
push id32652
push userarchaeopteryx@coole-files.de
push dateTue, 10 Oct 2017 21:49:31 +0000
treeherdermozilla-central@f1ecd5c26948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, kmag
bugs1305237
milestone58.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 1305237 Expose frameAncestors to webextensions, r=bz,kmag MozReview-Commit-ID: 64lIMu6neaD
dom/webidl/ChannelWrapper.webidl
toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html
toolkit/components/extensions/test/mochitest/file_simple_sandboxed_subframe.html
toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
toolkit/components/extensions/test/mochitest/mochitest-common.ini
toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
toolkit/components/extensions/webrequest/ChannelWrapper.cpp
toolkit/components/extensions/webrequest/ChannelWrapper.h
toolkit/modules/addons/WebRequest.jsm
toolkit/modules/addons/WebRequestContent.js
toolkit/modules/tests/browser/browser.ini
toolkit/modules/tests/browser/browser_WebRequest.js
toolkit/modules/tests/browser/browser_WebRequest_ancestors.js
--- a/dom/webidl/ChannelWrapper.webidl
+++ b/dom/webidl/ChannelWrapper.webidl
@@ -281,16 +281,28 @@ interface ChannelWrapper : EventTarget {
   /**
    * For cross-process requests, the <browser> or <iframe> element to which the
    * content loading this request belongs. For requests that don't originate
    * from a remote browser, this is null.
    */
   [Cached, Pure]
   readonly attribute nsISupports? browserElement;
 
+  /**
+   * Returns an array of objects that combine the url and frameId from the
+   * ancestorPrincipals and ancestorOuterWindowIDs on loadInfo.
+   * The immediate parent is the first entry, the last entry is always the top
+   * level frame.  It will be an empty list for toplevel window loads and
+   * non-subdocument resource loads within a toplevel window.  For the latter,
+   * originURL will provide information on what window is doing the load.  It
+   * will be null if the request is not associated with a window (e.g. XHR with
+   * mozBackgroundRequest = true).
+   */
+  [Cached, Frozen, GetterThrows, Pure]
+  readonly attribute sequence<MozFrameAncestorInfo>? frameAncestors;
 
   /**
    * For HTTP requests, returns an array of request headers which will be, or
    * have been, sent with this request.
    *
    * For non-HTTP requests, throws NS_ERROR_UNEXPECTED.
    */
   [Throws]
--- a/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html
+++ b/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html
@@ -7,12 +7,17 @@
 <body>
 
 <script>
 "use strict";
 
 let req = new XMLHttpRequest();
 req.open("GET", "/xhr_sandboxed");
 req.send();
+
+let sandbox = document.createElement("iframe");
+sandbox.setAttribute("sandbox", "allow-scripts");
+sandbox.setAttribute("src", "file_simple_sandboxed_subframe.html");
+document.documentElement.appendChild(sandbox);
 </script>
 <img src="file_image_great.png"/>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_subframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+
+</body>
+</html>
--- a/toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
+++ b/toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
@@ -13,11 +13,12 @@ let req = new XMLHttpRequest();
 req.open("GET", "/xhr_resource_2");
 req.send();
 
 let sandbox = document.createElement("iframe");
 sandbox.setAttribute("sandbox", "allow-scripts");
 sandbox.setAttribute("src", "file_simple_sandboxed_frame.html");
 document.documentElement.appendChild(sandbox);
 </script>
-<img src="file_image_bad.png#2"/>
+<img src="file_image_redirect.png"/>
+<iframe src="data:text/plain,webRequestTest"/>
 </body>
 </html>
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -35,16 +35,17 @@ support-files =
   file_style_redirect.css
   file_script_good.js
   file_script_bad.js
   file_script_redirect.js
   file_script_xhr.js
   file_remote_frame.html
   file_sample.html
   file_simple_sandboxed_frame.html
+  file_simple_sandboxed_subframe.html
   file_simple_xhr.html
   file_simple_xhr_frame.html
   file_simple_xhr_frame2.html
   redirect_auto.sjs
   redirection.sjs
   file_privilege_escalation.html
   file_ext_test_api_injection.js
   file_permission_xhr.html
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
@@ -40,68 +40,129 @@ let extensionData = {
 let expected = {
   "file_simple_xhr.html": {
     type: "main_frame",
     toplevel: true,
   },
   "file_image_good.png": {
     type: "image",
     toplevel: true,
+    origin: "file_simple_xhr.html",
   },
   "example.txt": {
     type: "xmlhttprequest",
     toplevel: true,
+    origin: "file_simple_xhr.html",
   },
+  // sub frames will have the origin and first ancestor is the
+  // parent document
   "file_simple_xhr_frame.html": {
     type: "sub_frame",
     toplevelParent: true,
+    origin: "file_simple_xhr.html",
+    parent: "file_simple_xhr.html",
+  },
+  // a resource in a sub frame will have origin of the subframe,
+  // but the ancestor chain starts with the parent document
+  "xhr_resource": {
+    type: "xmlhttprequest",
+    origin: "file_simple_xhr_frame.html",
+    parent: "file_simple_xhr.html",
   },
   "file_image_bad.png": {
     type: "image",
-  },
-  "xhr_resource": {
-    type: "xmlhttprequest",
+    depth: 2,
+    origin: "file_simple_xhr_frame.html",
+    parent: "file_simple_xhr.html",
   },
   "file_simple_xhr_frame2.html": {
     type: "sub_frame",
+    depth: 2,
+    origin: "file_simple_xhr_frame.html",
+    parent: "file_simple_xhr_frame.html",
   },
-  "file_image_bad.png#2": {
+  "file_image_redirect.png": {
     type: "image",
+    depth: 2,
+    origin: "file_simple_xhr_frame2.html",
+    parent: "file_simple_xhr_frame.html",
   },
   "xhr_resource_2": {
     type: "xmlhttprequest",
+    depth: 2,
+    origin: "file_simple_xhr_frame2.html",
+    parent: "file_simple_xhr_frame.html",
   },
-  // This is loaded in a sandbox iframe.
+  // Last frame tests content policy frame ancestors.
+  "webRequestTest": {
+    type: "sub_frame",
+    depth: 3,
+    origin: "file_simple_xhr_frame2.html",
+    parent: "file_simple_xhr_frame2.html",
+  },
+  // This is loaded in a sandbox iframe.  originUrl is not availabe for that,
+  // and requests within a sandboxed iframe will additionally have an empty
+  // url on their immediate parent/ancestor.
   "file_simple_sandboxed_frame.html": {
     type: "sub_frame",
+    depth: 3,
+    parent: "file_simple_xhr_frame2.html",
   },
   "xhr_sandboxed": {
     type: "xmlhttprequest",
     sandboxed: true,
+    depth: 3,
+    parent: "",
   },
   "file_image_great.png": {
     type: "image",
     sandboxed: true,
+    depth: 3,
+    parent: "",
+  },
+  "file_simple_sandboxed_subframe.html": {
+    type: "sub_frame",
+    depth: 4,
+    parent: "",
   },
 };
 
 function checkDetails(details) {
   let url = new URL(details.url);
-  let filename = url.pathname.split("/").pop();
+  let filename = url.pathname.split(url.protocol == "data:" ? "," : "/").pop();
   let expect = expected[filename];
   is(expect.type, details.type, `${details.type} type matches`);
+  if (details.parentFrameId == -1) {
+    is(details.frameAncestors.length, 0, "no ancestors for main_frame requests");
+  } else if (details.parentFrameId == 0) {
+    is(details.frameAncestors.length, 1, "one ancestors for sub_frame requests");
+  } else {
+    ok(details.frameAncestors.length > 1, "have multiple ancestors for deep subframe requests");
+    is(details.frameAncestors.length, expect.depth, "have multiple ancestors for deep subframe requests");
+  }
+  if (details.parentFrameId > -1) {
+    ok(!expect.origin || details.originUrl.includes(expect.origin), "origin url is correct");
+    is(details.frameAncestors[0].frameId, details.parentFrameId, "first ancestor matches request.parentFrameId");
+    ok(details.frameAncestors[0].url.includes(expect.parent), "ancestor parent page correct");
+    is(details.frameAncestors[details.frameAncestors.length - 1].frameId, 0, "last ancestor is always zero");
+    // All our tests should be somewhere within the frame that we set topframe in the query string.  That
+    // frame will always be the last ancestor.
+    ok(details.frameAncestors[details.frameAncestors.length - 1].url.includes("topframe=true"), "last ancestor is always topframe");
+  }
   if (expect.toplevel) {
     is(details.frameId, 0, "expect load at top level");
     is(details.parentFrameId, -1, "expect top level frame to have no parent");
   } else if (details.type == "sub_frame") {
     ok(details.frameId > 0, "expect sub_frame to load into a new frame");
     if (expect.toplevelParent) {
       is(details.parentFrameId, 0, "expect sub_frame to have top level parent");
+      is(details.frameAncestors.length, 1, "one ancestor for top sub_frame request");
     } else {
       ok(details.parentFrameId > 0, "expect sub_frame to have parent");
+      ok(details.frameAncestors.length > 1, "sub_frame has ancestors");
     }
     expect.subframeId = details.frameId;
     expect.parentId = details.parentFrameId;
   } else if (expect.sandboxed) {
     is(details.documentUrl, undefined, "null principal documentUrl for sandboxed request");
   } else {
     // get the parent frame.
     let purl = new URL(details.documentUrl);
@@ -116,28 +177,27 @@ add_task(async function test_webRequest_
   // Clear the image cache, since it gets in the way otherwise.
   let imgTools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(SpecialPowers.Ci.imgITools);
   let cache = imgTools.getImgCacheForDocument(document);
   cache.clearCache(false);
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   await extension.startup();
 
-  let a = addLink(`file_simple_xhr.html?nocache=${Math.random()}`);
+  let a = addLink(`file_simple_xhr.html?topframe=true&nocache=${Math.random()}`);
   a.click();
 
   for (let i = 0; i < Object.keys(expected).length; i++) {
     checkDetails(await extension.awaitMessage("onBeforeRequest"));
   }
 
   let closed = extension.awaitMessage("tab-closed");
   extension.sendMessage("close-tab");
   await closed;
   await extension.unload();
 });
-
 </script>
 </head>
 <body>
 <div id="test">Sample text</div>
 
 </body>
 </html>
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -564,16 +564,70 @@ ChannelWrapper::ParentWindowId() const
     } else {
       parentID = loadInfo->GetParentOuterWindowID();
     }
     return NormalizeWindowID(loadInfo, parentID);
   }
   return -1;
 }
 
+void
+ChannelWrapper::GetFrameAncestors(dom::Nullable<nsTArray<dom::MozFrameAncestorInfo>>& aFrameAncestors, ErrorResult& aRv) const
+{
+  nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo();
+  if (!loadInfo || WindowId(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>& ancestorOuterWindowIDs = aLoadInfo->AncestorOuterWindowIDs();
+  uint32_t size = ancestorPrincipals.Length();
+  MOZ_DIAGNOSTIC_ASSERT(size == ancestorOuterWindowIDs.Length());
+  if (size != ancestorOuterWindowIDs.Length()) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  bool subFrame = aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::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.parentWindowId == 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 = ParentWindowId();
+  }
+
+  for (uint32_t i = 0; i < size; ++i) {
+    auto ancestor = aFrameAncestors.AppendElement();
+    nsCOMPtr<nsIURI> uri;
+    MOZ_TRY(ancestorPrincipals[i]->GetURI(getter_AddRefs(uri)));
+    if (!uri) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    MOZ_TRY(uri->GetSpec(ancestor->mUrl));
+    ancestor->mFrameId = NormalizeWindowID(aLoadInfo, ancestorOuterWindowIDs[i]);
+  }
+  return NS_OK;
+}
+
 /*****************************************************************************
  * Response filtering
  *****************************************************************************/
 
 void
 ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon, nsITabParent* aTabParent)
 {
   mAddonEntries.Put(aAddon.Id(), aTabParent);
@@ -716,17 +770,17 @@ ChannelWrapper::GetStatusLine(nsCString&
 
 already_AddRefed<nsIURI>
 ChannelWrapper::FinalURI() const
 {
   nsCOMPtr<nsIURI> uri;
   if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
     NS_GetFinalChannelURI(chan, getter_AddRefs(uri));
   }
-  return uri.forget();;
+  return uri.forget();
 }
 
 void
 ChannelWrapper::GetFinalURL(nsString& aRetVal) const
 {
   if (HaveChannel()) {
     aRetVal = FinalURLInfo().Spec();
   }
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.h
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.h
@@ -185,16 +185,18 @@ public:
     }
     return nullptr;
   }
 
   int64_t WindowId() const;
 
   int64_t ParentWindowId() const;
 
+  void GetFrameAncestors(dom::Nullable<nsTArray<dom::MozFrameAncestorInfo>>& aFrameAncestors, ErrorResult& aRv) const;
+
   bool IsSystemLoad() const;
 
   void GetOriginURL(nsCString& aRetVal) const;
 
   void GetDocumentURL(nsCString& aRetVal) const;
 
   already_AddRefed<nsIURI> GetOriginURI() const;
 
@@ -257,16 +259,18 @@ private:
 
 
   const URLInfo& FinalURLInfo() const;
   const URLInfo* DocumentURLInfo() const;
 
 
   uint64_t WindowId(nsILoadInfo* aLoadInfo) const;
 
+  nsresult GetFrameAncestors(nsILoadInfo* aLoadInfo, nsTArray<dom::MozFrameAncestorInfo>& aFrameAncestors) const;
+
   static uint64_t GetNextId()
   {
     static uint64_t sNextId = 1;
     return ++sNextId;
   }
 
   void CheckEventListeners();
 
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -166,17 +166,17 @@ class ResponseHeaderChanger extends Head
 }
 
 const MAYBE_CACHED_EVENTS = new Set([
   "onResponseStarted", "onBeforeRedirect", "onCompleted", "onErrorOccurred",
 ]);
 
 const OPTIONAL_PROPERTIES = [
   "requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
-  "requestBody", "scheme", "realm", "isProxy", "challenger", "proxyInfo", "ip",
+  "requestBody", "scheme", "realm", "isProxy", "challenger", "proxyInfo", "ip", "frameAncestors",
 ];
 
 function serializeRequestData(eventName) {
   let data = {
     requestId: this.requestId,
     url: this.url,
     originUrl: this.originUrl,
     documentUrl: this.documentUrl,
--- a/toolkit/modules/addons/WebRequestContent.js
+++ b/toolkit/modules/addons/WebRequestContent.js
@@ -111,16 +111,17 @@ var ContentPolicy = {
     }
 
     if (!ids.length) {
       return Ci.nsIContentPolicy.ACCEPT;
     }
 
     let windowId = 0;
     let parentWindowId = -1;
+    let frameAncestors = [];
     let mm = Services.cpmm;
 
     function getWindowId(window) {
       return window.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils)
         .outerWindowID;
     }
 
@@ -145,16 +146,28 @@ var ContentPolicy = {
           doc = node;
         }
         window = doc.defaultView;
       }
 
       windowId = getWindowId(window);
       if (window.parent !== window) {
         parentWindowId = getWindowId(window.parent);
+
+        for (let frame = window.parent; ; frame = frame.parent) {
+          frameAncestors.push({
+            url: frame.document.documentURIObject.spec,
+            frameId: getWindowId(frame),
+          });
+          if (frame === frame.parent) {
+            // Set the last frameId to zero for top level frame.
+            frameAncestors[frameAncestors.length - 1].frameId = 0;
+            break;
+          }
+        }
       }
 
       let ir = window.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDocShell)
                      .QueryInterface(Ci.nsIInterfaceRequestor);
       try {
         // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
         mm = ir.getInterface(Ci.nsIContentFrameMessageManager);
@@ -165,16 +178,19 @@ var ContentPolicy = {
       }
     }
 
     let data = {ids,
                 url,
                 type: WebRequestCommon.typeForPolicyType(policyType),
                 windowId,
                 parentWindowId};
+    if (frameAncestors.length > 0) {
+      data.frameAncestors = frameAncestors;
+    }
     if (requestOrigin) {
       data.originUrl = requestOrigin.spec;
     }
     mm.sendAsyncMessage("WebRequest:ShouldLoad", data);
 
     return Ci.nsIContentPolicy.ACCEPT;
   },
 
--- a/toolkit/modules/tests/browser/browser.ini
+++ b/toolkit/modules/tests/browser/browser.ini
@@ -30,14 +30,15 @@ support-files =
 [browser_FinderHighlighter.js]
 skip-if = debug || os = "linux"
 support-files = file_FinderSample.html
 [browser_Geometry.js]
 [browser_InlineSpellChecker.js]
 [browser_WebNavigation.js]
 skip-if = true # Superseded by WebExtension tests
 [browser_WebRequest.js]
+[browser_WebRequest_ancestors.js]
 [browser_WebRequest_cookies.js]
 [browser_WebRequest_filtering.js]
 [browser_PageMetadata.js]
 [browser_PromiseMessage.js]
 [browser_RemotePageManager.js]
 [browser_Troubleshoot.js]
--- a/toolkit/modules/tests/browser/browser_WebRequest.js
+++ b/toolkit/modules/tests/browser/browser_WebRequest.js
@@ -39,16 +39,21 @@ function onBeforeRequest(details) {
     is(details.browser, expected_browser, "correct <browser> element");
     checkType(details);
 
     windowIDs.set(details.url, details.windowId);
     if (details.url.indexOf("page2") != -1) {
       let page1id = windowIDs.get(URL);
       ok(details.windowId != page1id, "sub-frame gets its own window ID");
       is(details.parentWindowId, page1id, "parent window id is correct");
+
+      is(details.frameAncestors.length, 1, "correctly has only one ancestor");
+      let ancestor = details.frameAncestors[0];
+      ok(ancestor.url.includes("page1"), "parent window url seems correct");
+      is(ancestor.frameId, page1id, "parent window id is correct");
     }
   }
   if (details.url.indexOf("_bad.") != -1) {
     return {cancel: true};
   }
   return undefined;
 }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/browser/browser_WebRequest_ancestors.js
@@ -0,0 +1,54 @@
+"use strict";
+
+var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+var {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
+var {PromiseUtils} = Cu.import("resource://gre/modules/PromiseUtils.jsm", {});
+
+add_task(async function test_ancestors_exist() {
+  let deferred = PromiseUtils.defer();
+  function onBeforeRequest(details) {
+    info(`onBeforeRequest ${details.url}`);
+    ok(typeof details.frameAncestors === "object", `ancestors exists [${typeof details.frameAncestors}]`);
+    deferred.resolve();
+  }
+
+  WebRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: new MatchPatternSet(["http://mochi.test/test/*"])}, ["blocking"]);
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/test/");
+  await deferred.promise;
+  await BrowserTestUtils.removeTab(tab);
+
+  WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
+});
+
+add_task(async function test_ancestors_null() {
+  let deferred = PromiseUtils.defer();
+  function onBeforeRequest(details) {
+    info(`onBeforeRequest ${details.url}`);
+    ok(details.frameAncestors === undefined, "ancestors do not exist");
+    deferred.resolve();
+  }
+
+  WebRequest.onBeforeRequest.addListener(onBeforeRequest, null, ["blocking"]);
+
+  function fetch(url) {
+    return new Promise((resolve, reject) => {
+      let xhr = new XMLHttpRequest();
+      xhr.mozBackgroundRequest = true;
+      xhr.open("GET", url);
+      xhr.onload = () => { resolve(xhr.responseText); };
+      xhr.onerror = () => { reject(xhr.status); };
+      // use a different contextId to avoid auth cache.
+      xhr.setOriginAttributes({userContextId: 1});
+      xhr.send();
+    });
+  }
+
+  await fetch("http://mochi.test:8888/test/");
+  await deferred.promise;
+
+  WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
+});