Bug 1525447 support incognito setting in proxy api r=zombie
☠☠ backed out by b2eade4222d6 ☠ ☠
authorShane Caraveo <scaraveo@mozilla.com>
Fri, 15 Feb 2019 18:08:44 +0000
changeset 459575 60cb2cb96e5e212b34774c04a6a5582d03e07357
parent 459574 aa6103c8ef0ff2544a48ccd46bcca982ab15b5e8
child 459576 40545233f9fec17e4dfdd766353f1910bb5c9030
push id35563
push userccoroiu@mozilla.com
push dateSat, 16 Feb 2019 09:36:04 +0000
treeherdermozilla-central@1cfd69d05aa1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszombie
bugs1525447
milestone67.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 1525447 support incognito setting in proxy api r=zombie Differential Revision: https://phabricator.services.mozilla.com/D18754
toolkit/components/extensions/ProxyScriptContext.jsm
toolkit/components/extensions/parent/ext-proxy.js
toolkit/components/extensions/test/xpcshell/test_proxy_incognito.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/toolkit/components/extensions/ProxyScriptContext.jsm
+++ b/toolkit/components/extensions/ProxyScriptContext.jsm
@@ -287,17 +287,17 @@ class ProxyChannelFilter {
    * This method (which is required by the nsIProtocolProxyService interface)
    * is called to apply proxy filter rules for the given URI and proxy object
    * (or list of proxy objects).
    *
    * @param {nsIProtocolProxyService} service A reference to the Protocol Proxy Service.
    * @param {nsIChannel} channel The channel for which these proxy settings apply.
    * @param {nsIProxyInfo} defaultProxyInfo The proxy (or list of proxies) that
    *     would be used by default for the given URI. This may be null.
-   * @param {nsIProtocolProxyChannelFilter} proxyFilter
+   * @param {nsIProxyProtocolFilterResult} proxyFilter
    */
   async applyFilter(service, channel, defaultProxyInfo, proxyFilter) {
     let proxyInfo;
     try {
       let wrapper = ChannelWrapper.get(channel);
 
       let browserData = {tabId: -1, windowId: -1};
       if (wrapper.browserElement) {
@@ -395,18 +395,18 @@ class ProxyScriptContext extends BaseCon
     this.FindProxyForURL = Cu.unwaiveXrays(this.sandbox.FindProxyForURL);
     if (typeof this.FindProxyForURL !== "function") {
       this.extension.emit("proxy-error", {
         message: "The proxy script must define FindProxyForURL as a function",
       });
       return false;
     }
 
-    ProxyService.registerFilter(
-      this /* nsIProtocolProxyFilter aFilter */,
+    ProxyService.registerChannelFilter(
+      this /* nsIProtocolProxyChannelFilter aFilter */,
       0 /* unsigned long aPosition */
     );
 
     return true;
   }
 
   get principal() {
     return this.extension.principal;
@@ -416,38 +416,45 @@ class ProxyScriptContext extends BaseCon
     return this.sandbox;
   }
 
   /**
    * This method (which is required by the nsIProtocolProxyService interface)
    * is called to apply proxy filter rules for the given URI and proxy object
    * (or list of proxy objects).
    *
-   * @param {Object} service A reference to the Protocol Proxy Service.
-   * @param {Object} uri The URI for which these proxy settings apply.
-   * @param {Object} defaultProxyInfo The proxy (or list of proxies) that
+   * @param {nsIProtocolProxyService} service A reference to the Protocol Proxy Service.
+   * @param {nsIChannel} channel The channel for which these proxy settings apply.
+   * @param {nsIProxyInfo} defaultProxyInfo The proxy (or list of proxies) that
    *     would be used by default for the given URI. This may be null.
-   * @param {Object} callback nsIProxyProtocolFilterResult to call onProxyFilterResult
+   * @param {nsIProxyProtocolFilterResult} proxyFilter to call
          on with the proxy info to apply for the given URI.
    */
-  applyFilter(service, uri, defaultProxyInfo, callback) {
+  applyFilter(service, channel, defaultProxyInfo, proxyFilter) {
+    let proxyInfo;
     try {
-      // TODO Bug 1337001 - provide path and query components to non-https URLs.
-      let ret = this.FindProxyForURL(uri.prePath, uri.host, this.contextInfo);
-      ret = ProxyInfoData.proxyInfoFromProxyData(this, ret, defaultProxyInfo);
-      callback.onProxyFilterResult(ret);
+      let wrapper = ChannelWrapper.get(channel);
+      if (this.extension.policy.privateBrowsingAllowed ||
+          wrapper.loadInfo.originAttributes.privateBrowsingId == 0) {
+        let uri = wrapper.finalURI;
+        // TODO Bug 1337001 - provide path and query components to non-https URLs.
+        let ret = this.FindProxyForURL(uri.prePath, uri.host, this.contextInfo);
+        proxyInfo = ProxyInfoData.proxyInfoFromProxyData(this, ret, defaultProxyInfo);
+      }
     } catch (e) {
       let error = this.normalizeError(e);
       this.extension.emit("proxy-error", {
         message: error.message,
         fileName: error.fileName,
         lineNumber: error.lineNumber,
         stack: error.stack,
       });
-      callback.onProxyFilterResult(defaultProxyInfo);
+    } finally {
+      // FindProxyForURL may return nothing, null, or proxyInfo.
+      proxyFilter.onProxyFilterResult(proxyInfo !== undefined ? proxyInfo : defaultProxyInfo);
     }
   }
 
   /**
    * Unloads the proxy filter and shuts down the sandbox.
    */
   unload() {
     super.unload();
--- a/toolkit/components/extensions/parent/ext-proxy.js
+++ b/toolkit/components/extensions/parent/ext-proxy.js
@@ -89,17 +89,16 @@ function registerProxyFilterEvent(contex
   let listener = (data) => {
     return fire.sync(data);
   };
 
   let filter = {...filterProps};
   if (filter.urls) {
     let perms = new MatchPatternSet([...extension.whiteListedHosts.patterns,
                                      ...extension.optionalOrigins.patterns]);
-
     filter.urls = new MatchPatternSet(filter.urls);
 
     if (!perms.overlapsAll(filter.urls)) {
       Cu.reportError("The proxy.onRequest filter doesn't overlap with host permissions.");
     }
   }
 
   let proxyFilter = new ProxyChannelFilter(context, extension, listener, filter, extraInfoSpec);
@@ -223,16 +222,20 @@ this.proxy = class extends ExtensionAPI 
           ),
           {
             set: details => {
               if (AppConstants.platform === "android") {
                 throw new ExtensionError(
                   "proxy.settings is not supported on android.");
               }
 
+              if (!extension.privateBrowsingAllowed) {
+                throw new ExtensionError("proxy.settings requires private browsing permission.");
+              }
+
               if (!Services.policies.isAllowed("changeProxySettings")) {
                 throw new ExtensionError(
                   "Proxy settings are being managed by the Policies manager.");
               }
 
               let value = details.value;
 
               // proxyType is optional and it should default to "system" when missing.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_incognito.js
@@ -0,0 +1,141 @@
+"use strict";
+
+/* eslint no-unused-vars: ["error", {"args": "none", "varsIgnorePattern": "^(FindProxyForURL)$"}] */
+
+const server = createHttpServer({hosts: ["example.com"]});
+
+server.registerPathHandler("/dummy", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  response.write("<!DOCTYPE html><html></html>");
+});
+
+
+add_task(async function test_incognito_proxy_onRequest_access() {
+  // No specific support exists in the proxy api for this test,
+  // rather it depends on functionality existing in ChannelWrapper
+  // that prevents notification of private channels if the
+  // extension does not have permission.
+  Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false);
+
+  // This extension will fail if it gets a request
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["proxy", "<all_urls>"],
+    },
+    async background() {
+      browser.proxy.onRequest.addListener(async (details) => {
+        browser.test.fail("proxy.onRequest received incognito request");
+      }, {urls: ["<all_urls>"]});
+
+      // Actual call arguments do not matter here.
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "none",
+        }}),
+        /proxy.settings requires private browsing permission/,
+        "proxy.settings requires private browsing permission.");
+
+      browser.test.sendMessage("ready");
+    },
+  });
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  // This extension will succeed if it gets a request
+  let pextension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
+    manifest: {
+      permissions: ["proxy", "<all_urls>"],
+    },
+    background() {
+      browser.proxy.onRequest.addListener(async (details) => {
+        browser.test.notifyPass("proxy.onRequest");
+      }, {urls: ["<all_urls>"]});
+    },
+  });
+  await pextension.startup();
+
+  let finished = pextension.awaitFinish("proxy.onRequest");
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {privateBrowsing: true});
+  await finished;
+
+  await extension.unload();
+  await pextension.unload();
+  await contentPage.close();
+
+  Services.prefs.clearUserPref("extensions.allowPrivateBrowsingByDefault");
+});
+
+function scriptData(script) {
+  return String(script).replace(/^.*?\{([^]*)\}$/, "$1");
+}
+
+add_task(async function test_incognito_proxy_register_access() {
+  Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false);
+
+  // This extension will fail if it gets a request
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["proxy"],
+    },
+    async background() {
+      browser.runtime.onMessage.addListener((message, sender) => {
+        if (sender.url === browser.extension.getURL("proxy.js")) {
+          browser.test.fail(message);
+        }
+      });
+      await browser.proxy.register("proxy.js");
+      browser.test.sendMessage("ready");
+    },
+    files: {
+      "proxy.js": scriptData(() => {
+        function FindProxyForURL() {
+          // Shoot a message off to the background.
+          browser.runtime.sendMessage("incognito fail");
+          return null;
+        }
+      }),
+    },
+  });
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  // This extension will succeed if it gets a request
+  let pb_extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
+    manifest: {
+      permissions: ["proxy"],
+    },
+    async background() {
+      browser.runtime.onMessage.addListener((message, sender) => {
+        if (sender.url === browser.extension.getURL("proxy.js")) {
+          browser.test.notifyPass(message);
+        }
+      });
+      await browser.proxy.register("proxy.js");
+      browser.test.sendMessage("ready");
+    },
+    files: {
+      "proxy.js": scriptData(() => {
+        function FindProxyForURL() {
+          // Shoot a message off to the background.
+          browser.runtime.sendMessage("success");
+          return null;
+        }
+      }),
+    },
+  });
+  await pb_extension.startup();
+  await pb_extension.awaitMessage("ready");
+
+  let finished = pb_extension.awaitFinish("success");
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {privateBrowsing: true});
+  await finished;
+
+  await extension.unload();
+  await pb_extension.unload();
+  await contentPage.close();
+
+  Services.prefs.clearUserPref("extensions.allowPrivateBrowsingByDefault");
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -156,13 +156,14 @@ skip-if = appname == "thunderbird"
 [test_native_manifests.js]
 subprocess = true
 skip-if = os == "android"
 [test_ext_permissions.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_ext_permissions_uninstall.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_proxy_listener.js]
+[test_proxy_incognito.js]
 [test_proxy_scripts.js]
 [test_proxy_scripts_results.js]
 [test_ext_brokenlinks.js]
 [test_ext_performance_counters.js]
 skip-if = appname == "thunderbird" || os == "android"