Bug 1273138 fix WebRequest for background pages, r=kmag draft
authorShane Caraveo <scaraveo@mozilla.com>
Wed, 02 Nov 2016 09:30:50 -0700
changeset 432769 7d82d1000473b2530d6144987a28e35815c11999
parent 432726 52e103db1287d122e6b434840affbae7335c74f9
child 535748 deae44d66b3d4917c6d6b3dfb183c0c64460cd17
push id34418
push usermixedpuppy@gmail.com
push dateWed, 02 Nov 2016 18:34:14 +0000
reviewerskmag
bugs1273138
milestone52.0a1
Bug 1273138 fix WebRequest for background pages, r=kmag MozReview-Commit-ID: DEW9anMmKi2
browser/components/extensions/ext-tabs.js
toolkit/components/extensions/ext-webNavigation.js
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/test/mochitest/.eslintrc.js
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/mochitest.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html
toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js
toolkit/components/extensions/test/mochitest/webrequest_test.jsm
toolkit/components/extensions/test/mochitest/webrequest_worker.js
toolkit/modules/addons/WebRequest.jsm
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -68,24 +68,18 @@ extensions.on("page-shutdown", (type, co
       let tab = gBrowser.getTabForBrowser(context.xulBrowser);
       if (tab) {
         gBrowser.removeTab(tab);
       }
     }
   }
 });
 
