Bug 1115495 - Part 2: PAC generator for browsing and system wide proxy. r=mcmanus, r=echen
authorShian-Yow Wu <swu@mozilla.com>
Thu, 26 Mar 2015 15:11:08 +0800
changeset 266109 748b96aa103bf1e8d785504eda0e43bc348f6880
parent 266108 c6a9943ddf801054b001c1ece5d20b9087a37528
child 266110 f3cc3b9043084be12923819f1136410549d3c362
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, echen
bugs1115495
milestone39.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 1115495 - Part 2: PAC generator for browsing and system wide proxy. r=mcmanus, r=echen
b2g/app/b2g.js
b2g/chrome/content/settings.js
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
dom/system/gonk/NetworkService.js
dom/system/gonk/tests/marionette/test_data_connection_proxy.js
mobile/android/installer/package-manifest.in
netwerk/base/PACGenerator.js
netwerk/base/PACGenerator.manifest
netwerk/base/moz.build
netwerk/base/nsIPACGenerator.idl
netwerk/test/unit/test_pac_generator.js
netwerk/test/unit/xpcshell.ini
testing/profiles/prefs_general.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -991,16 +991,23 @@ pref("gfx.gralloc.fence-with-readpixels"
 // The url of the page used to display network error details.
 pref("b2g.neterror.url", "app://system.gaiamobile.org/net_error.html");
 
 // The origin used for the shared themes uri space.
 pref("b2g.theme.origin", "app://theme.gaiamobile.org");
 pref("dom.mozApps.themable", true);
 pref("dom.mozApps.selected_theme", "default_theme.gaiamobile.org");
 
+// Enable PAC generator for B2G.
+pref("network.proxy.pac_generator", true);
+
+// List of app origins to apply browsing traffic proxy setting, separated by
+// comma.  Specify '*' in the list to apply to all apps.
+pref("network.proxy.browsing.app_origins", "app://system.gaiamobile.org");
+
 // Enable Web Speech synthesis API
 pref("media.webspeech.synth.enabled", true);
 
 // Downloads API
 pref("dom.mozDownloads.enabled", true);
 pref("dom.downloads.max_retention_days", 7);
 
 // External Helper Application Handling
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -25,16 +25,20 @@ XPCOMUtils.defineLazyGetter(this, "libcu
   return libcutils;
 });
 #endif
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator",
+                                   "@mozilla.org/pac-generator;1",
+                                   "nsIPACGenerator");
+
 // Once Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
 // is resolved this helper could be removed.
 var SettingsListener = {
   _callbacks: {},
 
   init: function sl_init() {
     if ('mozSettings' in navigator && navigator.mozSettings) {
       navigator.mozSettings.onsettingchange = this.onchange.bind(this);
@@ -477,16 +481,50 @@ SettingsListener.observe("theme.selected
   if (currentTheme != newTheme) {
     debug("New theme selected " + value);
     Services.prefs.setCharPref('dom.mozApps.selected_theme', newTheme);
     Services.prefs.savePrefFile(null);
     Services.obs.notifyObservers(null, 'app-theme-changed', newTheme);
   }
 });
 
+// =================== Proxy server ======================
+(function setupBrowsingProxySettings() {
+  function setPAC() {
+    let usePAC;
+    try {
+      usePAC = Services.prefs.getBoolPref('network.proxy.pac_generator');
+    } catch (ex) {}
+
+    if (usePAC) {
+      Services.prefs.setCharPref('network.proxy.autoconfig_url',
+                                 gPACGenerator.generate());
+      Services.prefs.setIntPref('network.proxy.type',
+                                Ci.nsIProtocolProxyService.PROXYCONFIG_PAC);
+    }
+  }
+
+  SettingsListener.observe('browser.proxy.enabled', false, function(value) {
+    Services.prefs.setBoolPref('network.proxy.browsing.enabled', value);
+    setPAC();
+  });
+
+  SettingsListener.observe('browser.proxy.host', '', function(value) {
+    Services.prefs.setCharPref('network.proxy.browsing.host', value);
+    setPAC();
+  });
+
+  SettingsListener.observe('browser.proxy.port', 0, function(value) {
+    Services.prefs.setIntPref('network.proxy.browsing.port', value);
+    setPAC();
+  });
+
+  setPAC();
+})();
+
 // =================== Various simple mapping  ======================
 let settingsToObserve = {
   'accessibility.screenreader_quicknav_modes': {
     prefName: 'accessibility.accessfu.quicknav_modes',
     resetToPref: true,
     defaultValue: ''
   },
   'accessibility.screenreader_quicknav_index': {
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -668,16 +668,19 @@
 @RESPATH@/components/EngineeringModeAPI.js
 @RESPATH@/components/EngineeringModeService.js
 
 #ifdef MOZ_DEBUG
 @RESPATH@/components/TestInterfaceJS.js
 @RESPATH@/components/TestInterfaceJS.manifest
 #endif
 
+@RESPATH@/components/PACGenerator.js
+@RESPATH@/components/PACGenerator.manifest
+
 ; Modules
 @RESPATH@/modules/*
 
 ; Safe Browsing
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
 @RESPATH@/components/nsUrlClassifierListManager.js
 @RESPATH@/components/nsUrlClassifierLib.js
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -613,16 +613,19 @@
 @RESPATH@/components/MozKeyboard.js
 @RESPATH@/components/InputMethod.manifest
 
 #ifdef MOZ_DEBUG
 @RESPATH@/components/TestInterfaceJS.js
 @RESPATH@/components/TestInterfaceJS.manifest
 #endif
 
+@RESPATH@/components/PACGenerator.js
+@RESPATH@/components/PACGenerator.manifest
+
 ; Modules
 @RESPATH@/browser/modules/*
 @RESPATH@/modules/*
 
 ; Safe Browsing
 #ifdef MOZ_URL_CLASSIFIER
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -18,31 +18,36 @@ const NETWORKSERVICE_CID = Components.ID
 const TOPIC_PREF_CHANGED             = "nsPref:changed";
 const TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
 const PREF_NETWORK_DEBUG_ENABLED     = "network.debugging.enabled";
 
 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker",
                                    "@mozilla.org/network/worker;1",
                                    "nsINetworkWorker");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator",
+                                   "@mozilla.org/pac-generator;1",
+                                   "nsIPACGenerator");
+
 // 1xx - Requested action is proceeding
 const NETD_COMMAND_PROCEEDING   = 100;
 // 2xx - Requested action has been successfully completed
 const NETD_COMMAND_OKAY         = 200;
 // 4xx - The command is accepted but the requested action didn't
 // take place.
 const NETD_COMMAND_FAIL         = 400;
 // 5xx - The command syntax or parameters error
 const NETD_COMMAND_ERROR        = 500;
 // 6xx - Unsolicited broadcasts
 const NETD_COMMAND_UNSOLICITED  = 600;
 
 const WIFI_CTRL_INTERFACE = "wl0.1";
 
-const MANUAL_PROXY_CONFIGURATION = 1;
+const PROXY_TYPE_MANUAL = Ci.nsIProtocolProxyService.PROXYCONFIG_MANUAL;
+const PROXY_TYPE_PAC = Ci.nsIProtocolProxyService.PROXYCONFIG_PAC;
 
 let debug;
 function updateDebug() {
   let debugPref = false; // set default value here.
   try {
     debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
   } catch (e) {}
 
@@ -518,40 +523,64 @@ NetworkService.prototype = {
         // Sets direct connection to internet.
         this.clearNetworkProxy();
 
         debug("No proxy support for " + network.name + " network interface.");
         return;
       }
 
       debug("Going to set proxy settings for " + network.name + " network interface.");
-      // Sets manual proxy configuration.
-      Services.prefs.setIntPref("network.proxy.type", MANUAL_PROXY_CONFIGURATION);
+
       // Do not use this proxy server for all protocols.
       Services.prefs.setBoolPref("network.proxy.share_proxy_settings", false);
       Services.prefs.setCharPref("network.proxy.http", network.httpProxyHost);
       Services.prefs.setCharPref("network.proxy.ssl", network.httpProxyHost);
       let port = network.httpProxyPort === 0 ? 8080 : network.httpProxyPort;
       Services.prefs.setIntPref("network.proxy.http_port", port);
       Services.prefs.setIntPref("network.proxy.ssl_port", port);
+
+      let usePAC;
+      try {
+        usePAC = Services.prefs.getBoolPref("network.proxy.pac_generator");
+      } catch (ex) {}
+
+      if (usePAC) {
+        Services.prefs.setCharPref("network.proxy.autoconfig_url",
+                                   gPACGenerator.generate());
+        Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+      } else {
+        Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_MANUAL);
+      }
     } catch(ex) {
         debug("Exception " + ex + ". Unable to set proxy setting for " +
                          network.name + " network interface.");
     }
   },
 
   clearNetworkProxy: function() {
     debug("Going to clear all network proxy.");
 
-    Services.prefs.clearUserPref("network.proxy.type");
     Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
     Services.prefs.clearUserPref("network.proxy.http");
     Services.prefs.clearUserPref("network.proxy.http_port");
     Services.prefs.clearUserPref("network.proxy.ssl");
     Services.prefs.clearUserPref("network.proxy.ssl_port");
+
+    let usePAC;
+    try {
+      usePAC = Services.prefs.getBoolPref("network.proxy.pac_generator");
+    } catch (ex) {}
+
+    if (usePAC) {
+      Services.prefs.setCharPref("network.proxy.autoconfig_url",
+                                 gPACGenerator.generate());
+      Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+    } else {
+      Services.prefs.clearUserPref("network.proxy.type");
+    }
   },
 
   // Enable/Disable DHCP server.
   setDhcpServer: function(enabled, config, callback) {
     if (null === config) {
       config = {};
     }
 
--- a/dom/system/gonk/tests/marionette/test_data_connection_proxy.js
+++ b/dom/system/gonk/tests/marionette/test_data_connection_proxy.js
@@ -1,17 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = "head.js";
 
 const HTTP_PROXY = "10.0.2.200";
 const HTTP_PROXY_PORT = "8080";
-const MANUAL_PROXY_CONFIGURATION = 1;
+const PROXY_TYPE_MANUAL = Ci.nsIProtocolProxyService.PROXYCONFIG_MANUAL;
+const PROXY_TYPE_PAC = Ci.nsIProtocolProxyService.PROXYCONFIG_PAC;
 
 // Test initial State
 function verifyInitialState() {
   log("= verifyInitialState =");
 
   // Data should be off before starting any test.
   return getSettings(SETTINGS_KEY_DATA_ENABLED)
     .then(value => {
@@ -33,30 +34,34 @@ function setTestApn() {
 }
 
 function waitForHttpProxyVerified(aShouldBeSet) {
   let TIME_OUT_VALUE = 20000;
 
   return new Promise(function(aResolve, aReject) {
     try {
       waitFor(aResolve, () => {
+        let usePAC = SpecialPowers.getBoolPref("network.proxy.pac_generator");
         let proxyType = SpecialPowers.getIntPref("network.proxy.type");
         let httpProxy = SpecialPowers.getCharPref("network.proxy.http");
         let sslProxy = SpecialPowers.getCharPref("network.proxy.ssl");
         let httpProxyPort = SpecialPowers.getIntPref("network.proxy.http_port");
         let sslProxyPort = SpecialPowers.getIntPref("network.proxy.ssl_port");
 
         if ((aShouldBeSet &&
-             proxyType == MANUAL_PROXY_CONFIGURATION &&
+             (usePAC ? proxyType == PROXY_TYPE_PAC :
+                       proxyType == PROXY_TYPE_MANUAL) &&
              httpProxy == HTTP_PROXY &&
              sslProxy == HTTP_PROXY &&
              httpProxyPort == HTTP_PROXY_PORT &&
              sslProxyPort == HTTP_PROXY_PORT) ||
-            (!aShouldBeSet && proxyType != MANUAL_PROXY_CONFIGURATION &&
-             !httpProxy && !sslProxy && !httpProxyPort && !sslProxyPort)) {
+             (!aShouldBeSet &&
+              (usePAC ? proxyType == PROXY_TYPE_PAC :
+                        proxyType != PROXY_TYPE_MANUAL) &&
+              !httpProxy && !sslProxy && !httpProxyPort && !sslProxyPort)) {
           return true;
         }
 
         return false;
       }, TIME_OUT_VALUE);
     } catch(aError) {
       // Timed out.
       aReject(aError);
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -455,16 +455,19 @@
 @BINPATH@/components/nsAsyncShutdown.js
 
 @BINPATH@/components/Downloads.manifest
 @BINPATH@/components/DownloadLegacy.js
 
 @BINPATH@/components/PresentationDeviceInfoManager.manifest
 @BINPATH@/components/PresentationDeviceInfoManager.js
 
+@BINPATH@/components/PACGenerator.js
+@BINPATH@/components/PACGenerator.manifest
+
 ; Modules
 @BINPATH@/modules/*
 
 #ifdef MOZ_SAFE_BROWSING
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
new file mode 100644
--- /dev/null
+++ b/netwerk/base/PACGenerator.js
@@ -0,0 +1,165 @@
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+let DEBUG = false;
+
+if (DEBUG) {
+  debug = function (s) { dump("-*- PACGenerator: " + s + "\n"); };
+}
+else {
+  debug = function (s) {};
+}
+
+const PACGENERATOR_CONTRACTID = "@mozilla.org/pac-generator;1";
+const PACGENERATOR_CID = Components.ID("{788507c4-eb5f-4de8-b19b-e0d531974e8a}");
+
+//
+// RFC 2396 section 3.2.2:
+//
+// host = hostname | IPv4address
+// hostname = *( domainlabel "." ) toplabel [ "." ]
+// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+//
+const HOST_REGEX =
+  new RegExp("^(?:" +
+               // *( domainlabel "." )
+               "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
+               // toplabel
+               "[a-z](?:[a-z0-9-]*[a-z0-9])?" +
+             "|" +
+               // IPv4 address
+               "\\d+\\.\\d+\\.\\d+\\.\\d+" +
+             ")$",
+             "i");
+
+function PACGenerator() {
+  debug("Starting PAC Generator service.");
+}
+
+PACGenerator.prototype = {
+
+  classID : PACGENERATOR_CID,
+
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIPACGenerator]),
+
+  classInfo : XPCOMUtils.generateCI({classID: PACGENERATOR_CID,
+                                     contractID: PACGENERATOR_CONTRACTID,
+                                     classDescription: "PACGenerator",
+                                     interfaces: [Ci.nsIPACGenerator]}),
+
+  /**
+   * Validate the the host.
+   */
+  isValidHost: function isValidHost(host) {
+    if (!HOST_REGEX.test(host)) {
+      debug("Unexpected host: '" + host + "'");
+      return false;
+    }
+    return true;
+  },
+
+  /**
+   * Returns a PAC string based on proxy settings in the preference.
+   * Only effective when the network.proxy.pac_generator preference is true.
+   */
+  generate: function generate() {
+    let enabled, host, port, proxy;
+
+    try {
+      enabled = Services.prefs.getBoolPref("network.proxy.pac_generator");
+    } catch (ex) {}
+    if (!enabled) {
+      debug("PAC Generator disabled.");
+      return "";
+    }
+
+    let pac = "data:text/plain,function FindProxyForURL(url, host) { ";
+
+    // Direct connection for localhost.
+    pac += "if (shExpMatch(host, 'localhost') || " +
+           "shExpMatch(host, '127.0.0.1')) {" +
+           " return 'DIRECT'; } ";
+
+    // Rules for browsing proxy.
+    try {
+      enabled = Services.prefs.getBoolPref("network.proxy.browsing.enabled");
+      host = Services.prefs.getCharPref("network.proxy.browsing.host");
+      port = Services.prefs.getIntPref("network.proxy.browsing.port");
+    } catch (ex) {}
+
+    if (enabled && host && this.isValidHost(host)) {
+      proxy = host + ":" + ((port && port !== 0) ? port : 8080);
+      let appOrigins;
+      try {
+        appOrigins = Services.prefs.getCharPref("network.proxy.browsing.app_origins");
+      } catch (ex) {}
+
+      pac += "var origins ='" + appOrigins +
+             "'.split(/[ ,]+/).filter(Boolean); " +
+             "if ((origins.indexOf('*') > -1 || origins.indexOf(myAppOrigin()) > -1)" +
+             " && isInBrowser()) { return 'PROXY " + proxy + "'; } ";
+    }
+
+    // Rules for system proxy.
+    let share;
+    try {
+      share = Services.prefs.getBoolPref("network.proxy.share_proxy_settings");
+    } catch (ex) {}
+
+    if (share) {
+      // Add rules for all protocols.
+      try {
+        host = Services.prefs.getCharPref("network.proxy.http");
+        port = Services.prefs.getIntPref("network.proxy.http_port");
+      } catch (ex) {}
+
+      if (host && this.isValidHost(host)) {
+        proxy = host + ":" + ((port && port !== 0) ? port : 8080);
+        pac += "return 'PROXY " + proxy + "'; "
+      } else {
+        pac += "return 'DIRECT'; ";
+      }
+    } else {
+      // Add rules for specific protocols.
+      const proxyTypes = [
+        {"scheme": "http:", "pref": "http"},
+        {"scheme": "https:", "pref": "ssl"},
+        {"scheme": "ftp:", "pref": "ftp"}
+      ];
+      for (let i = 0; i < proxyTypes.length; i++) {
+       try {
+          host = Services.prefs.getCharPref("network.proxy." +
+                                            proxyTypes[i]["pref"]);
+          port = Services.prefs.getIntPref("network.proxy." +
+                                            proxyTypes[i]["pref"] + "_port");
+        } catch (ex) {}
+
+        if (host && this.isValidHost(host)) {
+          proxy = host + ":" + (port === 0 ? 8080 : port);
+          pac += "if (url.substring(0, " + (proxyTypes[i]["scheme"]).length +
+                 ") == '" + proxyTypes[i]["scheme"] + "') { return 'PROXY " +
+                 proxy + "'; } ";
+         }
+      }
+      pac += "return 'DIRECT'; ";
+    }
+
+    pac += "}";
+
+    debug("PAC: " + pac);
+
+    return pac;
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PACGenerator]);
new file mode 100644
--- /dev/null
+++ b/netwerk/base/PACGenerator.manifest
@@ -0,0 +1,2 @@
+component {788507c4-eb5f-4de8-b19b-e0d531974e8a} PACGenerator.js
+contract @mozilla.org/pac-generator;1 {788507c4-eb5f-4de8-b19b-e0d531974e8a}
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -61,16 +61,17 @@ XPIDL_SOURCES += [
     'nsINetUtil.idl',
     'nsINetworkInterceptController.idl',
     'nsINetworkLinkService.idl',
     'nsINetworkPredictor.idl',
     'nsINetworkPredictorVerifier.idl',
     'nsINetworkProperties.idl',
     'nsINSSErrorsService.idl',
     'nsINullChannel.idl',
+    'nsIPACGenerator.idl',
     'nsIParentChannel.idl',
     'nsIParentRedirectingChannel.idl',
     'nsIPermission.idl',
     'nsIPermissionManager.idl',
     'nsIPrivateBrowsingChannel.idl',
     'nsIProgressEventSink.idl',
     'nsIPrompt.idl',
     'nsIProtocolHandler.idl',
@@ -256,16 +257,21 @@ else:
         'nsURLHelperUnix.cpp',
     ]
 
 if CONFIG['MOZ_ENABLE_QTNETWORK']:
     SOURCES += [
         'nsAutodialQt.cpp',
     ]
 
+EXTRA_COMPONENTS += [
+    'PACGenerator.js',
+    'PACGenerator.manifest'
+]
+
 EXTRA_JS_MODULES += [
     'NetUtil.jsm',
 ]
 
 FAIL_ON_WARNINGS = True
 
 MSVC_ENABLE_PGO = True
 
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIPACGenerator.idl
@@ -0,0 +1,15 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(4b3eeeea-1108-4aa0-8f26-e3ebdeb0454c)]
+interface nsIPACGenerator : nsISupports
+{
+  /**
+   * Returns a PAC string based on proxy settings in the preference.
+   * Only effective when the network.proxy.pac_generator preference is true.
+   */
+  DOMString generate();
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_pac_generator.js
@@ -0,0 +1,238 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let ios = Cc["@mozilla.org/network/io-service;1"]
+             .getService(Components.interfaces.nsIIOService);
+
+let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+let prefs = Cc["@mozilla.org/preferences-service;1"]
+               .getService(Components.interfaces.nsIPrefBranch);
+
+let pgen = Cc["@mozilla.org/pac-generator;1"]
+              .getService(Components.interfaces.nsIPACGenerator);
+
+const TARGET_HOST ="www.mozilla.org";
+const HTTP_HOST = "httpHost";
+const HTTP_PORT = 1111;
+const HTTPS_HOST = "httpsHost";
+const HTTPS_PORT = 2222;
+const FTP_HOST= "ftpHost";
+const FTP_PORT = 3333;
+const MY_APP_ID = 10;
+const MY_APP_ORIGIN = "apps://browser.gaiamobile.com";
+const APP_ORIGINS_LIST = "apps://test1.com, apps://browser.gaiamobile.com";
+const BROWSING_HOST = "browsingHost";
+const BROWSING_PORT = 4444;
+const PROXY_TYPE_PAC = Ci.nsIProtocolProxyService.PROXYCONFIG_PAC;
+
+const proxyTypes = {
+  "http": {"pref": "http", "host": HTTP_HOST, "port": HTTP_PORT},
+  "https": {"pref": "ssl", "host": HTTPS_HOST, "port": HTTPS_PORT},
+  "ftp": {"pref": "ftp", "host": FTP_HOST, "port": FTP_PORT}
+};
+
+function default_proxy_settings() {
+  prefs.setBoolPref("network.proxy.pac_generator", true);
+  prefs.setIntPref("network.proxy.type", 0);
+  prefs.setCharPref("network.proxy.autoconfig_url", "");
+  for (let i in proxyTypes) {
+    let p = proxyTypes[i];
+    prefs.setCharPref("network.proxy." + p["pref"], p["host"]);
+    prefs.setIntPref("network.proxy." + p["pref"] + "_port", p["port"]);
+  }
+}
+
+function TestResolveCallback(type, host, callback) {
+  this.type = type;
+  this.host = host;
+  this.callback = callback;
+}
+TestResolveCallback.prototype = {
+  QueryInterface:
+  function TestResolveCallback_QueryInterface(iid) {
+    if (iid.equals(Components.interfaces.nsIProtocolProxyCallback) ||
+        iid.equals(Components.interfaces.nsISupports))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+
+  onProxyAvailable:
+  function TestResolveCallback_onProxyAvailable(req, channel, pi, status) {
+    if (this.type) {
+      // Check for localhost.
+      if (this.host == "localhost" || this.host == "127.0.0.1") {
+          do_check_eq(pi, null);
+          this.callback();
+          return;
+      }
+
+      // Check for browsing proxy.
+      let browsingEnabled;
+      try {
+        browsingEnabled = prefs.getBoolPref("network.proxy.browsing.enabled");
+      } catch (ex) {}
+
+      if (browsingEnabled) {
+        let proxyHost, proxyPort;
+        try {
+          proxyHost = prefs.getCharPref("network.proxy.browsing.host");
+          proxyPort = prefs.getIntPref("network.proxy.browsing.port");
+        } catch (ex) {}
+
+        if (proxyHost) {
+          do_check_eq(pi.host, proxyHost);
+          do_check_eq(pi.port, proxyPort);
+          this.callback();
+          return;
+        }
+      }
+
+      // Check for system proxy.
+      let share;
+      try {
+        share = prefs.getBoolPref("network.proxy.share_proxy_settings");
+      } catch (ex) {}
+
+      let p = share ? proxyTypes["http"] : proxyTypes[this.type];
+      if (p) {
+        let proxyHost, proxyPort;
+        try {
+          proxyHost = prefs.getCharPref("network.proxy." + p["pref"]);
+          proxyPort = prefs.getIntPref("network.proxy." + p["pref"] + "_port");
+        } catch (ex) {}
+
+        if (proxyHost) {
+          // Connection through proxy.
+          do_check_neq(pi, null);
+          do_check_eq(pi.host, proxyHost);
+          do_check_eq(pi.port, proxyPort);
+        } else {
+          // Direct connection.
+          do_check_eq(pi, null);
+        }
+      }
+    }
+
+    this.callback();
+  }
+};
+
+function test_resolve_type(type, host, callback) {
+  // We have to setup a profile, otherwise indexed db used by webapps
+  // will throw random exception when trying to get profile folder.
+  do_get_profile();
+
+  // We also need a valid nsIXulAppInfo service as Webapps.jsm is querying it.
+  Cu.import("resource://testing-common/AppInfo.jsm");
+  updateAppInfo();
+
+  // Mock getAppByLocalId() to return testing app origin.
+  Cu.import("resource://gre/modules/AppsUtils.jsm");
+  AppsUtils.getAppByLocalId = function(aAppId) {
+    let app = { origin: MY_APP_ORIGIN};
+    return app;
+  };
+
+  let channel = ios.newChannel2(type + "://" + host + "/",
+                                null,
+                                null,
+                                null,      // aLoadingNode
+                                Services.scriptSecurityManager.getSystemPrincipal(),
+                                null,      // aTriggeringPrincipal
+                                Ci.nsILoadInfo.SEC_NORMAL,
+                                Ci.nsIContentPolicy.TYPE_OTHER);
+  channel.notificationCallbacks =
+    AppsUtils.createLoadContext(MY_APP_ID, true);
+
+  let req = pps.asyncResolve(channel, 0, new TestResolveCallback(type, host, callback));
+}
+
+function test_resolve(host, callback) {
+  test_resolve_type("http", host, function() {
+    test_resolve_type("https", host, function() {
+      test_resolve_type("ftp", host, run_next_test);
+    });
+  });
+}
+
+add_test(function test_localhost() {
+  default_proxy_settings();
+  prefs.setBoolPref("network.proxy.share_proxy_settings", true);
+  Services.prefs.setCharPref("network.proxy.autoconfig_url", pgen.generate());
+  Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+  test_resolve("localhost", run_next_test);
+});
+
+add_test(function test_share_on_proxy() {
+  default_proxy_settings();
+  prefs.setBoolPref("network.proxy.share_proxy_settings", true);
+  Services.prefs.setCharPref("network.proxy.autoconfig_url", pgen.generate());
+  Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+  test_resolve(TARGET_HOST, run_next_test);
+});
+
+add_test(function test_share_on_direct() {
+  default_proxy_settings();
+  prefs.setBoolPref("network.proxy.share_proxy_settings", true);
+  prefs.setCharPref("network.proxy.http", "");
+  prefs.setCharPref("network.proxy.ssl", "");
+  prefs.setCharPref("network.proxy.ftp", "");
+  Services.prefs.setCharPref("network.proxy.autoconfig_url", pgen.generate());
+  Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+  test_resolve(TARGET_HOST, run_next_test);
+});
+
+add_test(function test_share_off_proxy() {
+  default_proxy_settings();
+  prefs.setBoolPref("network.proxy.share_proxy_settings", false);
+  Services.prefs.setCharPref("network.proxy.autoconfig_url", pgen.generate());
+  Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+  test_resolve(TARGET_HOST, run_next_test);
+});
+
+add_test(function test_share_off_direct() {
+  default_proxy_settings();
+  prefs.setBoolPref("network.proxy.share_proxy_settings", false);
+  prefs.setCharPref("network.proxy.http", "");
+  prefs.setCharPref("network.proxy.ssl", "");
+  prefs.setCharPref("network.proxy.ftp", "");
+  Services.prefs.setCharPref("network.proxy.autoconfig_url", pgen.generate());
+  Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+  test_resolve(TARGET_HOST, run_next_test);
+});
+
+add_test(function test_browsing_proxy() {
+  default_proxy_settings();
+  prefs.setBoolPref("network.proxy.browsing.enabled", true);
+  prefs.setCharPref("network.proxy.browsing.host", BROWSING_HOST);
+  prefs.setIntPref("network.proxy.browsing.port", BROWSING_PORT);
+  prefs.setCharPref("network.proxy.browsing.app_origins", APP_ORIGINS_LIST);
+  Services.prefs.setCharPref("network.proxy.autoconfig_url", pgen.generate());
+  Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+  test_resolve(TARGET_HOST, run_next_test);
+});
+
+function run_test(){
+  do_register_cleanup(() => {
+    prefs.clearUserPref("network.proxy.pac_generator");
+    prefs.clearUserPref("network.proxy.type");
+    prefs.clearUserPref("network.proxy.autoconfig_url");
+    prefs.clearUserPref("network.proxy.share_proxy_settings");
+    prefs.clearUserPref("network.proxy.http");
+    prefs.clearUserPref("network.proxy.http_port");
+    prefs.clearUserPref("network.proxy.ssl");
+    prefs.clearUserPref("network.proxy.ssl_port");
+    prefs.clearUserPref("network.proxy.ftp");
+    prefs.clearUserPref("network.proxy.ftp_port");
+    prefs.clearUserPref("network.proxy.browsing.enabled");
+    prefs.clearUserPref("network.proxy.browsing.host");
+    prefs.clearUserPref("network.proxy.browsing.port");
+    prefs.clearUserPref("network.proxy.browsing.app_origins");
+  });
+  run_next_test();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -214,16 +214,17 @@ skip-if = bits != 32
 [test_multipart_streamconv.js]
 [test_multipart_streamconv_missing_lead_boundary.js]
 [test_nestedabout_serialize.js]
 [test_net_addr.js]
 # Bug 732363: test fails on windows for unknown reasons.
 skip-if = os == "win"
 [test_nojsredir.js]
 [test_offline_status.js]
+[test_pac_generator.js]
 [test_parse_content_type.js]
 [test_permmgr.js]
 [test_plaintext_sniff.js]
 [test_post.js]
 [test_private_necko_channel.js]
 [test_private_cookie_changed.js]
 [test_progress.js]
 [test_protocolproxyservice.js]
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -298,8 +298,12 @@ user_pref("media.decoder.heuristic.dorma
 #endif
 
 // Don't prompt about e10s
 user_pref("browser.displayedE10SPrompt.1", 5);
 // Don't use auto-enabled e10s
 user_pref("browser.tabs.remote.autostart.1", false);
 // Don't forceably kill content processes after a timeout
 user_pref("dom.ipc.tabs.shutdownTimeoutSecs", 0);
+
+// Don't let PAC generator to set PAC, as mochitest framework has its own PAC
+// rules during testing.
+user_pref("network.proxy.pac_generator", false);