Bug 1318800 fix systemPrincipal checks so top level tab loads work with webrequests, r?kmag draft
authorShane Caraveo <scaraveo@mozilla.com>
Tue, 29 Nov 2016 16:00:06 -0800
changeset 445564 b381603cc812bf4b757531d65a72fa019686b718
parent 445465 8d8846f63b74eb930e48b410730ae088e9bdbee8
child 538565 e935547993b40b4e517c7a6ffff4aed9788d3b91
push id37558
push usermixedpuppy@gmail.com
push dateWed, 30 Nov 2016 00:01:24 +0000
reviewerskmag
bugs1318800
milestone53.0a1
Bug 1318800 fix systemPrincipal checks so top level tab loads work with webrequests, r?kmag MozReview-Commit-ID: D5b8Qs839bP
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_webRequest.js
toolkit/components/extensions/test/mochitest/head_webrequest.js
toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html
toolkit/modules/addons/WebRequest.jsm
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -13,16 +13,17 @@ support-files =
   file_iframe_document.sjs
   file_bypass_cache.sjs
   file_language_fr_en.html
   file_language_ja.html
   file_language_tlh.html
   file_dummy.html
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
+  ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
 
 [browser_ext_browserAction_context.js]
 [browser_ext_browserAction_disabled.js]
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_pageAction_icon_permissions.js]
 [browser_ext_browserAction_popup.js]
 [browser_ext_browserAction_popup_preload.js]
 [browser_ext_browserAction_popup_resize.js]
@@ -88,16 +89,17 @@ support-files =
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_topwindowid.js]
+[browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_windows.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_params.js]
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows_create_url.js]
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 tags = webextensions in-process-webextensions
 
-
 [browser_ext_legacy_extension_context_contentscript.js]
 [browser_ext_omnibox.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows_allowScriptsToClose.js]
 [browser_ext_windows_events.js]
 
 [include:browser-common.ini]
 [parent:browser-common.ini]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_webRequest.js