-extensions.on("fill-browser-data", (type, browser, data, result) => {
-  let tabId = TabManager.getBrowserId(browser);
-  if (tabId == -1) {
-    result.cancel = true;
-    return;
-  }
-
-  data.tabId = tabId;
+extensions.on("fill-browser-data", (type, browser, data) => {
+  data.tabId = browser ? TabManager.getBrowserId(browser) : -1;
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 global.currentWindow = function(context) {
   let {xulWindow} = context;
   if (xulWindow && context.viewType != "background") {
     return xulWindow;
   }
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -104,36 +104,30 @@ function WebNavigationEventManager(conte
     let filters = urlFilters ?
           new MatchURLFilters(urlFilters.url) : null;
 
     let listener = data => {
       if (!data.browser) {
         return;
       }
 
-      let tabId = TabManager.getBrowserId(data.browser);
-      if (tabId == -1) {
-        return;
-      }
-
       let data2 = {
         url: data.url,
         timeStamp: Date.now(),
         frameId: ExtensionManagement.getFrameId(data.windowId),
         parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
       };
 
       if (eventName == "onErrorOccurred") {
         data2.error = data.error;
       }
 
       // Fills in tabId typically.
-      let result = {};
-      extensions.emit("fill-browser-data", data.browser, data2, result);
-      if (result.cancel) {
+      extensions.emit("fill-browser-data", data.browser, data2);
+      if (data2.tabId < 0) {
         return;
       }
 
       fillTransitionProperties(eventName, data, data2);
 
       runSafe(context, callback, data2);
     };
 
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -18,22 +18,19 @@ var {
 
 // EventManager-like class specifically for WebRequest. Inherits from
 // SingletonEventManager. Takes care of converting |details| parameter
 // when invoking listeners.
 function WebRequestEventManager(context, eventName) {
   let name = `webRequest.${eventName}`;
   let register = (callback, filter, info) => {
     let listener = data => {
-      if (!data.browser) {
-        return;
-      }
-
-      let tabId = TabManager.getBrowserId(data.browser);
-      if (tabId == -1) {
+      // Prevent listening in on requests originating from system principal to
+      // prevent tinkering with OCSP, app and addon updates, etc.
+      if (data.isSystemPrincipal) {
         return;
       }
 
       let data2 = {
         requestId: data.requestId,
         url: data.url,
         originUrl: data.originUrl,
         method: data.method,
@@ -47,22 +44,17 @@ function WebRequestEventManager(context,
       if (maybeCached.includes(eventName)) {
         data2.fromCache = !!data.fromCache;
       }
 
       if ("ip" in data) {
         data2.ip = data.ip;
       }
 
-      // Fills in tabId typically.
-      let result = {};
-      extensions.emit("fill-browser-data", data.browser, data2, result);
-      if (result.cancel) {
-        return;
-      }
+      extensions.emit("fill-browser-data", data.browser, data2);
 
       let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
                       "requestBody"];
       for (let opt of optional) {
         if (opt in data) {
           data2[opt] = data[opt];
         }
       }
--- a/toolkit/components/extensions/test/mochitest/.eslintrc.js
+++ b/toolkit/components/extensions/test/mochitest/.eslintrc.js
@@ -3,18 +3,21 @@
 module.exports = { // eslint-disable-line no-undef
   "extends": "../../../../../testing/mochitest/mochitest.eslintrc.js",
 
   "env": {
     "webextensions": true,
   },
 
   "globals": {
+    "ChromeWorker": false,
+    "onmessage": true,
     "sendAsyncMessage": false,
 
     "waitForLoad": true,
     "promiseConsoleOutput": true,
 
     "ExtensionTestUtils": false,
     "NetUtil": true,
+    "webrequest_test": false,
     "XPCOMUtils": true,
   },
 };
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 support-files =
   chrome_head.js
   head.js
   file_sample.html
+  webrequest_chromeworker.js
+  webrequest_test.jsm
 tags = webextensions
 
 [test_chrome_ext_background_debug_global.html]
 skip-if = (os == 'android') # android doesn't have devtools
 [test_chrome_ext_background_page.html]
 skip-if = (toolkit == 'android') # android doesn't have devtools
 [test_chrome_ext_eventpage_warning.html]
 [test_chrome_ext_contentscript_unrecognizedprop_warning.html]
@@ -21,8 +23,10 @@ skip-if = (os == 'android') # browser.ta
 skip-if = os != "mac" && os != "linux"
 [test_ext_cookies_expiry.html]
 [test_ext_cookies_permissions.html]
 [test_ext_jsversion.html]
 [test_ext_schema.html]
 [test_chrome_ext_storage_cleanup.html]
 [test_chrome_ext_idle.html]
 [test_chrome_ext_downloads_saveAs.html]
+[test_chrome_ext_webrequest_background_events.html]
+skip-if = os == 'android' # webrequest api unsupported (bug 1258975).
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -29,16 +29,17 @@ support-files =
   file_script_redirect.js
   file_script_xhr.js
   file_sample.html
   redirection.sjs
   file_privilege_escalation.html
   file_ext_test_api_injection.js
   file_permission_xhr.html
   file_teardown_test.js
+  webrequest_worker.js
 tags = webextensions
 
 [test_clipboard.html]
 # skip-if = # disabled test case with_permission_allow_copy, see inline comment.
 [test_ext_inIncognitoContext_window.html]
 skip-if = os == 'android' # Android does not currently support windows.
 [test_ext_geturl.html]
 [test_ext_background_canvas.html]
@@ -86,16 +87,18 @@ skip-if = (os == 'android' || buildapp =
 skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250
 [test_ext_unload_frame.html]
 [test_ext_i18n.html]
 skip-if = (os == 'android') # Bug 1258975 on android.
 [test_ext_web_accessible_resources.html]
 skip-if = (os == 'android') # Bug 1258975 on android.
 [test_ext_webrequest.html]
 skip-if = os == 'android' # webrequest api unsupported (bug 1258975).
+[test_ext_webrequest_background_events.html]
+skip-if = os == 'android' # webrequest api unsupported (bug 1258975).
 [test_ext_webrequest_upload.html]
 skip-if = os == 'android' # webrequest api unsupported (bug 1258975).
 [test_ext_webnavigation.html]
 skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975).
 [test_ext_webnavigation_filters.html]
 skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975).
 [test_ext_subframes_privileges.html]
 skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975).
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for simple WebExtension</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="chrome_head.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+Cu.import(SimpleTest.getTestFileURL("webrequest_test.jsm"));
+let {testFetch, testXHR} = webrequest_test;
+
+// Here we test that any requests originating from a system principal are not
+// accessible through WebRequest.  text_ext_webrequest_background_events tests
+// non-system principal requests.
+
+let testExtension = {
+  manifest: {
+    permissions: [
+      "webRequest",
+      "<all_urls>",
+    ],
+  },
+  background() {
+    let eventNames = [
+      "onBeforeRequest",
+      "onBeforeSendHeaders",
+      "onSendHeaders",
+      "onHeadersReceived",
+      "onResponseStarted",
+      "onCompleted",
+    ];
+
+    function listener(name, details) {
+      // If we get anything, we failed.  Removing the system principal check
+      // in ext-webrequest triggers this failure.
+      browser.test.fail(`recieved ${name}`);
+    }
+
+    for (let name of eventNames) {
+      browser.webRequest[name].addListener(
+        listener.bind(null, name),
+        {urls: ["https://example.com/*"]}
+      );
+    }
+  },
+};
+
+add_task(function* test_webRequest_chromeworker_events() {
+  let extension = ExtensionTestUtils.loadExtension(testExtension);
+  yield extension.startup();
+  yield new Promise(resolve => {
+    let worker = new ChromeWorker("webrequest_chromeworker.js");
+    worker.onmessage = event => {
+      ok("chrome worker fetch finished");
+      resolve();
+    };
+    worker.postMessage("go");
+  });
+  yield extension.unload();
+});
+
+add_task(function* test_webRequest_chromepage_events() {
+  let extension = ExtensionTestUtils.loadExtension(testExtension);
+  yield extension.startup();
+  yield new Promise(resolve => {
+    fetch("https://example.com/example.txt").then(() => {
+      ok("test page loaded");
+      resolve();
+    });
+  });
+  yield extension.unload();
+});
+
+add_task(function* test_webRequest_jsm_events() {
+  let extension = ExtensionTestUtils.loadExtension(testExtension);
+  yield extension.startup();
+  yield testFetch("https://example.com/example.txt").then(() => {
+    ok("fetch page loaded");
+  });
+  yield testXHR("https://example.com/example.txt").then(() => {
+    ok("xhr page loaded");
+  });
+  yield extension.unload();
+});
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for simple WebExtension</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(function* test_webRequest_serviceworker_events() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [["dom.serviceWorkers.testing.enabled", true]],
+  });
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: [
+        "webRequest",
+        "<all_urls>",
+      ],
+    },
+    background() {
+      let eventNames = new Set([
+        "onBeforeRequest",
+        "onBeforeSendHeaders",
+        "onSendHeaders",
+        "onHeadersReceived",
+        "onResponseStarted",
+        "onCompleted",
+      ]);
+
+      function listener(name, details) {
+        browser.test.assertTrue(eventNames.has(name), `recieved ${name}`);
+        eventNames.delete(name);
+        if (eventNames.size == 0) {
+          browser.test.sendMessage("done");
+        }
+      }
+
+      for (let name of eventNames) {
+        browser.webRequest[name].addListener(
+          listener.bind(null, name),
+          {urls: ["https://example.com/*"]}
+        );
+      }
+    },
+  });
+
+  yield extension.startup();
+  let registration = yield navigator.serviceWorker.register("webrequest_worker.js", {scope: "."});
+  yield extension.awaitMessage("done");
+  yield registration.unregister();
+  yield extension.unload();
+});
+
+add_task(function* test_webRequest_background_events() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: [
+        "webRequest",
+        "<all_urls>",
+      ],
+    },
+    background() {
+      let eventNames = new Set([
+        "onBeforeRequest",
+        "onBeforeSendHeaders",
+        "onSendHeaders",
+        "onHeadersReceived",
+        "onResponseStarted",
+        "onCompleted",
+      ]);
+
+      function listener(name, details) {
+        browser.test.assertTrue(eventNames.has(name), `recieved ${name}`);
+        eventNames.delete(name);
+      }
+
+      for (let name of eventNames) {
+        browser.webRequest[name].addListener(
+          listener.bind(null, name),
+          {urls: ["https://example.com/*"]}
+        );
+      }
+
+      fetch("https://example.com/example.txt").then(() => {
+        browser.test.assertEq(0, eventNames.size, "messages recieved");
+        browser.test.sendMessage("done");
+      }, () => {
+        browser.test.fail("fetch recieved");
+        browser.test.sendMessage("done");
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitMessage("done");
+  yield extension.unload();
+});
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js
@@ -0,0 +1,8 @@
+"use strict";
+
+onmessage = function(event) {
+  fetch("https://example.com/example.txt").then(() => {
+    postMessage("Done!");
+  });
+};
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/webrequest_test.jsm
@@ -0,0 +1,22 @@
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["webrequest_test"];
+
+Components.utils.importGlobalProperties(["fetch", "XMLHttpRequest"]);
+
+this.webrequest_test = {
+  testFetch(url) {
+    return fetch(url);
+  },
+
+  testXHR(url) {
+    return new Promise(resolve => {
+      let xhr = new XMLHttpRequest();
+      xhr.open("HEAD", url);
+      xhr.onload = () => {
+        resolve();
+      };
+      xhr.send();
+    });
+  },
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/webrequest_worker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+fetch("https://example.com/example.txt");
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -540,24 +540,28 @@ HttpObserverManager = {
           method: channel.requestMethod,
           browser: browser,
           type: WebRequestCommon.typeForPolicyType(policyType),
           fromCache: getData(channel).fromCache,
         };
 
         if (loadInfo) {
           let originPrincipal = loadInfo.triggeringPrincipal || loadInfo.loadingPrincipal;
-          if (originPrincipal && originPrincipal.URI) {
+          if (originPrincipal.URI) {
             commonData.originUrl = originPrincipal.URI.spec;
           }
           Object.assign(commonData, {
             windowId: loadInfo.frameOuterWindowID ?
                         loadInfo.frameOuterWindowID : loadInfo.outerWindowID,
             parentWindowId: loadInfo.frameOuterWindowID ?
                               loadInfo.outerWindowID : loadInfo.parentOuterWindowID,
+            isSystemPrincipal: Services.scriptSecurityManager
+                                       .isSystemPrincipal(loadInfo.triggeringPrincipal) ||
+                               Services.scriptSecurityManager
+                                       .isSystemPrincipal(loadInfo.loadingPrincipal),
           });
         } else {
           Object.assign(commonData, {
             windowId: 0,
             parentWindowId: 0,
           });
         }