@@ -0,0 +1,95 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* globals makeExtension */
+"use strict";
+
+Services.scriptloader.loadSubScript(new URL("head_webrequest.js", gTestPath).href,
+                                    this);
+
+Cu.import("resource:///modules/HiddenFrame.jsm", this);
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function createHiddenBrowser(url) {
+  let frame = new HiddenFrame();
+  return new Promise(resolve =>
+    frame.get().then(subframe => {
+      let doc = subframe.document;
+      let browser = doc.createElementNS(XUL_NS, "browser");
+      browser.setAttribute("type", "content");
+      browser.setAttribute("disableglobalhistory", "true");
+      browser.setAttribute("src", url);
+
+      doc.documentElement.appendChild(browser);
+      resolve({frame: frame, browser: browser});
+    }));
+}
+
+let extension;
+let dummy = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/file_dummy.html";
+
+add_task(function* setup() {
+  // SelfSupport has a tendency to fire when running this test alone, without
+  // a good way to turn it off we just set the url to ""
+  yield SpecialPowers.pushPrefEnv({
+    set: [["browser.selfsupport.url", ""]],
+  });
+  extension = makeExtension();
+  yield extension.startup();
+});
+
+add_task(function* test_newWindow() {
+  let expect = {
+    "file_dummy.html": {
+      type: "main_frame",
+    },
+  };
+  // NOTE: When running solo, favicon will be loaded at some point during
+  // the tests in this file, so all tests ignore it.  When running with
+  // other tests in this directory, favicon gets loaded at some point before
+  // we run, and we never see the request, thus it cannot be handled as part
+  // of expect above.
+  extension.sendMessage("set-expected", {expect, ignore: ["favicon.ico"]});
+  yield extension.awaitMessage("continue");
+
+  let openedWindow = yield BrowserTestUtils.openNewBrowserWindow();
+  yield BrowserTestUtils.openNewForegroundTab(openedWindow.gBrowser, dummy + "?newWindow");
+
+  yield extension.awaitMessage("done");
+  yield BrowserTestUtils.closeWindow(openedWindow);
+});
+
+add_task(function* test_newTab() {
+  // again, in this window
+  let expect = {
+    "file_dummy.html": {
+      type: "main_frame",
+    },
+  };
+  extension.sendMessage("set-expected", {expect, ignore: ["favicon.ico"]});
+  yield extension.awaitMessage("continue");
+  let tab = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, dummy + "?newTab");
+
+  yield extension.awaitMessage("done");
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_subframe() {
+  let expect = {
+    "file_dummy.html": {
+      type: "main_frame",
+    },
+  };
+  // test a content subframe attached to hidden window
+  extension.sendMessage("set-expected", {expect, ignore: ["favicon.ico"]});
+  yield extension.awaitMessage("continue");
+  let frameInfo = yield createHiddenBrowser(dummy + "?subframe");
+  yield extension.awaitMessage("done");
+  // cleanup
+  frameInfo.browser.remove();
+  frameInfo.frame.destroy();
+});
+
+add_task(function* teardown() {
+  yield extension.unload();
+});
+
--- a/toolkit/components/extensions/test/mochitest/head_webrequest.js
+++ b/toolkit/components/extensions/test/mochitest/head_webrequest.js
@@ -8,24 +8,26 @@ let commonEvents = {
   "onHeadersReceived":   [{urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]],
   "onResponseStarted":   [{urls: ["<all_urls>"]}],
   "onCompleted":         [{urls: ["<all_urls>"]}, ["responseHeaders"]],
   "onErrorOccurred":     [{urls: ["<all_urls>"]}],
 };
 
 function background(events) {
   let expect;
+  let ignore;
   let defaultOrigin;
 
   browser.test.onMessage.addListener((msg, expected) => {
     if (msg !== "set-expected") {
       return;
     }
     expect = expected.expect;
     defaultOrigin = expected.origin;
+    ignore = expected.ignore;
     let promises = [];
     // Initialize some stuff we'll need in the tests.
     for (let entry of Object.values(expect)) {
       // a place for the test infrastructure to store some state.
       entry.test = {};
       // Each entry in expected gets a Promise that will be resolved in the
       // last event for that entry.  This will either be onCompleted, or the
       // last entry if an events list was provided.
@@ -51,16 +53,19 @@ function background(events) {
     let url = new URL(details.url);
     let filename;
     if (url.protocol == "data:") {
       // pathname is everything after protocol.
       filename = url.pathname;
     } else {
       filename = url.pathname.split("/").pop();
     }
+    if (ignore && ignore.includes(filename)) {
+      return;
+    }
     let expected = expect[filename];
     if (!expected) {
       browser.test.fail(`unexpected request ${filename}`);
       return;
     }
     // Save filename for redirect verification.
     expected.test.filename = filename;
     return expected;
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html
@@ -9,16 +9,22 @@
   <script type="text/javascript" src="head_webrequest.js"></script>
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 <script>
 "use strict";
 
 let extension;
 add_task(function* setup() {
+  // SelfSupport has a tendency to fire when running this test alone, without
+  // a good way to turn it off we just set the url to ""
+  yield SpecialPowers.pushPrefEnv({
+    set: [["browser.selfsupport.url", ""]],
+  });
+
   // 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);
 
   extension = makeExtension();
   yield extension.startup();
 });
@@ -229,16 +235,55 @@ add_task(function* test_webRequest_tabId
   };
   extension.sendMessage("set-expected", {expect, origin: location.href});
   yield extension.awaitMessage("continue");
   let a = addLink("file_WebRequest_page3.html?trigger=a");
   a.click();
   yield extension.awaitMessage("done");
 });
 
+add_task(function* test_webRequest_tabId_browser() {
+  async function background(url) {
+    let tabId;
+    browser.test.onMessage.addListener(async (msg, expected) => {
+      await browser.tabs.remove(tabId);
+      browser.test.sendMessage("done");
+    });
+
+    let tab = await browser.tabs.create({url});
+    tabId = tab.id;
+  }
+
+  let tabExt = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: [
+        "tabs",
+      ],
+    },
+    background: `(${background})('${SimpleTest.getTestFileURL("file_sample.html")}?nocache=${Math.random()}')`,
+  });
+
+  let expect = {
+    "file_sample.html": {
+      type: "main_frame",
+    },
+  };
+  // expecting origin == undefined
+  extension.sendMessage("set-expected", {expect});
+  yield extension.awaitMessage("continue");
+
+  // open a tab from a system principal
+  yield tabExt.startup();
+
+  yield extension.awaitMessage("done");
+  tabExt.sendMessage("done");
+  yield tabExt.awaitMessage("done");
+  yield tabExt.unload();
+});
+
 add_task(function* test_webRequest_frames() {
   let expect = {
     "text/plain,webRequestTest": {
       type: "sub_frame",
       events: ["onBeforeRequest", "onCompleted"],
     },
     "text/plain,webRequestTest_bad": {
       type: "sub_frame",
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -619,20 +619,26 @@ HttpObserverManager = {
     };
 
     if (loadInfo) {
       let originPrincipal = loadInfo.triggeringPrincipal;
       if (originPrincipal.URI) {
         data.originUrl = originPrincipal.URI.spec;
       }
 
+      // If there is no loadingPrincipal, check that the request is not going to
+      // inherit a system principal.  triggeringPrincipal is the context that
+      // initiated the load, but is not necessarily the principal that the
+      // request results in, only rely on that if no other principal is available.
       let {isSystemPrincipal} = Services.scriptSecurityManager;
-
-      data.isSystemPrincipal = (isSystemPrincipal(loadInfo.triggeringPrincipal) ||
-                                isSystemPrincipal(loadInfo.loadingPrincipal));
+      let isTopLevel = !loadInfo.loadingPrincipal && !!data.browser;
+      data.isSystemPrincipal = !isTopLevel &&
+                               isSystemPrincipal(loadInfo.loadingPrincipal ||
+                                                 loadInfo.principalToInherit ||
+                                                 loadInfo.triggeringPrincipal);
 
       if (loadInfo.frameOuterWindowID) {
         Object.assign(data, {
           windowId: loadInfo.frameOuterWindowID,
           parentWindowId: loadInfo.outerWindowID,
         });
       } else {
         Object.assign(data, {