merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 24 Sep 2015 12:01:40 +0200
changeset 264082 bf2bc1aa78c0b72d9b6b13f7a8c6ae61c60a51dc
parent 264064 0f498d5adbf959b6743f76acc849d87ecb26f19f (current diff)
parent 264081 19b55237cb368ce684bc8626cf9e8abe37ff1d0a (diff)
child 264162 001942e4617b2324bfa6cdfb1155581cbc3f0cc4
push id29428
push usercbook@mozilla.com
push dateThu, 24 Sep 2015 10:01:52 +0000
treeherdermozilla-central@bf2bc1aa78c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
first release with
nightly linux32
bf2bc1aa78c0 / 44.0a1 / 20150924030231 / files
nightly linux64
bf2bc1aa78c0 / 44.0a1 / 20150924030231 / files
nightly mac
bf2bc1aa78c0 / 44.0a1 / 20150924030231 / files
nightly win32
bf2bc1aa78c0 / 44.0a1 / 20150924030231 / files
nightly win64
bf2bc1aa78c0 / 44.0a1 / 20150924030231 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
toolkit/content/tests/unit/test_updateChannelModule.js
toolkit/modules/UpdateChannel.jsm
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app0/empty
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app3/features/system1@tests.mozilla.org.xpi
toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app3/features/system3@tests.mozilla.org.xpi
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1362,18 +1362,16 @@ pref("devtools.command-button-eyedropper
 pref("devtools.command-button-screenshot.enabled", false);
 pref("devtools.command-button-rulers.enabled", false);
 
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 // What was the last active sidebar in the inspector
 pref("devtools.inspector.activeSidebar", "ruleview");
-// Enable the markup preview
-pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
 // Collapse pseudo-elements by default in the rule-view
 pref("devtools.inspector.show_pseudo_elements", false);
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -67,35 +67,35 @@ function init(aEvent)
   }
 
 #ifdef MOZ_UPDATER
   gAppUpdater = new appUpdater();
 
   let defaults = Services.prefs.getDefaultBranch("");
   let channelLabel = document.getElementById("currentChannel");
   let currentChannelText = document.getElementById("currentChannelText");
-  channelLabel.value = UpdateChannel.get();
+  channelLabel.value = UpdateUtils.UpdateChannel;
   if (/^release($|\-)/.test(channelLabel.value))
       currentChannelText.hidden = true;
 #endif
 
 #ifdef XP_MACOSX
   // it may not be sized at this point, and we need its width to calculate its position
   window.sizeToContent();
   window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
 #endif
 }
 
 #ifdef MOZ_UPDATER
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 var gAppUpdater;
 
 function onUnload(aEvent) {
   if (gAppUpdater.isChecking)
     gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
   // Safe to call even when there isn't a download in progress.
   gAppUpdater.removeDownloadListener();
--- a/browser/base/content/browser-feeds.js
+++ b/browser/base/content/browser-feeds.js
@@ -162,10 +162,22 @@ var FeedHandler = {
     // background browsers, we'll update on tab switch.
     if (browserForLink == gBrowser.selectedBrowser) {
       // Batch updates to avoid updating the UI for multiple onLinkAdded events
       // fired within 100ms of each other.
       if (this._updateFeedTimeout)
         clearTimeout(this._updateFeedTimeout);
       this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
     }
-  }
+  },
+
+  init() {
+    window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
+  },
+
+  receiveMessage(msg) {
+    switch (msg.name) {
+      case "FeedWriter:ShownFirstRun":
+        Services.prefs.setBoolPref("browser.feeds.showFirstRunUI", false);
+        break;
+    }
+  },
 };
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -37,18 +37,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                   "resource://gre/modules/Log.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
                                    "@mozilla.org/browser/favicon-service;1",
                                    "mozIAsyncFavicons");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
@@ -960,16 +960,17 @@ var gBrowserInit = {
     // These routines add message listeners. They must run before
     // loading the frame script to ensure that we don't miss any
     // message sent between when the frame script is loaded and when
     // the listener is registered.
     DOMLinkHandler.init();
     gPageStyleMenu.init();
     LanguageDetectionListener.init();
     BrowserOnClick.init();
+    FeedHandler.init();
     DevEdition.init();
     AboutPrivateBrowsingListener.init();
     TrackingProtection.init();
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
@@ -2913,17 +2914,17 @@ var BrowserOnClick = {
       port: location.port,
       timestamp: Math.round(Date.now() / 1000),
       errorCode: transportSecurityInfo.errorCode,
       failedCertChain: asciiCertChain,
       userAgent: window.navigator.userAgent,
       version: 1,
       build: gAppInfo.appBuildID,
       product: gAppInfo.name,
-      channel: UpdateChannel.get()
+      channel: UpdateUtils.UpdateChannel
     }
 
     let reportURL = Services.prefs.getCharPref("security.ssl.errorReporting.url");
 
     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
         .createInstance(Ci.nsIXMLHttpRequest);
     try {
       xhr.open("POST", reportURL);
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -13,18 +13,16 @@ Cu.import("resource://gre/modules/PageTh
 Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
 Cu.import("resource:///modules/DirectoryLinksProvider.jsm");
 Cu.import("resource://gre/modules/NewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Rect",
   "resource://gre/modules/Geometry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-  "resource://gre/modules/UpdateChannel.jsm");
 
 var {
   links: gLinks,
   allPages: gAllPages,
   linkChecker: gLinkChecker,
   pinnedLinks: gPinnedLinks,
   blockedLinks: gBlockedLinks,
   gridPrefs: gGridPrefs
--- a/browser/base/content/test/general/browser_remoteTroubleshoot.js
+++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js
@@ -55,17 +55,17 @@ add_task(function*() {
   Assert.ok(got.message.graphics, "should have graphics");
 
   // Check we have channel and build ID info:
   Assert.equal(got.message.application.buildID, Services.appinfo.appBuildID,
                "should have correct build ID");
 
   let updateChannel = null;
   try {
-    updateChannel = Cu.import("resource://gre/modules/UpdateChannel.jsm", {}).UpdateChannel.get();
+    updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
   } catch (ex) {}
   if (!updateChannel) {
     Assert.ok(!('updateChannel' in got.message.application),
                 "should not have update channel where not available.");
   } else {
     Assert.equal(got.message.application.updateChannel, updateChannel,
                  "should have correct update channel.");
   }
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -4,16 +4,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
 const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
 
 function LOG(str) {
   var prefB = Cc["@mozilla.org/preferences-service;1"].
               getService(Ci.nsIPrefBranch);
 
@@ -708,19 +709,17 @@ FeedWriter.prototype = {
     }
   },
 
   _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) {
     var checkbox = this._getUIElement("alwaysUse");
     if (checkbox) {
       var alwaysUse = false;
       try {
-        var prefs = Cc["@mozilla.org/preferences-service;1"].
-                    getService(Ci.nsIPrefBranch);
-        if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
+        if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
           alwaysUse = true;
       }
       catch(ex) { }
       this._setCheckboxCheckedState(checkbox, alwaysUse);
     }
   },
 
   _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() {
@@ -798,20 +797,17 @@ FeedWriter.prototype = {
           break;
         default:
           this._setAlwaysUseLabel();
       }
     }
   },
 
   _setSelectedHandler: function FW__setSelectedHandler(feedType) {
-    var prefs =
-        Cc["@mozilla.org/preferences-service;1"].
-        getService(Ci.nsIPrefBranch);
-
+    var prefs = Services.prefs;
     var handler = "bookmarks";
     try {
       handler = prefs.getCharPref(getPrefReaderForType(feedType));
     }
     catch (ex) { }
 
     switch (handler) {
       case "web": {
@@ -894,20 +890,18 @@ FeedWriter.prototype = {
 
     // Last-selected application
     var menuItem = liveBookmarksMenuItem.cloneNode(false);
     menuItem.removeAttribute("selected");
     menuItem.setAttribute("anonid", "selectedAppMenuItem");
     menuItem.className = "menuitem-iconic selectedAppMenuItem";
     menuItem.setAttribute("handlerType", "client");
     try {
-      var prefs = Cc["@mozilla.org/preferences-service;1"].
-                  getService(Ci.nsIPrefBranch);
-      this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
-                                                Ci.nsILocalFile);
+      this._selectedApp = Services.prefs.getComplexValue(getPrefAppForType(feedType),
+                                                         Ci.nsILocalFile);
 
       if (this._selectedApp.exists())
         this._initMenuItemWithFile(menuItem, this._selectedApp);
       else {
         // Hide the menuitem if the last selected application doesn't exist
         menuItem.setAttribute("hidden", true);
       }
     }
@@ -953,35 +947,39 @@ FeedWriter.prototype = {
     menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
 
     handlersMenuPopup.appendChild(menuItem);
 
     // separator
     var chooseAppSep = liveBookmarksMenuItem.nextSibling.cloneNode(false);
     handlersMenuPopup.appendChild(chooseAppSep);
 
-    // List of web handlers
-    var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
-               getService(Ci.nsIWebContentConverterService);
-    var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
-    if (handlers.length != 0) {
-      for (var i = 0; i < handlers.length; ++i) {
-        if (!handlers[i].uri) {
-          LOG("Handler with name " + handlers[i].name + " has no URI!? Skipping...");
-          continue;
+    // FIXME: getting a list of webhandlers doesn't work in the content process
+    // right now, see bug 1109714.
+    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_CONTENT) {
+      // List of web handlers
+      var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+                 getService(Ci.nsIWebContentConverterService);
+      var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
+      if (handlers.length != 0) {
+        for (var i = 0; i < handlers.length; ++i) {
+          if (!handlers[i].uri) {
+            LOG("Handler with name " + handlers[i].name + " has no URI!? Skipping...");
+            continue;
+          }
+          menuItem = liveBookmarksMenuItem.cloneNode(false);
+          menuItem.removeAttribute("selected");
+          menuItem.className = "menuitem-iconic";
+          menuItem.setAttribute("label", handlers[i].name);
+          menuItem.setAttribute("handlerType", "web");
+          menuItem.setAttribute("webhandlerurl", handlers[i].uri);
+          handlersMenuPopup.appendChild(menuItem);
+
+          this._setFaviconForWebReader(handlers[i].uri, menuItem);
         }
-        menuItem = liveBookmarksMenuItem.cloneNode(false);
-        menuItem.removeAttribute("selected");
-        menuItem.className = "menuitem-iconic";
-        menuItem.setAttribute("label", handlers[i].name);
-        menuItem.setAttribute("handlerType", "web");
-        menuItem.setAttribute("webhandlerurl", handlers[i].uri);
-        handlersMenuPopup.appendChild(menuItem);
-
-        this._setFaviconForWebReader(handlers[i].uri, menuItem);
       }
     }
 
     this._setSelectedHandler(feedType);
 
     // "Subscribe using..."
     this._setSubscribeUsingLabel();
 
@@ -995,17 +993,17 @@ FeedWriter.prototype = {
 
     // Set up the "Subscribe Now" button
     this._getUIElement("subscribeButton")
         .addEventListener("command", this, false);
 
     // first-run ui
     var showFirstRunUI = true;
     try {
-      showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
+      showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
     }
     catch (ex) { }
     if (showFirstRunUI) {
       var textfeedinfo1, textfeedinfo2;
       switch (feedType) {
         case Ci.nsIFeed.TYPE_VIDEO:
           textfeedinfo1 = "feedSubscriptionVideoPodcast1";
           textfeedinfo2 = "feedSubscriptionVideoPodcast2";
@@ -1024,17 +1022,17 @@ FeedWriter.prototype = {
       var feedinfo2 = this._document.getElementById("feedSubscriptionInfo2");
       var feedinfo2Str = this._getString(textfeedinfo2);
 
       feedinfo1.textContent = feedinfo1Str;
       feedinfo2.textContent = feedinfo2Str;
 
       header.setAttribute('firstrun', 'true');
 
-      prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
+      this._mm.sendAsyncMessage("FeedWriter:ShownFirstRun");
     }
   },
 
   /**
    * Returns the original URI object of the feed and ensures that this
    * component is only ever invoked from the preview document.  
    * @param aWindow 
    *        The window of the document invoking the BrowserFeedWriter
@@ -1085,18 +1083,17 @@ FeedWriter.prototype = {
     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
                  getService(Ci.nsIScriptSecurityManager);
     this._feedPrincipal = secman.getSimpleCodebasePrincipal(this._feedURI);
 
     LOG("Subscribe Preview: feed uri = " + this._window.location.href);
 
     // Set up the subscription UI
     this._initSubscriptionUI();
-    var prefs = Cc["@mozilla.org/preferences-service;1"].
-                getService(Ci.nsIPrefBranch);
+    let prefs = Services.prefs;
     prefs.addObserver(PREF_SELECTED_ACTION, this, false);
     prefs.addObserver(PREF_SELECTED_READER, this, false);
     prefs.addObserver(PREF_SELECTED_WEB, this, false);
     prefs.addObserver(PREF_SELECTED_APP, this, false);
     prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
     prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
     prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
     prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
@@ -1128,18 +1125,17 @@ FeedWriter.prototype = {
 
   close: function FW_close() {
     this._getUIElement("handlersMenuPopup")
         .removeEventListener("command", this, false);
     this._getUIElement("subscribeButton")
         .removeEventListener("command", this, false);
     this._document = null;
     this._window = null;
-    var prefs = Cc["@mozilla.org/preferences-service;1"].
-                getService(Ci.nsIPrefBranch);
+    let prefs = Services.prefs;
     prefs.removeObserver(PREF_SELECTED_ACTION, this);
     prefs.removeObserver(PREF_SELECTED_READER, this);
     prefs.removeObserver(PREF_SELECTED_WEB, this);
     prefs.removeObserver(PREF_SELECTED_APP, this);
     prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
     prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
     prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
     prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
@@ -1167,18 +1163,17 @@ FeedWriter.prototype = {
       this._feedURI = null;
     }
   },
 
   subscribe: function FW_subscribe() {
     var feedType = this._getFeedType();
 
     // Subscribe to the feed using the selected handler and save prefs
-    var prefs = Cc["@mozilla.org/preferences-service;1"].
-                getService(Ci.nsIPrefBranch);
+    var prefs = Services.prefs;
     var defaultHandler = "reader";
     var useAsDefault = this._getUIElement("alwaysUse").getAttribute("checked");
 
     var selectedItem = this._getSelectedItemFromMenulist(this._handlersMenuList);
     let subscribeCallback = function() {
       if (selectedItem.hasAttribute("webhandlerurl")) {
         var webURI = selectedItem.getAttribute("webhandlerurl");
         prefs.setCharPref(getPrefReaderForType(feedType), "web");
@@ -1315,15 +1310,24 @@ FeedWriter.prototype = {
         if (aDataLen > 0) {
           var dataURL = "data:" + aMimeType + ";base64," +
                         btoa(String.fromCharCode.apply(null, aData));
           aMenuItem.setAttribute('image', dataURL);
         }
       });
   },
 
+  get _mm() {
+    let mm = this._window.QueryInterface(Ci.nsIInterfaceRequestor).
+                          getInterface(Ci.nsIDocShell).
+                          QueryInterface(Ci.nsIInterfaceRequestor).
+                          getInterface(Ci.nsIContentFrameMessageManager);
+    delete this._mm;
+    return this._mm = mm;
+  },
+
   classID: FEEDWRITER_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver,
                                          Ci.nsINavHistoryObserver,
                                          Ci.nsIDOMGlobalPropertyInitializer])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FeedWriter]);
--- a/browser/components/feeds/test/mochitest.ini
+++ b/browser/components/feeds/test/mochitest.ini
@@ -5,14 +5,11 @@ support-files =
   bug408328-data.xml
   bug436801-data.xml
   bug494328-data.xml
   bug589543-data.xml
   valid-feed.xml
   valid-unsniffable-feed.xml
 
 [test_bug436801.html]
-skip-if = e10s
 [test_bug494328.html]
-skip-if = e10s
 [test_bug589543.html]
-skip-if = e10s
 [test_registerHandler.html]
--- a/browser/components/loop/modules/MozLoopAPI.jsm
+++ b/browser/components/loop/modules/MozLoopAPI.jsm
@@ -20,18 +20,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
                                         "resource:///modules/loop/LoopStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
                                         "resource://gre/modules/MozSocialAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
                                         "resource://gre/modules/PageMetadata.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                         "resource://gre/modules/PluralForm.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                        "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                        "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                         "resource:///modules/UITour.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Social",
                                         "resource:///modules/Social.jsm");
 XPCOMUtils.defineLazyGetter(this, "appInfo", function() {
   return Cc["@mozilla.org/xre/app-info;1"]
            .getService(Ci.nsIXULAppInfo)
            .QueryInterface(Ci.nsIXULRuntime);
@@ -805,17 +805,17 @@ function injectLoopAPI(targetWindow) {
     appVersionInfo: {
       enumerable: true,
       get: function() {
         if (!appVersionInfo) {
           // If the lazy getter explodes, we're probably loaded in xpcshell,
           // which doesn't have what we need, so log an error.
           try {
             appVersionInfo = Cu.cloneInto({
-              channel: UpdateChannel.get(),
+              channel: UpdateUtils.UpdateChannel,
               version: appInfo.version,
               OS: appInfo.OS
             }, targetWindow);
           } catch (ex) {
             // only log outside of xpcshell to avoid extra message noise
             if (typeof targetWindow !== 'undefined' && "console" in targetWindow) {
               MozLoopService.log.error("Failed to construct appVersionInfo; if this isn't " +
                                        "an xpcshell unit test, something is wrong", ex);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -119,18 +119,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX",
                                   "resource:///modules/SignInToWebsite.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 
 #ifdef E10S_TESTING_ONLY
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 #endif
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
                                   "resource:///modules/ContentCrashReporters.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
                                   "resource:///modules/ContentCrashReporters.jsm");
 #endif
@@ -2972,17 +2972,17 @@ var E10SUINotification = {
   // e10s testing period to Nightly users.
   CURRENT_NOTICE_COUNT: 4,
   CURRENT_PROMPT_PREF: "browser.displayedE10SPrompt.1",
   PREVIOUS_PROMPT_PREF: "browser.displayedE10SPrompt",
 
   checkStatus: function() {
     let skipE10sChecks = false;
     try {
-      let updateChannel = UpdateChannel.get();
+      let updateChannel = UpdateUtils.UpdateChannel;
       let channelAuthorized = updateChannel == "nightly" || updateChannel == "aurora";
 
       skipE10sChecks = !channelAuthorized ||
                        Services.prefs.getBoolPref("browser.tabs.remote.disabled-for-a11y");
     } catch(e) {}
 
     if (skipE10sChecks) {
       return;
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -160,18 +160,18 @@ var gMainPane = {
       shouldProceed = !cancelQuit.data;
 
       if (shouldProceed) {
         for (let prefToChange of prefsToChange) {
           prefToChange.value = e10sCheckbox.checked;
         }
 
         let tmp = {};
-        Components.utils.import("resource://gre/modules/UpdateChannel.jsm", tmp);
-        if (!e10sCheckbox.checked && tmp.UpdateChannel.get() != "default") {
+        Components.utils.import("resource://gre/modules/UpdateUtils.jsm", tmp);
+        if (!e10sCheckbox.checked && tmp.UpdateUtils.UpdateChannel != "default") {
           Services.prefs.setBoolPref("browser.requestE10sFeedback", true);
           Services.prompt.alert(window, brandName, bundle.getString("e10sFeedbackAfterRestart"));
         }
         Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestart);
       }
     }
 
     // Revert the checkbox in case we didn't quit
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -15,18 +15,18 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                   "resource://gre/modules/TelemetryEnvironment.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
                                   "resource://gre/modules/TelemetryLog.jsm");
@@ -270,17 +270,17 @@ Experiments.Policy.prototype = {
     return new Date(this.now().getTime() + offset);
   },
 
   oneshotTimer: function (callback, timeout, thisObj, name) {
     return CommonUtils.namedTimer(callback, timeout, thisObj, name);
   },
 
   updatechannel: function () {
-    return UpdateChannel.get();
+    return UpdateUtils.UpdateChannel;
   },
 
   locale: function () {
     let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
     return chrome.getSelectedLocale("global");
   },
 
   /**
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -21,18 +21,18 @@ Cu.import("resource://gre/modules/Timer.
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
   "resource://gre/modules/osfile.jsm")
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
   "@mozilla.org/network/effective-tld-service;1",
   "nsIEffectiveTLDService");
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
   return new TextDecoder();
 });
 XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
   return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
@@ -275,17 +275,17 @@ var DirectoryLinksProvider = {
       this._setupStartEndTime(link);
       this._suggestedLinks.set(suggestedSite, suggestedMap);
     }
   },
 
   _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
     // Replace with the same display locale used for selecting links data
     uri = uri.replace("%LOCALE%", this.locale);
-    uri = uri.replace("%CHANNEL%", UpdateChannel.get());
+    uri = uri.replace("%CHANNEL%", UpdateUtils.UpdateChannel);
 
     return this._downloadJsonData(uri).then(json => {
       return OS.File.writeAtomic(this._directoryFilePath, json, {tmpPath: this._directoryFilePath + ".tmp"});
     });
   },
 
   /**
    * Downloads a links with json content
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -93,16 +93,22 @@
     @media not all and (-moz-os-version: windows-win7) {
       @media not all and (-moz-os-version: windows-win8) {
         @media (-moz-windows-default-theme) {
           #main-window {
             background-color: hsl(0, 0%, 78%);
           }
         }
 
+        @media not all and (-moz-windows-default-theme) {
+          #main-window {
+            background-color: transparent;
+          }
+        }
+
         #titlebar-buttonbox,
         .titlebar-button {
           -moz-appearance: none !important;
         }
 
         .titlebar-button {
           border: none;
           margin: 0 !important;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -344,28 +344,23 @@
   z-index: 1;
 }
 
 #nav-bar {
   background-clip: padding-box;
   background-image: linear-gradient(@toolbarHighlight@, transparent);
 }
 
-@media (-moz-os-version: windows-xp),
-       (-moz-os-version: windows-vista),
-       (-moz-os-version: windows-win7),
-       (-moz-os-version: windows-win8) {
-  #nav-bar {
-    border-top: 1px solid @toolbarShadowColor@ !important;
-    box-shadow: 0 1px 0 @toolbarHighlight@ inset;
-  }
-  @media not all and (-moz-windows-compositor) {
-    #TabsToolbar[collapsed="true"] + #nav-bar {
-      border-top-style: none !important;
-    }
+#nav-bar {
+  border-top: 1px solid @toolbarShadowColor@ !important;
+  box-shadow: 0 1px 0 @toolbarHighlight@ inset;
+}
+@media not all and (-moz-windows-compositor) {
+  #TabsToolbar[collapsed="true"] + #nav-bar {
+    border-top-style: none !important;
   }
 }
 
 #personal-bookmarks {
   min-height: 24px;
 }
 
 #print-preview-toolbar:not(:-moz-lwtheme) {
@@ -2000,27 +1995,34 @@ richlistitem[type~="action"][actiontype=
   }
 }
 
 /* Remove border between tab strip and navigation toolbar on Windows 10+ */
 @media not all and (-moz-os-version: windows-xp) {
   @media not all and (-moz-os-version: windows-vista) {
     @media not all and (-moz-os-version: windows-win7) {
       @media not all and (-moz-os-version: windows-win8) {
-        .tab-background-end[visuallyselected=true]::after,
-        .tab-background-start[visuallyselected=true]::after {
-          content: none;
-        }
-
-        #TabsToolbar {
-          --tab-stroke-background-size: 0 0;
-        }
-
-        :root {
-          --tab-toolbar-navbar-overlap: 0;
+        @media (-moz-windows-default-theme) {
+          .tab-background-end[visuallyselected=true]::after,
+          .tab-background-start[visuallyselected=true]::after {
+            content: none;
+          }
+
+          #TabsToolbar {
+            --tab-stroke-background-size: 0 0;
+          }
+
+          :root {
+            --tab-toolbar-navbar-overlap: 0;
+          }
+
+          #nav-bar {
+            border-top-style: none !important;
+            box-shadow: none;
+          }
         }
       }
     }
   }
 }
 
 /* Invert the unhovered close tab icons on bright-text tabs */
 @media not all and (min-resolution: 1.1dppx) {
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -218,17 +218,19 @@ menu.subviewbutton > .menu-right:-moz-lo
     color: -moz-FieldText;
   }
 
   #BMB_bookmarksPopup .subviewbutton[disabled=true] > .menu-text {
     color: GrayText;
   }
 
   #BMB_bookmarksPopup menupopup[placespopup=true] > hbox {
-    box-shadow: none;
+    /* After fixing of bug 1194480 the box-shadow can be removed again */
+    /* box-shadow: none; */
+    box-shadow: 0 0 4px rgba(0,0,0,0.02);
     background: -moz-field;
     border: 1px solid ThreeDShadow;
   }
 
   .subviewbutton.panel-subview-footer,
   #BMB_bookmarksPopup .subviewbutton.panel-subview-footer {
     color: ButtonText;
   }
--- a/devtools/client/markupview/markup-view.js
+++ b/devtools/client/markupview/markup-view.js
@@ -3,17 +3,16 @@
 /* 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/. */
 
 const {Cc, Cu, Ci} = require("chrome");
 
 // Page size for pageup/pagedown
 const PAGE_SIZE = 10;
-const PREVIEW_AREA = 700;
 const DEFAULT_MAX_CHILDREN = 100;
 const COLLAPSE_ATTRIBUTE_LENGTH = 120;
 const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
 const COLLAPSE_DATA_URL_LENGTH = 60;
 const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
 const GRAB_DELAY = 400;
 const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50;
 const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5;
@@ -117,17 +116,16 @@ function MarkupView(aInspector, aFrame, 
   this._onCopy = this._onCopy.bind(this);
   this._frame.contentWindow.addEventListener("copy", this._onCopy);
 
   this._boundFocus = this._onFocus.bind(this);
   this._frame.addEventListener("focus", this._boundFocus, false);
 
   this._makeTooltipPersistent = this._makeTooltipPersistent.bind(this);
 
-  this._initPreview();
   this._initTooltips();
   this._initHighlighter();
 
   EventEmitter.decorate(this);
 }
 
 exports.MarkupView = MarkupView;
 
@@ -642,16 +640,21 @@ MarkupView.prototype = {
         }
         this.navigate(selection);
         break;
       }
       case Ci.nsIDOMKeyEvent.DOM_VK_F2: {
         this.beginEditingOuterHTML(this._selectedContainer.node);
         break;
       }
+      case Ci.nsIDOMKeyEvent.DOM_VK_S: {
+        let selection = this._selectedContainer.node;
+        this._inspector.scrollNodeIntoView(selection);
+        break;
+      }
       case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE: {
         if (this.isDragging) {
           this.cancelDragging();
           break;
         }
       }
       default:
         handled = false;
@@ -1567,102 +1570,16 @@ MarkupView.prototype = {
 
     this._lastDropTarget = null;
     this._lastDragTarget = null;
 
     return this._destroyer;
   },
 
   /**
-   * Initialize the preview panel.
-   */
-  _initPreview: function() {
-    this._previewEnabled = Services.prefs.getBoolPref("devtools.inspector.markupPreview");
-    if (!this._previewEnabled) {
-      return;
-    }
-
-    this._previewBar = this.doc.querySelector("#previewbar");
-    this._preview = this.doc.querySelector("#preview");
-    this._viewbox = this.doc.querySelector("#viewbox");
-
-    this._previewBar.classList.remove("disabled");
-
-    this._previewWidth = this._preview.getBoundingClientRect().width;
-
-    this._boundResizePreview = this._resizePreview.bind(this);
-    this._frame.contentWindow.addEventListener("resize",
-      this._boundResizePreview, true);
-    this._frame.contentWindow.addEventListener("overflow",
-      this._boundResizePreview, true);
-    this._frame.contentWindow.addEventListener("underflow",
-      this._boundResizePreview, true);
-
-    this._boundUpdatePreview = this._updatePreview.bind(this);
-    this._frame.contentWindow.addEventListener("scroll",
-      this._boundUpdatePreview, true);
-    this._updatePreview();
-  },
-
-  /**
-   * Move the preview viewbox.
-   */
-  _updatePreview: function() {
-    if (!this._previewEnabled) {
-      return;
-    }
-    let win = this._frame.contentWindow;
-
-    if (win.scrollMaxY == 0) {
-      this._previewBar.classList.add("disabled");
-      return;
-    }
-
-    this._previewBar.classList.remove("disabled");
-
-    let ratio = this._previewWidth / PREVIEW_AREA;
-    let width = ratio * win.innerWidth;
-
-    let height = ratio * (win.scrollMaxY + win.innerHeight);
-    let scrollTo
-    if (height >= win.innerHeight) {
-      scrollTo = -(height - win.innerHeight) * (win.scrollY / win.scrollMaxY);
-      this._previewBar.setAttribute("style", "height:" + height +
-        "px;transform:translateY(" + scrollTo + "px)");
-    } else {
-      this._previewBar.setAttribute("style", "height:100%");
-    }
-
-    let bgSize = ~~width + "px " + ~~height + "px";
-    this._preview.setAttribute("style", "background-size:" + bgSize);
-
-    height = ~~(win.innerHeight * ratio) + "px";
-    let top = ~~(win.scrollY * ratio) + "px";
-    this._viewbox.setAttribute("style", "height:" + height +
-      ";transform: translateY(" + top + ")");
-  },
-
-  /**
-   * Hide the preview while resizing, to avoid slowness.
-   */
-  _resizePreview: function() {
-    if (!this._previewEnabled) {
-      return;
-    }
-    let win = this._frame.contentWindow;
-    this._previewBar.classList.add("hide");
-    clearTimeout(this._resizePreviewTimeout);
-
-    setTimeout(() => {
-      this._updatePreview();
-      this._previewBar.classList.remove("hide");
-    }, 1000);
-  },
-
-  /**
    * Takes an element as it's only argument and marks the element
    * as the drop target
    */
   indicateDropTarget: function(el) {
     if (this._lastDropTarget) {
       this._lastDropTarget.classList.remove("drop-target");
     }
 
--- a/devtools/client/markupview/markup-view.xhtml
+++ b/devtools/client/markupview/markup-view.xhtml
@@ -92,14 +92,10 @@
           save="${elt}"
           class="editor comment theme-comment"><!--
    --><span>&lt;!--</span><!--
    --><pre save="${value}" style="display:inline-block; white-space: normal;" tabindex="0"></pre><!--
    --><span>--&gt;</span><!--
  --></span>
 
   </div>
-  <div id="previewbar" class="disabled">
-     <div id="preview"/>
-     <div id="viewbox"/>
-  </div>
 </body>
 </html>
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -186,16 +186,25 @@ StyleSheetEditor.prototype = {
         this._friendlyName = decodeURI(this._friendlyName);
       } catch (ex) {
       }
     }
     return this._friendlyName;
   },
 
   /**
+   * Check if transitions are enabled for style changes.
+   *
+   * @return Boolean
+   */
+  get transitionsEnabled() {
+    return Services.prefs.getBoolPref(TRANSITION_PREF);
+  },
+
+  /**
    * If this is an original source, get the path of the CSS file it generated.
    */
   linkCSSFile: function() {
     if (!this.styleSheet.isOriginalSource) {
       return;
     }
 
     let relatedSheet = this.styleSheet.relatedStyleSheet;
@@ -490,19 +499,17 @@ StyleSheetEditor.prototype = {
                              // (stylesheet is enabled) so that 'missed' updates
                              // while the stylesheet is disabled can be performed
                              // when it is enabled back. @see enableStylesheet
 
     if (this.sourceEditor) {
       this._state.text = this.sourceEditor.getText();
     }
 
-    let transitionsEnabled = Services.prefs.getBoolPref(TRANSITION_PREF);
-
-    this.styleSheet.update(this._state.text, transitionsEnabled)
+    this.styleSheet.update(this._state.text, this.transitionsEnabled)
                    .then(null, Cu.reportError);
   },
 
   /**
    * Handle mousemove events, calling _highlightSelectorAt after a delay only
    * and reseting the delay everytime.
    */
   _onMouseMove: function(e) {
@@ -678,17 +685,17 @@ StyleSheetEditor.prototype = {
    * file from disk and live update the stylesheet object with the contents.
    */
   updateLinkedStyleSheet: function() {
     OS.File.read(this.linkedCSSFile).then((array) => {
       let decoder = new TextDecoder();
       let text = decoder.decode(array);
 
       let relatedSheet = this.styleSheet.relatedStyleSheet;
-      relatedSheet.update(text, true);
+      relatedSheet.update(text, this.transitionsEnabled);
     }, this.markLinkedFileBroken);
   },
 
   /**
     * Retrieve custom key bindings objects as expected by Editor.
     * Editor action names are not displayed to the user.
     *
     * @return {array} key binding objects for the source editor
--- a/devtools/client/themes/markup-view.css
+++ b/devtools/client/themes/markup-view.css
@@ -47,59 +47,16 @@
 .not-displayed .close {
   opacity: .7;
 }
 
 .tag-line {
   padding-left: 2px;
 }
 
-/* Preview */
-
-#previewbar {
-  position: fixed;
-  top: 0;
-  right: 0;
-  width: 90px;
-  background: black;
-  border-left: 1px solid;
-  border-bottom: 1px solid;
-  overflow: hidden;
-}
-
-#previewbar {
-  background: var(--theme-tab-toolbar-background);
-  border-color: var(--theme-splitter-color);
-}
-
-#preview {
-  position: absolute;
-  top: 0;
-  right: 5px;
-  width: 80px;
-  height: 100%;
-  background-image: -moz-element(#root);
-  background-repeat: no-repeat;
-}
-
-#previewbar.hide,
-#previewbar.disabled {
-  display: none;
-}
-
-#viewbox {
-  position: absolute;
-  top: 0;
-  right: 5px;
-  width: 80px;
-  border: 1px dashed #888;
-  background: rgba(205,205,255,0.2);
-  outline: 1px solid transparent;
-}
-
 /* Events */
 .markupview-events {
   font-size: 8px;
   font-weight: bold;
   line-height: 10px;
   border-radius: 3px;
   padding: 0px 2px;
   -moz-margin-start: 5px;
--- a/mobile/android/chrome/content/aboutFeedback.js
+++ b/mobile/android/chrome/content/aboutFeedback.js
@@ -17,17 +17,17 @@ const HEARTY_ICON_XXHDPI = "chrome://bro
 
 const FLOATY_ICON_MDPI = "chrome://browser/skin/images/icon_floaty_mdpi.png";
 const FLOATY_ICON_HDPI = "chrome://browser/skin/images/icon_floaty_hdpi.png";
 const FLOATY_ICON_XHDPI = "chrome://browser/skin/images/icon_floaty_xhdpi.png";
 const FLOATY_ICON_XXHDPI = "chrome://browser/skin/images/icon_floaty_xxhdpi.png";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Messaging.jsm");
-Cu.import("resource://gre/modules/UpdateChannel.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
 document.addEventListener("DOMContentLoaded", init, false);
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 function init() {
   let anchors = document.querySelectorAll(".maybe-later");
@@ -133,17 +133,17 @@ function sendFeedback(aEvent) {
     data["url"] = urlElement.value;
   }
 
   data["device"] = Services.sysinfo.get("device");
   data["manufacturer"] = Services.sysinfo.get("manufacturer");
   data["platform"] = Services.appinfo.OS;
   data["version"] = Services.appinfo.version;
   data["locale"] = Services.locale.getSystemLocale().getCategory("NSILOCALE_CTYPE");
-  data["channel"] = UpdateChannel.get();
+  data["channel"] = UpdateUtils.UpdateChannel;
 
   // Source field is added only when Fennec prompts the user.
   let getParam = window.location.href.split("?");
   if (getParam.length > 1) {
     let urlParam = new URLSearchParams(getParam[1]);
     if(urlParam.get("source")) {
       data["source"] = urlParam.get("source");
     }
--- a/netwerk/protocol/http/UserAgentUpdates.jsm
+++ b/netwerk/protocol/http/UserAgentUpdates.jsm
@@ -22,17 +22,17 @@ XPCOMUtils.defineLazyModuleGetter(
 
 XPCOMUtils.defineLazyModuleGetter(
   this, "OS", "resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(
   this, "Promise", "resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(
-  this, "UpdateChannel", "resource://gre/modules/UpdateChannel.jsm");
+  this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(
   this, "gUpdateTimer", "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
 
 XPCOMUtils.defineLazyGetter(this, "gApp",
   function() {
     return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo)
                                             .QueryInterface(Ci.nsIXULRuntime);
@@ -187,17 +187,17 @@ this.UserAgentUpdates = {
   _getParameters() {
     return {
       "%DATE%": function() { return Date.now().toString(); },
       "%PRODUCT%": function() { return gApp.name; },
       "%APP_ID%": function() { return gApp.ID; },
       "%APP_VERSION%": function() { return gApp.version; },
       "%BUILD_ID%": function() { return gApp.appBuildID; },
       "%OS%": function() { return gApp.OS; },
-      "%CHANNEL%": function() { return UpdateChannel.get(); },
+      "%CHANNEL%": function() { return UpdateUtils.UpdateChannel; },
       "%DISTRIBUTION%": function() { return this._getPref(PREF_APP_DISTRIBUTION, ""); },
       "%DISTRIBUTION_VERSION%": function() { return this._getPref(PREF_APP_DISTRIBUTION_VERSION, ""); },
     };
   },
 
   _getUpdateURL: function() {
     let url = this._getPref(PREF_UPDATES_URL, "");
     let params = this._getParameters();
--- a/services/datareporting/policy.jsm
+++ b/services/datareporting/policy.jsm
@@ -20,17 +20,17 @@ this.EXPORTED_SYMBOLS = [
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 #endif
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/UpdateChannel.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
 
 // The current policy version number. If the version number stored in the prefs
 // is smaller than this, data upload will be disabled until the user is re-notified
 // about the policy changes.
 const DATAREPORTING_POLICY_VERSION = 1;
 
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
@@ -383,17 +383,17 @@ this.DataReportingPolicy.prototype = Obj
   },
 
   /**
    * The minimum policy version which for dataSubmissionPolicyAccepted to
    * to be valid.
    */
   get minimumPolicyVersion() {
     // First check if the current channel has an ove
-    let channel = UpdateChannel.get(false);
+    let channel = UpdateUtils.getUpdateChannel(false);
     let channelPref = this._prefs.get("minimumPolicyVersion.channel-" + channel);
     return channelPref !== undefined ?
            channelPref : this._prefs.get("minimumPolicyVersion", 1);
   },
 
   get dataSubmissionPolicyAcceptedVersion() {
     return this._prefs.get("dataSubmissionPolicyAcceptedVersion", 0);
   },
--- a/services/datareporting/tests/xpcshell/test_policy.js
+++ b/services/datareporting/tests/xpcshell/test_policy.js
@@ -3,32 +3,32 @@
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
 Cu.import("resource://testing-common/services/datareporting/mocks.jsm");
-Cu.import("resource://gre/modules/UpdateChannel.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 function getPolicy(name,
                    aCurrentPolicyVersion = 1,
                    aMinimumPolicyVersion = 1,
                    aBranchMinimumVersionOverride) {
   let branch = "testing.datareporting." + name;
 
   // The version prefs should not be removed on reset, so set them in the
   // default branch.
   let defaultPolicyPrefs = new Preferences({ branch: branch + ".policy."
                                            , defaultBranch: true });
   defaultPolicyPrefs.set("currentPolicyVersion", aCurrentPolicyVersion);
   defaultPolicyPrefs.set("minimumPolicyVersion", aMinimumPolicyVersion);
-  let branchOverridePrefName = "minimumPolicyVersion.channel-" + UpdateChannel.get(false);
+  let branchOverridePrefName = "minimumPolicyVersion.channel-" + UpdateUtils.getUpdateChannel(false);
   if (aBranchMinimumVersionOverride !== undefined)
     defaultPolicyPrefs.set(branchOverridePrefName, aBranchMinimumVersionOverride);
   else
     defaultPolicyPrefs.reset(branchOverridePrefName);
 
   let policyPrefs = new Preferences(branch + ".policy.");
   let healthReportPrefs = new Preferences(branch + ".healthreport.");
 
--- a/services/healthreport/healthreporter.jsm
+++ b/services/healthreport/healthreporter.jsm
@@ -25,18 +25,18 @@ Cu.import("resource://gre/modules/osfile
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
                                   "resource://gre/modules/TelemetryController.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 // Oldest year to allow in date preferences. This module was implemented in
 // 2012 and no dates older than that should be encountered.
 const OLDEST_ALLOWED_YEAR = 2012;
 
 const DAYS_IN_PAYLOAD = 180;
 
 const DEFAULT_DATABASE_NAME = "healthreport.sqlite";
@@ -1098,17 +1098,17 @@ AbstractHealthReporter.prototype = Objec
         out[k] = ai[v];
       }
     } catch (ex) {
       this._log.warn("Could not obtain Services.appinfo: " +
                      CommonUtils.exceptionStr(ex));
     }
 
     try {
-      out["updateChannel"] = UpdateChannel.get();
+      out["updateChannel"] = UpdateUtils.UpdateChannel;
     } catch (ex) {
       this._log.warn("Could not obtain update channel: " +
                      CommonUtils.exceptionStr(ex));
     }
 
     return out;
   },
 });
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -41,18 +41,18 @@ Cu.import("resource://gre/modules/osfile
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-common/utils.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
                                   "resource://gre/modules/PlacesDBUtils.jsm");
 
 
 const LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_LAST_NUMERIC};
 const LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_LAST_TEXT};
 const DAILY_DISCRETE_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC};
 const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC};
@@ -328,17 +328,17 @@ AppInfoProvider.prototype = Object.freez
       try {
         yield m.setLastText(k, ai[v]);
       } catch (ex) {
         this._log.warn("Error obtaining Services.appinfo." + v);
       }
     }
 
     try {
-      yield m.setLastText("updateChannel", UpdateChannel.get());
+      yield m.setLastText("updateChannel", UpdateUtils.UpdateChannel);
     } catch (ex) {
       this._log.warn("Could not obtain update channel: " +
                      CommonUtils.exceptionStr(ex));
     }
 
     yield m.setLastText("distributionID", this._prefs.get("distribution.id", ""));
     yield m.setLastText("distributionVersion", this._prefs.get("distribution.version", ""));
     yield m.setLastText("hotfixVersion", this._prefs.get("extensions.hotfix.lastVersion", ""));
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -446,10 +446,36 @@ this.BrowserTestUtils = {
           resolve();
         }
       }, true);
 
       if (!dontRemove && !tab.closing) {
         tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
       }
     });
+  },
+
+  /**
+   * Version of EventUtils' `sendChar` function; it will synthesize a keypress
+   * event in a child process and returns a Promise that will result when the
+   * event was fired. Instead of a Window, a Browser object is required to be
+   * passed to this function.
+   *
+   * @param {String} char
+   *        A character for the keypress event that is sent to the browser.
+   * @param {Browser} browser
+   *        Browser element, must not be null.
+   *
+   * @returns {Promise}
+   * @resolves True if the keypress event was synthesized.
+   */
+  sendChar(char, browser) {
+    return new Promise(resolve => {
+      let mm = browser.messageManager;
+      mm.addMessageListener("Test:SendCharDone", function charMsg(message) {
+        mm.removeMessageListener("Test:SendCharDone", charMsg);
+        resolve(message.data.sendCharResult);
+      });
+
+      mm.sendAsyncMessage("Test:SendChar", { char: char });
+    });
   }
 };
--- a/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
+++ b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
@@ -6,16 +6,20 @@
 // Set up a dummy environment so that EventUtils works. We need to be careful to
 // pass a window object into each EventUtils method we call rather than having
 // it rely on the |window| global.
 var EventUtils = {};
 EventUtils.window = {};
 EventUtils.parent = EventUtils.window;
 EventUtils._EU_Ci = Components.interfaces;
 EventUtils._EU_Cc = Components.classes;
+// EventUtils' `sendChar` function relies on the navigator to synthetize events.
+EventUtils.navigator = content.document.defaultView.navigator;
+EventUtils.KeyboardEvent = content.document.defaultView.KeyboardEvent;
+
 Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
 addMessageListener("Test:SynthesizeMouse", (message) => {
   let data = message.data;
   let target = data.target;
   if (typeof target == "string") {
     target = content.document.querySelector(target);
   }
@@ -34,8 +38,13 @@ addMessageListener("Test:SynthesizeMouse
       left += rect.width / 2;
       top += rect.height / 2;
     }
   }
 
   let result = EventUtils.synthesizeMouseAtPoint(left, top, data.event, content);
   sendAsyncMessage("Test:SynthesizeMouseDone", { defaultPrevented: result });
 });
+
+addMessageListener("Test:SendChar", message => {
+  let result = EventUtils.sendChar(message.data.char, content);
+  sendAsyncMessage("Test:SendCharDone", { sendCharResult: result });
+});
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -90,16 +90,17 @@ user_pref("browser.safebrowsing.provider
 user_pref("browser.safebrowsing.provider.mozilla.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
 user_pref("browser.safebrowsing.provider.mozilla.updateURL", "http://%(server)s/safebrowsing-dummy/update");
 user_pref("privacy.trackingprotection.introURL", "http://%(server)s/trackingprotection/tour");
 // Point update checks to the local testing server for fast failures
 user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL");
 user_pref("extensions.update.background.url", "http://%(server)s/extensions-dummy/updateBackgroundURL");
 user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL");
 user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL");
+user_pref("extensions.systemAddon.update.url", "http://%(server)s/dummy-system-addons.xml");
 // Turn off extension updates so they don't bother tests
 user_pref("extensions.update.enabled", false);
 // Make sure opening about:addons won't hit the network
 user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
 // Make sure AddonRepository won't hit the network
 user_pref("extensions.getAddons.maxResults", 0);
 user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
 user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
--- a/testing/talos/talos/config.py
+++ b/testing/talos/talos/config.py
@@ -135,16 +135,18 @@ DEFAULTS = dict(
         'extensions.getAddons.search.browseURL':
             'http://127.0.0.1/extensions-dummy/repositoryBrowseURL',
         'extensions.getAddons.search.url':
             'http://127.0.0.1/extensions-dummy/repositorySearchURL',
         'plugins.update.url':
             'http://127.0.0.1/plugins-dummy/updateCheckURL',
         'media.gmp-manager.url':
             'http://127.0.0.1/gmpmanager-dummy/update.xml',
+        'extensions.systemAddon.update.url':
+            'http://127.0.0.1/dummy-system-addons.xml',
         'media.navigator.enabled': True,
         'media.peerconnection.enabled': True,
         'media.navigator.permission.disabled': True,
         'media.capturestream_hints.enabled': True,
         'browser.contentHandlers.types.0.uri': 'http://127.0.0.1/rss?url=%s',
         'browser.contentHandlers.types.1.uri': 'http://127.0.0.1/rss?url=%s',
         'browser.contentHandlers.types.2.uri': 'http://127.0.0.1/rss?url=%s',
         'browser.contentHandlers.types.3.uri': 'http://127.0.0.1/rss?url=%s',
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -1500,16 +1500,17 @@ try {
 
 // We need to avoid hitting the network with certain components.
 try {
   if (runningInParent) {
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
       .getService(Components.interfaces.nsIPrefBranch);
 
     prefs.setCharPref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml");
+    prefs.setCharPref("extensions.systemAddon.update.url", "http://%(server)s/dummy-system-addons.xml");
     prefs.setCharPref("browser.selfsupport.url", "https://%(server)s/selfsupport-dummy/");
     prefs.setCharPref("toolkit.telemetry.server", "https://%(server)s/telemetry-dummy");
     prefs.setCharPref("browser.search.geoip.url", "https://%(server)s/geoip-dummy");
   }
 } catch (e) { }
 
 // Make tests run consistently on DevEdition (which has a lightweight theme
 // selected by default).
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5309,22 +5309,16 @@
     "kind": "flag",
     "description": "Social has been enabled at least once on the current session"
   },
   "ENABLE_PRIVILEGE_EVER_CALLED": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "Whether enablePrivilege has ever been called during the current session"
   },
-  "READ_SAVED_PING_SUCCESS": {
-    "alert_emails": ["perf-telemetry-alerts@mozilla.com"],
-    "expires_in_version": "never",
-    "kind": "boolean",
-    "description": "Successfully reading a saved ping file"
-  },
   "TOUCH_ENABLED_DEVICE": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "The device supports touch input",
     "cpp_guard": "XP_WIN"
   },
   "COMPONENTS_SHIM_ACCESSED_BY_CONTENT": {
     "expires_in_version": "never",
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -75,18 +75,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
                                   "resource://gre/modules/TelemetryStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe",
                                   "resource://gre/modules/ThirdPartyCookieProbe.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                   "resource://gre/modules/TelemetryEnvironment.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionRecorder",
                                   "resource://gre/modules/SessionRecorder.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
                                   "resource://gre/modules/TelemetryArchive.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySession",
                                   "resource://gre/modules/TelemetrySession.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                   "resource://gre/modules/TelemetrySend.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                   "resource://gre/modules/TelemetryReportingPolicy.jsm");
@@ -407,17 +407,17 @@ var Impl = {
     try {
       arch = Services.sysinfo.get("arch");
     } catch (e) {
       this._log.trace("assemblePing - Unable to get system architecture.", e);
     }
 
     let updateChannel = null;
     try {
-      updateChannel = UpdateChannel.get(false);
+      updateChannel = UpdateUtils.getUpdateChannel(false);
     } catch (e) {
       this._log.trace("assemblePing - Unable to get update channel.", e);
     }
 
     return {
       architecture: arch,
       buildId: Services.appinfo.appBuildID,
       name: Services.appinfo.name,
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -27,18 +27,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/ctypes.jsm");
 #ifndef MOZ_WIDGET_GONK
 Cu.import("resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 #endif
 XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
                                   "resource://gre/modules/ProfileAge.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 const CHANGE_THROTTLE_INTERVAL_MS = 5 * 60 * 1000;
 
 /**
  * This is a policy object used to override behavior for testing.
  */
 var Policy = {
   now: () => new Date(),
@@ -1002,17 +1002,17 @@ EnvironmentCache.prototype = {
   },
 
   /**
    * Update the cached settings data.
    */
   _updateSettings: function () {
     let updateChannel = null;
     try {
-      updateChannel = UpdateChannel.get(false);
+      updateChannel = UpdateUtils.getUpdateChannel(false);
     } catch (e) {}
 
     this._currentEnvironment.settings = {
 #ifndef MOZ_WIDGET_GONK
       addonCompatibilityCheckEnabled: AddonManager.checkCompatibility,
 #endif
       blocklistEnabled: Preferences.get(PREF_BLOCKLIST_ENABLED, true),
 #ifndef MOZ_WIDGET_ANDROID
--- a/toolkit/components/telemetry/TelemetryReportingPolicy.jsm
+++ b/toolkit/components/telemetry/TelemetryReportingPolicy.jsm
@@ -14,18 +14,18 @@ Cu.import("resource://gre/modules/Log.js
 Cu.import("resource://gre/modules/Preferences.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/Timer.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://services-common/observers.js", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                   "resource://gre/modules/TelemetrySend.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 const LOGGER_NAME = "Toolkit.Telemetry";
 const LOGGER_PREFIX = "TelemetryReportingPolicy::";
 
 // Oldest year to allow in date preferences. The FHR infobar was implemented in
 // 2012 and no dates older than that should be encountered.
 const OLDEST_ALLOWED_ACCEPTANCE_YEAR = 2012;
 
@@ -246,17 +246,17 @@ var TelemetryReportingPolicyImpl = {
    */
   get minimumPolicyVersion() {
     const minPolicyVersion = Preferences.get(PREF_MINIMUM_POLICY_VERSION, 1);
 
     // First check if the current channel has a specific minimum policy version. If not,
     // use the general minimum policy version.
     let channel = "";
     try {
-      channel = UpdateChannel.get(false);
+      channel = UpdateUtils.getUpdateChannel(false);
     } catch(e) {
       this._log.error("minimumPolicyVersion - Unable to retrieve the current channel.");
       return minPolicyVersion;
     }
     const channelPref = PREF_MINIMUM_POLICY_VERSION + ".channel-" + channel;
     return Preferences.get(channelPref, minPolicyVersion);
   },
 
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -138,18 +138,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
                                   "resource://gre/modules/TelemetryStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
                                   "resource://gre/modules/TelemetryLog.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe",
                                   "resource://gre/modules/ThirdPartyCookieProbe.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
                                   "resource://gre/modules/UITelemetry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                   "resource://gre/modules/TelemetryEnvironment.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
 
 function generateUUID() {
   let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
   // strip {}
--- a/toolkit/components/telemetry/TelemetryStorage.jsm
+++ b/toolkit/components/telemetry/TelemetryStorage.jsm
@@ -370,59 +370,32 @@ this.TelemetryStorage = {
    * @param {Object} pingData The ping object.
    * @return {Promise} A promise resolved when the ping is saved to the pings directory.
    */
   addPendingPing: function(pingData) {
     return TelemetryStorageImpl.addPendingPing(pingData);
   },
 
   /**
-   * Add a ping from an existing file to the saved pings directory so that it gets saved
-   * and sent along with other pings.
-   * Note: that the original ping file will not be modified.
-   *
-   * @param {String} pingPath The path to the ping file that needs to be added to the
-   *                           saved pings directory.
-   * @return {Promise} A promise resolved when the ping is saved to the pings directory.
-   */
-  addPendingPingFromFile: function(pingPath) {
-    return TelemetryStorageImpl.addPendingPingFromFile(pingPath);
-  },
-
-  /**
    * Remove the file for a ping
    *
    * @param {object} ping The ping.
    * @returns {promise}
    */
   cleanupPingFile: function(ping) {
     return TelemetryStorageImpl.cleanupPingFile(ping);
   },
 
   /**
-   * Load the histograms from a file.
-   *
-   * @param {string} file The file to load.
-   * @returns {promise}
-   */
-  loadHistograms: function loadHistograms(file) {
-    return TelemetryStorageImpl.loadHistograms(file);
-  },
-
-  /**
    * The number of pending pings on disk.
    */
   get pendingPingCount() {
     return TelemetryStorageImpl.pendingPingCount;
   },
 
-  testLoadHistograms: function(file) {
-    return TelemetryStorageImpl.testLoadHistograms(file);
-  },
-
   /**
    * Loads a ping file.
    * @param {String} aFilePath The path of the ping file.
    * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
    *                           ping contains invalid data.
    */
   loadPingFile: Task.async(function* (aFilePath) {
     return TelemetryStorageImpl.loadPingFile(aFilePath);
@@ -1171,36 +1144,16 @@ var TelemetryStorageImpl = {
   savePing: Task.async(function*(ping, overwrite) {
     yield getPingDirectory();
     let file = pingFilePath(ping);
     yield this.savePingToFile(ping, file, overwrite);
     return file;
   }),
 
   /**
-   * Add a ping from an existing file to the saved pings directory so that it gets saved
-   * and sent along with other pings.
-   * Note: that the original ping file will not be modified.
-   *
-   * @param {String} pingPath The path to the ping file that needs to be added to the
-   *                           saved pings directory.
-   * @return {Promise} A promise resolved when the ping is saved to the pings directory.
-   */
-  addPendingPingFromFile: function(pingPath) {
-    // Pings in the saved ping directory need to have the ping id or slug (old format) as
-    // the file name. We load the ping content, check that it is valid, and use it to save
-    // the ping file with the correct file name.
-    return this.loadPingFile(pingPath).then(ping => {
-      // Since we read a ping successfully, update the related histogram.
-      Telemetry.getHistogramById("READ_SAVED_PING_SUCCESS").add(1);
-      return this.addPendingPing(ping);
-    });
-  },
-
-  /**
    * Add a ping to the saved pings directory so that it gets saved
    * and sent along with other pings.
    * Note: that the original ping file will not be modified.
    *
    * @param {Object} ping The ping object.
    * @return {Promise} A promise resolved when the ping is saved to the pings directory.
    */
   addPendingPing: function(ping) {
@@ -1390,47 +1343,20 @@ var TelemetryStorageImpl = {
       id: p[0],
       lastModificationDate: p[1].lastModificationDate,
     }];
 
     list.sort((a, b) => b.lastModificationDate - a.lastModificationDate);
     return list;
   },
 
-  /**
-   * Load the histograms from a file.
-   *
-   * Once loaded, the saved pings can be accessed (destructively only)
-   * through |popPendingPings|.
-   *
-   * @param {string} file The file to load.
-   * @returns {promise}
-   */
-  loadHistograms: Task.async(function*(file) {
-    let success = true;
-    try {
-      const ping = yield this.loadPingfile(file);
-      return ping;
-    } catch (ex) {
-      success = false;
-      yield OS.File.remove(file);
-    } finally {
-      const success_histogram = Telemetry.getHistogramById("READ_SAVED_PING_SUCCESS");
-      success_histogram.add(success);
-    }
-  }),
-
   get pendingPingCount() {
     return this._pendingPings.size;
   },
 
-  testLoadHistograms: function(file) {
-    return this.loadHistograms(file.path);
-  },
-
   /**
    * Loads a ping file.
    * @param {String} aFilePath The path of the ping file.
    * @param {Boolean} [aCompressed=false] If |true|, expects the file to be compressed using lz4.
    * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
    *                           ping contains invalid data.
    * @throws {PingReadError} There was an error while reading the ping file from the disk.
    * @throws {PingParseError} There was an error while parsing the JSON content of the ping file.
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -281,22 +281,20 @@ function checkPayload(payload, reason, s
   }
 
   const TELEMETRY_PING = "TELEMETRY_PING";
   const TELEMETRY_SUCCESS = "TELEMETRY_SUCCESS";
   const TELEMETRY_TEST_FLAG = "TELEMETRY_TEST_FLAG";
   const TELEMETRY_TEST_COUNT = "TELEMETRY_TEST_COUNT";
   const TELEMETRY_TEST_KEYED_FLAG = "TELEMETRY_TEST_KEYED_FLAG";
   const TELEMETRY_TEST_KEYED_COUNT = "TELEMETRY_TEST_KEYED_COUNT";
-  const READ_SAVED_PING_SUCCESS = "READ_SAVED_PING_SUCCESS";
 
   if (successfulPings > 0) {
     Assert.ok(TELEMETRY_PING in payload.histograms);
   }
-  Assert.ok(READ_SAVED_PING_SUCCESS in payload.histograms);
   Assert.ok(TELEMETRY_TEST_FLAG in payload.histograms);
   Assert.ok(TELEMETRY_TEST_COUNT in payload.histograms);
 
   let rh = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, []);
   for (let name of rh) {
     if (/SQLITE/.test(name) && name in payload.histograms) {
       let histogramName = ("STARTUP_" + name);
       Assert.ok(histogramName in payload.histograms, histogramName + " must be available.");
@@ -341,19 +339,16 @@ function checkPayload(payload, reason, s
       sum: successfulPings,
       sum_squares_lo: successfulPings,
       sum_squares_hi: 0
     };
     let tc = payload.histograms[TELEMETRY_SUCCESS];
     Assert.equal(uneval(tc), uneval(expected_tc));
   }
 
-  let h = payload.histograms[READ_SAVED_PING_SUCCESS];
-  Assert.equal(h.values[0], 1);
-
   // The ping should include data from memory reporters.  We can't check that
   // this data is correct, because we can't control the values returned by the
   // memory reporters.  But we can at least check that the data is there.
   //
   // It's important to check for the presence of reporters with a mix of units,
   // because TelemetryController has separate logic for each one.  But we can't
   // currently check UNITS_COUNT_CUMULATIVE or UNITS_PERCENTAGE because
   // Telemetry doesn't touch a memory reporter with these units that's
@@ -482,27 +477,16 @@ add_task(function* test_expiredHistogram
   let dummy = Telemetry.newHistogram(histogram_id, "30", Telemetry.HISTOGRAM_EXPONENTIAL, 1, 2, 3);
 
   dummy.add(1);
 
   do_check_eq(TelemetrySession.getPayload()["histograms"][histogram_id], undefined);
   do_check_eq(TelemetrySession.getPayload()["histograms"]["TELEMETRY_TEST_EXPIRED"], undefined);
 });
 
-// Checks that an invalid histogram file is deleted if TelemetryStorage fails to parse it.
-add_task(function* test_runInvalidJSON() {
-  let pingFile = getSavedPingFile("invalid-histograms.dat");
-
-  writeStringToFile(pingFile, "this.is.invalid.JSON");
-  do_check_true(pingFile.exists());
-
-  yield TelemetryStorage.testLoadHistograms(pingFile);
-  do_check_false(pingFile.exists());
-});
-
 // Sends a ping to a non existing server. If we remove this test, we won't get
 // all the histograms we need in the main ping.
 add_task(function* test_noServerPing() {
   yield sendPing();
   // We need two pings in order to make sure STARTUP_MEMORY_STORAGE_SQLIE histograms
   // are initialised. See bug 1131585.
   yield sendPing();
 });
--- a/toolkit/components/urlformatter/nsURLFormatter.js
+++ b/toolkit/components/urlformatter/nsURLFormatter.js
@@ -20,18 +20,18 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 const PREF_APP_DISTRIBUTION           = "distribution.id";
 const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
 
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 function nsURLFormatterService() {
   XPCOMUtils.defineLazyGetter(this, "appInfo", function UFS_appInfo() {
     return Cc["@mozilla.org/xre/app-info;1"].
            getService(Ci.nsIXULAppInfo).
            QueryInterface(Ci.nsIXULRuntime);
   });
 
@@ -106,17 +106,17 @@ nsURLFormatterService.prototype = {
     APPBUILDID:       function() this.appInfo.appBuildID,
     PLATFORMVERSION:  function() this.appInfo.platformVersion,
     PLATFORMBUILDID:  function() this.appInfo.platformBuildID,
     APP:              function() this.appInfo.name.toLowerCase().replace(/ /, ""),
     OS:               function() this.appInfo.OS,
     XPCOMABI:         function() this.ABI,
     BUILD_TARGET:     function() this.appInfo.OS + "_" + this.ABI,
     OS_VERSION:       function() this.OSVersion,
-    CHANNEL:          function() UpdateChannel.get(),
+    CHANNEL:          function() UpdateUtils.UpdateChannel,
     MOZILLA_API_KEY:   function() "@MOZ_MOZILLA_API_KEY@",
     GOOGLE_API_KEY:   function() "@MOZ_GOOGLE_API_KEY@",
     GOOGLE_OAUTH_API_CLIENTID:function() "@MOZ_GOOGLE_OAUTH_API_CLIENTID@",
     GOOGLE_OAUTH_API_KEY:     function() "@MOZ_GOOGLE_OAUTH_API_KEY@",
     BING_API_CLIENTID:function() "@MOZ_BING_API_CLIENTID@",
     BING_API_KEY:     function() "@MOZ_BING_API_KEY@",
     DISTRIBUTION:     function() this.distribution.id,
     DISTRIBUTION_VERSION: function() this.distribution.version
--- a/toolkit/components/viewsource/content/viewSourceUtils.js
+++ b/toolkit/components/viewsource/content/viewSourceUtils.js
@@ -14,16 +14,18 @@
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
   "resource://gre/modules/ViewSourceBrowser.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
   "resource://gre/modules/Deprecated.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
 
 var gViewSourceUtils = {
 
   mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
   mnsIWebProgress: Components.interfaces.nsIWebProgress,
   mnsIWebPageDescriptor: Components.interfaces.nsIWebPageDescriptor,
 
   /**
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -13,17 +13,16 @@ skip-if = e10s # Bug 921935 - focusmanag
 skip-if = e10s # Bug ?????? - intermittent crash of child process reported when run under e10s
 [browser_bug982298.js]
 skip-if = e10s # Bug 1064580
 [browser_contentTitle.js]
 [browser_default_image_filename.js]
 [browser_f7_caret_browsing.js]
 skip-if = e10s
 [browser_findbar.js]
-skip-if = e10s # Disabled for e10s: Bug ?????? - seems to be a timing issue with RemoteFinder.jsm messages coming later than the tests expect.
 [browser_input_file_tooltips.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: doc.createElement is not a function)
 [browser_isSynthetic.js]
 support-files =
   empty.png
 [browser_keyevents_during_autoscrolling.js]
 skip-if = e10s # Bug 921935 - focusmanager issues with e10s
 [browser_save_resend_postdata.js]
--- a/toolkit/content/tests/browser/browser_findbar.js
+++ b/toolkit/content/tests/browser/browser_findbar.js
@@ -1,90 +1,97 @@
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 Components.utils.import("resource://gre/modules/Timer.jsm", this);
 
+const TEST_PAGE_URI = "data:text/html;charset=utf-8,The letter s.";
+
 /**
  * Makes sure that the findbar hotkeys (' and /) event listeners
  * are added to the system event group and do not get blocked
  * by calling stopPropagation on a keypress event on a page.
  */
 add_task(function* test_hotkey_event_propagation() {
   info("Ensure hotkeys are not affected by stopPropagation.");
 
   // Opening new tab
-  let tab = yield promiseTestPageLoad();
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
   let browser = gBrowser.getBrowserForTab(tab);
   let findbar = gBrowser.getFindBar();
 
   // Pressing these keys open the findbar.
   const HOTKEYS = ["/", "'"];
 
   // Checking if findbar appears when any hotkey is pressed.
   for (let key of HOTKEYS) {
     is(findbar.hidden, true, "Findbar is hidden now.");
     gBrowser.selectedTab = tab;
     yield promiseFocus();
-    EventUtils.sendChar(key, browser.contentWindow);
+    yield BrowserTestUtils.sendChar(key, browser);
     is(findbar.hidden, false, "Findbar should not be hidden.");
     yield closeFindbarAndWait(findbar);
   }
 
   // Stop propagation for all keyboard events.
-  let window = browser.contentWindow;
-  let stopPropagation = function(e) { e.stopImmediatePropagation(); };
-  window.addEventListener("keydown", stopPropagation, true);
-  window.addEventListener("keypress", stopPropagation, true);
-  window.addEventListener("keyup", stopPropagation, true);
+  let frameScript = () => {
+    const stopPropagation = e => e.stopImmediatePropagation();
+    let window = content.document.defaultView;
+    window.removeEventListener("keydown", stopPropagation);
+    window.removeEventListener("keypress", stopPropagation);
+    window.removeEventListener("keyup", stopPropagation);
+  };
+
+  let mm = browser.messageManager;
+  mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false);
 
   // Checking if findbar still appears when any hotkey is pressed.
   for (let key of HOTKEYS) {
     is(findbar.hidden, true, "Findbar is hidden now.");
     gBrowser.selectedTab = tab;
     yield promiseFocus();
-    EventUtils.sendChar(key, browser.contentWindow);
+    yield BrowserTestUtils.sendChar(key, browser);
     is(findbar.hidden, false, "Findbar should not be hidden.");
     yield closeFindbarAndWait(findbar);
   }
 
   gBrowser.removeTab(tab);
 });
 
 add_task(function* test_not_found() {
   info("Check correct 'Phrase not found' on new tab");
 
-  let tab = yield promiseTestPageLoad();
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
 
   // Search for the first word.
   yield promiseFindFinished("--- THIS SHOULD NEVER MATCH ---", false);
   let findbar = gBrowser.getFindBar();
   is(findbar._findStatusDesc.textContent, findbar._notFoundStr,
      "Findbar status text should be 'Phrase not found'");
 
   gBrowser.removeTab(tab);
 });
 
 add_task(function* test_found() {
-  let tab = yield promiseTestPageLoad();
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
 
   // Search for a string that WILL be found, with 'Highlight All' on
   yield promiseFindFinished("S", true);
   ok(!gBrowser.getFindBar()._findStatusDesc.textContent,
      "Findbar status should be empty");
 
   gBrowser.removeTab(tab);
 });
 
 // Setting first findbar to case-sensitive mode should not affect
 // new tab find bar.
 add_task(function* test_tabwise_case_sensitive() {
-  let tab1 = yield promiseTestPageLoad();
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
   let findbar1 = gBrowser.getFindBar();
 
-  let tab2 = yield promiseTestPageLoad();
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
   let findbar2 = gBrowser.getFindBar();
 
   // Toggle case sensitivity for first findbar
   findbar1.getElement("find-case-sensitive").click();
 
   gBrowser.selectedTab = tab1;
 
   // Not found for first tab.
@@ -97,47 +104,67 @@ add_task(function* test_tabwise_case_sen
   // But it didn't affect the second findbar.
   yield promiseFindFinished("S", true);
   ok(!findbar2._findStatusDesc.textContent, "Findbar status should be empty");
 
   gBrowser.removeTab(tab1);
   gBrowser.removeTab(tab2);
 });
 
-function promiseTestPageLoad() {
-  let deferred = Promise.defer();
+/**
+ * Navigating from a web page (for example mozilla.org) to an internal page
+ * (like about:addons) might trigger a change of browser's remoteness.
+ * 'Remoteness change' means that rendering page content moves from child
+ * process into the parent process or the other way around.
+ * This test ensures that findbar properly handles such a change.
+ */
+add_task(function * test_reinitialization_at_remoteness_change() {
+  info("Ensure findbar re-initialization at remoteness change.");
 
-  let tab = gBrowser.selectedTab = gBrowser.addTab("data:text/html;charset=utf-8,The letter s.");
-  let browser = gBrowser.selectedBrowser;
-  browser.addEventListener("load", function listener() {
-    if (browser.currentURI.spec == "about:blank")
-      return;
-    info("Page loaded: " + browser.currentURI.spec);
-    browser.removeEventListener("load", listener, true);
+  // Load a remote page and trigger findbar construction.
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
+  let browser = gBrowser.getBrowserForTab(tab);
+  let findbar = gBrowser.getFindBar();
+
+  // Findbar should operate normally.
+  yield promiseFindFinished("s", false);
+  ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty");
 
-    deferred.resolve(tab);
-  }, true);
+  gBrowser.updateBrowserRemoteness(browser, false);
 
-  return deferred.promise;
-}
+  // Findbar should keep operating normally.
+  yield promiseFindFinished("s", false);
+  ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
 
 function promiseFindFinished(searchText, highlightOn) {
   let deferred = Promise.defer();
 
   let findbar = gBrowser.getFindBar();
   findbar.startFind(findbar.FIND_NORMAL);
   let highlightElement = findbar.getElement("highlight");
   if (highlightElement.checked != highlightOn)
     highlightElement.click();
   executeSoon(() => {
     findbar._findField.value = searchText;
 
     let resultListener;
+    // When highlighting is on the finder sends a second "FOUND" message after
+    // the search wraps. This causes timing problems with e10s. waitMore
+    // forces foundOrTimeout wait for the second "FOUND" message before
+    // resolving the promise.
+    let waitMore = highlightOn;
     let findTimeout = setTimeout(() => foundOrTimedout(null), 2000);
     let foundOrTimedout = function(aData) {
+      if (aData !== null && waitMore) {
+        waitMore = false;
+        return;
+      }
       if (aData === null)
         info("Result listener not called, timeout reached.");
       clearTimeout(findTimeout);
       findbar.browser.finder.removeResultListener(resultListener);
       deferred.resolve();
     }
 
     resultListener = {
deleted file mode 100644
--- a/toolkit/content/tests/unit/test_updateChannelModule.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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/. */
-
-Components.utils.import("resource://gre/modules/Preferences.jsm");
-Components.utils.import("resource://gre/modules/UpdateChannel.jsm");
-
-const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
-const TEST_CHANNEL            = "TestChannel";
-const PREF_PARTNER_A          = "app.partner.test_partner_a";
-const TEST_PARTNER_A          = "TestPartnerA";
-const PREF_PARTNER_B          = "app.partner.test_partner_b";
-const TEST_PARTNER_B          = "TestPartnerB";
-
-function test_get() {
-  let defaultPrefs = new Preferences({ defaultBranch: true });
-  let currentChannel = defaultPrefs.get(PREF_APP_UPDATE_CHANNEL);
-
-  do_check_eq(UpdateChannel.get(), currentChannel);
-  do_check_eq(UpdateChannel.get(false), currentChannel);
-
-  defaultPrefs.set(PREF_APP_UPDATE_CHANNEL, TEST_CHANNEL);
-  do_check_eq(UpdateChannel.get(), TEST_CHANNEL);
-  do_check_eq(UpdateChannel.get(false), TEST_CHANNEL);
-
-  defaultPrefs.set(PREF_PARTNER_A, TEST_PARTNER_A);
-  defaultPrefs.set(PREF_PARTNER_B, TEST_PARTNER_B);
-  do_check_eq(UpdateChannel.get(),
-              TEST_CHANNEL + "-cck-" + TEST_PARTNER_A + "-" + TEST_PARTNER_B);
-  do_check_eq(UpdateChannel.get(false), TEST_CHANNEL);
-}
-
-function run_test() {
-  test_get();
-}
--- a/toolkit/content/tests/unit/xpcshell.ini
+++ b/toolkit/content/tests/unit/xpcshell.ini
@@ -1,7 +1,6 @@
 [DEFAULT]
 head = 
 tail = 
 skip-if = toolkit == 'gonk'
 
 [test_contentAreaUtils.js]
-[test_updateChannelModule.js]
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -368,22 +368,38 @@
         this._findSelection = this.nsISelectionController.SELECTION_FIND;
 
         this._findResetTimeout = -1;
 
         // Make sure the FAYT keypress listener is attached by initializing the
         // browser property
         if (this.getAttribute("browserid"))
           setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
+
+        if (typeof gBrowser !== 'undefined')
+          gBrowser.tabContainer.addEventListener("TabRemotenessChange", this);
       ]]></constructor>
 
       <destructor><![CDATA[
         this.destroy();
       ]]></destructor>
 
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body><![CDATA[
+          switch(aEvent.type) {
+            case "onRemotenessChange":
+              // Reinitializing browser to re-attach listeners.
+              this.browser._lastSearchString = this._findField.value;
+              this.browser = this.browser;
+              break;
+          }
+        ]]></body>
+      </method>
+
       <!-- This is necessary because the destructor isn't called when
            we are removed from a document that is not destroyed. This
            needs to be explicitly called in this case -->
       <method name="destroy">
         <body><![CDATA[
           if (this._destroyed)
             return;
           this._destroyed = true;
@@ -397,16 +413,19 @@
                                  this._observer);
           prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
                                  this._observer);
           prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
                                  this._observer);
 
           // Clear all timers that might still be running.
           this._cancelTimers();
+
+          if (typeof gBrowser !== 'undefined')
+            gBrowser.tabContainer.removeEventListener("TabRemotenessChange", this);
         ]]></body>
       </method>
 
       <method name="_cancelTimers">
         <body><![CDATA[
           if (this._flashFindBarTimeout) {
             clearInterval(this._flashFindBarTimeout);
             this._flashFindBarTimeout = null;
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -208,18 +208,20 @@ this.AppConstants = Object.freeze({
   DLL_PREFIX: "@DLL_PREFIX@",
   DLL_SUFFIX: "@DLL_SUFFIX@",
 
   MOZ_APP_NAME: "@MOZ_APP_NAME@",
   MOZ_APP_VERSION: "@MOZ_APP_VERSION@",
   MOZ_APP_VERSION_DISPLAY: "@MOZ_APP_VERSION_DISPLAY@",
   MOZ_BUILD_APP: "@MOZ_BUILD_APP@",
   MOZ_UPDATE_CHANNEL: "@MOZ_UPDATE_CHANNEL@",
+  INSTALL_LOCALE: "@AB_CD@",
   MOZ_WIDGET_TOOLKIT: "@MOZ_WIDGET_TOOLKIT@",
   ANDROID_PACKAGE_NAME: "@ANDROID_PACKAGE_NAME@",
+
   MOZ_ANDROID_APZ:
 #ifdef MOZ_ANDROID_APZ
     true,
 #else
     false,
 #endif
   DEBUG_JS_MODULES: "@DEBUG_JS_MODULES@"
 });
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -3,20 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
   Components;
-// Chunk size for the incremental downloader
-const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
-// Incremental downloader interval
-const DOWNLOAD_INTERVAL  = 0;
 // 1 day default
 const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;
 
 var GMPInstallFailureReason = {
   GMP_INVALID: 1,
   GMP_HIDDEN: 2,
   GMP_DISABLED: 3,
   GMP_UPDATE_DISABLED: 4,
@@ -25,183 +21,38 @@ var GMPInstallFailureReason = {
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/ctypes.jsm");
 Cu.import("resource://gre/modules/GMPUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
 
 this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader",
                          "GMPAddon"];
 
-var gLocale = null;
-
 // Shared code for suppressing bad cert dialogs
 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
   let temp = { };
   Cu.import("resource://gre/modules/CertUtils.jsm", temp);
   return temp;
 });
 
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
-
-/**
- * Number of milliseconds after which we need to cancel `checkForAddons`.
- *
- * Bug 1087674 suggests that the XHR we use in `checkForAddons` may
- * never terminate in presence of network nuisances (e.g. strange
- * antivirus behavior). This timeout is a defensive measure to ensure
- * that we fail cleanly in such case.
- */
-const CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS = 20000;
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 function getScopedLogger(prefix) {
   // `PARENT_LOGGER_ID.` being passed here effectively links this logger
   // to the parentLogger.
   return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " ");
 }
 
-// This is copied directly from nsUpdateService.js
-// It is used for calculating the URL string w/ var replacement.
-// TODO: refactor this out somewhere else
-XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
-  let osVersion;
-  try {
-    osVersion = Services.sysinfo.getProperty("name") + " " +
-                Services.sysinfo.getProperty("version");
-  }
-  catch (e) {
-    LOG("gOSVersion - OS Version unknown: updates are not possible.");
-  }
-
-  if (osVersion) {
-    if (AppConstants.platform == "win") {
-      const BYTE = ctypes.uint8_t;
-      const WORD = ctypes.uint16_t;
-      const DWORD = ctypes.uint32_t;
-      const WCHAR = ctypes.char16_t;
-      const BOOL = ctypes.int;
-
-      // This structure is described at:
-      // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
-      const SZCSDVERSIONLENGTH = 128;
-      const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
-          [
-          {dwOSVersionInfoSize: DWORD},
-          {dwMajorVersion: DWORD},
-          {dwMinorVersion: DWORD},
-          {dwBuildNumber: DWORD},
-          {dwPlatformId: DWORD},
-          {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
-          {wServicePackMajor: WORD},
-          {wServicePackMinor: WORD},
-          {wSuiteMask: WORD},
-          {wProductType: BYTE},
-          {wReserved: BYTE}
-          ]);
-
-      // This structure is described at:
-      // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
-      const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
-          [
-          {wProcessorArchitecture: WORD},
-          {wReserved: WORD},
-          {dwPageSize: DWORD},
-          {lpMinimumApplicationAddress: ctypes.voidptr_t},
-          {lpMaximumApplicationAddress: ctypes.voidptr_t},
-          {dwActiveProcessorMask: DWORD.ptr},
-          {dwNumberOfProcessors: DWORD},
-          {dwProcessorType: DWORD},
-          {dwAllocationGranularity: DWORD},
-          {wProcessorLevel: WORD},
-          {wProcessorRevision: WORD}
-          ]);
-
-      let kernel32 = false;
-      try {
-        kernel32 = ctypes.open("Kernel32");
-      } catch (e) {
-        LOG("gOSVersion - Unable to open kernel32! " + e);
-        osVersion += ".unknown (unknown)";
-      }
-
-      if(kernel32) {
-        try {
-          // Get Service pack info
-          try {
-            let GetVersionEx = kernel32.declare("GetVersionExW",
-                                                ctypes.default_abi,
-                                                BOOL,
-                                                OSVERSIONINFOEXW.ptr);
-            let winVer = OSVERSIONINFOEXW();
-            winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
-
-            if(0 !== GetVersionEx(winVer.address())) {
-              osVersion += "." + winVer.wServicePackMajor
-                        +  "." + winVer.wServicePackMinor;
-            } else {
-              LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
-              osVersion += ".unknown";
-            }
-          } catch (e) {
-            LOG("gOSVersion - error getting service pack information. Exception: " + e);
-            osVersion += ".unknown";
-          }
-
-          // Get processor architecture
-          let arch = "unknown";
-          try {
-            let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
-                                                       ctypes.default_abi,
-                                                       ctypes.void_t,
-                                                       SYSTEM_INFO.ptr);
-            let sysInfo = SYSTEM_INFO();
-            // Default to unknown
-            sysInfo.wProcessorArchitecture = 0xffff;
-
-            GetNativeSystemInfo(sysInfo.address());
-            switch(sysInfo.wProcessorArchitecture) {
-              case 9:
-                arch = "x64";
-                break;
-              case 6:
-                arch = "IA64";
-                break;
-              case 0:
-                arch = "x86";
-                break;
-            }
-          } catch (e) {
-            LOG("gOSVersion - error getting processor architecture.  Exception: " + e);
-          } finally {
-            osVersion += " (" + arch + ")";
-          }
-        } finally {
-          kernel32.close();
-        }
-      }
-    }
-
-    try {
-      osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
-    }
-    catch (e) {
-      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
-    }
-    osVersion = encodeURIComponent(osVersion);
-  }
-  return osVersion;
-});
-
 /**
  * Provides an easy API for downloading and installing GMP Addons
  */
 function GMPInstallManager() {
 }
 /**
  * Temp file name used for downloading
  */
@@ -216,34 +67,18 @@ GMPInstallManager.prototype = {
     let url = GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE);
     if (url) {
       log.info("Using override url: " + url);
     } else {
       url = GMPPrefs.get(GMPPrefs.KEY_URL);
       log.info("Using url: " + url);
     }
 
-    url =
-      url.replace(/%PRODUCT%/g, Services.appinfo.name)
-         .replace(/%VERSION%/g, Services.appinfo.version)
-         .replace(/%BUILD_ID%/g, Services.appinfo.appBuildID)
-         .replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + GMPUtils.ABI())
-         .replace(/%OS_VERSION%/g, gOSVersion);
-    if (/%LOCALE%/.test(url)) {
-      // TODO: Get the real local, does it actually matter for GMP plugins?
-      url = url.replace(/%LOCALE%/g, "en-US");
-    }
-    url =
-      url.replace(/%CHANNEL%/g, UpdateChannel.get())
-         .replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion)
-         .replace(/%DISTRIBUTION%/g,
-                  GMPPrefs.get(GMPPrefs.KEY_APP_DISTRIBUTION))
-         .replace(/%DISTRIBUTION_VERSION%/g,
-                  GMPPrefs.get(GMPPrefs.KEY_APP_DISTRIBUTION_VERSION))
-         .replace(/\+/g, "%2B");
+    url = UpdateUtils.formatUpdateURL(url);
+
     log.info("Using url (with replacement): " + url);
     return url;
   },
   /**
    * Performs an addon check.
    * @return a promise which will be resolved or rejected.
    *         The promise is resolved with an array of GMPAddons
    *         The promise is rejected with an object with properties:
@@ -255,48 +90,37 @@ GMPInstallManager.prototype = {
     let log = getScopedLogger("GMPInstallManager.checkForAddons");
     if (this._deferred) {
         log.error("checkForAddons already called");
         return Promise.reject({type: "alreadycalled"});
     }
     this._deferred = Promise.defer();
     let url = this._getURL();
 
-    this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
-                    createInstance(Ci.nsISupports);
-    // This is here to let unit test code override XHR
-    if (this._request.wrappedJSObject) {
-      this._request = this._request.wrappedJSObject;
+    let allowNonBuiltIn = true;
+    let certs = null;
+    if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE)) {
+      allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN, true);
+      if (GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
+        certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
+      }
     }
-    this._request.open("GET", url, true);
-    let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true);
-    this._request.channel.notificationCallbacks =
-      new gCertUtils.BadCertHandler(allowNonBuiltIn);
-    // Prevent the request from reading from the cache.
-    this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
-    // Prevent the request from writing to the cache.
-    this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
-    this._request.overrideMimeType("text/xml");
-    // The Cache-Control header is only interpreted by proxies and the
-    // final destination. It does not help if a resource is already
-    // cached locally.
-    this._request.setRequestHeader("Cache-Control", "no-cache");
-    // HTTP/1.0 servers might not implement Cache-Control and
-    // might only implement Pragma: no-cache
-    this._request.setRequestHeader("Pragma", "no-cache");
-
-    this._request.timeout = CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS;
-    this._request.addEventListener("error", event => this.onFailXML("onErrorXML", event), false);
-    this._request.addEventListener("abort", event => this.onFailXML("onAbortXML", event), false);
-    this._request.addEventListener("timeout", event => this.onFailXML("onTimeoutXML", event), false);
-    this._request.addEventListener("load", event => this.onLoadXML(event), false);
-
-    log.info("sending request to: " + url);
-    this._request.send(null);
+    ProductAddonChecker.getProductAddonList(url, allowNonBuiltIn, certs).then((addons) => {
+      if (!addons) {
+        this._deferred.resolve([]);
+      }
+      else {
+        this._deferred.resolve([for (a of addons) new GMPAddon(a)]);
+      }
+      delete this._deferred;
+    }, (ex) => {
+      this._deferred.reject(ex);
+      delete this._deferred;
+    });
 
     return this._deferred.promise;
   },
   /**
    * Installs the specified addon and calls a callback when done.
    * @param gmpAddon The GMPAddon object to install
    * @return a promise which will be resolved or rejected
    *         The promise will resolve with an array of paths that were extracted
@@ -488,192 +312,33 @@ GMPInstallManager.prototype = {
     log.info("Done cleanup");
   },
 
   /**
    * If set to true, specifies to leave the temporary downloaded zip file.
    * This is useful for tests.
    */
   overrideLeaveDownloadedZip: false,
-
-  /**
-   * The XMLHttpRequest succeeded and the document was loaded.
-   * @param event The nsIDOMEvent for the load
-  */
-  onLoadXML: function(event) {
-    let log = getScopedLogger("GMPInstallManager.onLoadXML");
-    try {
-      log.info("request completed downloading document");
-      let certs = null;
-      if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
-          GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
-        certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
-      }
-
-      let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
-                                          true);
-      log.info("allowNonBuiltIn: " + allowNonBuiltIn);
-
-      gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
-
-      this.parseResponseXML();
-    } catch (ex) {
-      log.error("could not load xml: " + ex);
-      this._deferred.reject({
-        target: event.target,
-        status: this._getChannelStatus(event.target),
-        message: "" + ex,
-      });
-      delete this._deferred;
-    }
-  },
-
-  /**
-   * Returns the status code for the XMLHttpRequest
-   */
-  _getChannelStatus: function(request) {
-    let log = getScopedLogger("GMPInstallManager._getChannelStatus");
-    let status = null;
-    try {
-      status = request.status;
-      log.info("request.status is: " + request.status);
-    }
-    catch (e) {
-    }
-
-    if (status == null) {
-      status = request.channel.QueryInterface(Ci.nsIRequest).status;
-    }
-    return status;
-  },
-
-  /**
-   * There was an error of some kind during the XMLHttpRequest.  This
-   * error may have been caused by external factors (e.g. network
-   * issues) or internally (by a timeout).
-   *
-   * @param event The nsIDOMEvent for the error
-   */
-  onFailXML: function(failure, event) {
-    let log = getScopedLogger("GMPInstallManager.onFailXML " + failure);
-    let request = event.target;
-    let status = this._getChannelStatus(request);
-    let message = "request.status: " + status +  " (" + event.type + ")";
-    log.warn(message);
-    this._deferred.reject({
-      target: request,
-      status: status,
-      message: message
-    });
-    delete this._deferred;
-  },
-
-  /**
-   * Returns an array of GMPAddon objects discovered by the update check.
-   * Or returns an empty array if there were any problems with parsing.
-   * If there's an error, it will be logged if logging is enabled.
-   */
-  parseResponseXML: function() {
-    try {
-      let log = getScopedLogger("GMPInstallManager.parseResponseXML");
-      let updatesElement = this._request.responseXML.documentElement;
-      if (!updatesElement) {
-        let message = "empty updates document";
-        log.warn(message);
-        this._deferred.reject({
-          target: this._request,
-          message: message
-        });
-        delete this._deferred;
-        return;
-      }
-
-      if (updatesElement.nodeName != "updates") {
-        let message = "got node name: " + updatesElement.nodeName +
-          ", expected: updates";
-        log.warn(message);
-        this._deferred.reject({
-          target: this._request,
-          message: message
-        });
-        delete this._deferred;
-        return;
-      }
-
-      const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
-      let gmpResults = [];
-      for (let i = 0; i < updatesElement.childNodes.length; ++i) {
-        let updatesChildElement = updatesElement.childNodes.item(i);
-        if (updatesChildElement.nodeType != ELEMENT_NODE) {
-          continue;
-        }
-        if (updatesChildElement.localName == "addons") {
-          gmpResults = GMPAddon.parseGMPAddonsNode(updatesChildElement);
-        }
-      }
-      this._deferred.resolve(gmpResults);
-      delete this._deferred;
-    } catch (e) {
-      this._deferred.reject({
-        target: this._request,
-        message: e
-      });
-      delete this._deferred;
-    }
-  },
 };
 
 /**
  * Used to construct a single GMP addon
  * GMPAddon objects are returns from GMPInstallManager.checkForAddons
  * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon
  *
- * @param gmpAddon The AUS response XML's DOM element `addon`
+ * @param addon The ProductAddonChecker `addon` object
  */
-function GMPAddon(gmpAddon) {
+function GMPAddon(addon) {
   let log = getScopedLogger("GMPAddon.constructor");
-  gmpAddon.QueryInterface(Ci.nsIDOMElement);
-  ["id", "URL", "hashFunction",
-   "hashValue", "version", "size"].forEach(name => {
-    if (gmpAddon.hasAttribute(name)) {
-      this[name] = gmpAddon.getAttribute(name);
-    }
-  });
-  this.size = Number(this.size) || undefined;
+  for (let name of Object.keys(addon)) {
+    this[name] = addon[name];
+  }
   log.info ("Created new addon: " + this.toString());
 }
-/**
- * Parses an XML GMP addons node from AUS into an array
- * @param addonsElement An nsIDOMElement compatible node with XML from AUS
- * @return An array of GMPAddon results
- */
-GMPAddon.parseGMPAddonsNode = function(addonsElement) {
-  let log = getScopedLogger("GMPAddon.parseGMPAddonsNode");
-  let gmpResults = [];
-  if (addonsElement.localName !== "addons") {
-    return;
-  }
 
-  addonsElement.QueryInterface(Ci.nsIDOMElement);
-  let addonCount = addonsElement.childNodes.length;
-  for (let i = 0; i < addonCount; ++i) {
-    let addonElement = addonsElement.childNodes.item(i);
-    if (addonElement.localName !== "addon") {
-      continue;
-    }
-    addonElement.QueryInterface(Ci.nsIDOMElement);
-    try {
-      gmpResults.push(new GMPAddon(addonElement));
-    } catch (e) {
-      log.warn("invalid addon: " + e);
-      continue;
-    }
-  }
-  return gmpResults;
-};
 GMPAddon.prototype = {
   /**
    * Returns a string representation of the addon
    */
   toString: function() {
     return this.id + " (" +
            "isValid: " + this.isValid +
            ", isInstalled: " + this.isInstalled +
@@ -794,192 +459,56 @@ GMPExtractor.prototype = {
  * Constructs an object which downloads and initiates an install of
  * the specified GMPAddon object.
  * @param gmpAddon The addon to install.
  */
 function GMPDownloader(gmpAddon)
 {
   this._gmpAddon = gmpAddon;
 }
-/**
- * Computes the file hash of fileToHash with the specified hash function
- * @param hashFunctionName A hash function name such as sha512
- * @param fileToHash An nsIFile to hash
- * @return a promise which resolve to a digest in binary hex format
- */
-GMPDownloader.computeHash = function(hashFunctionName, fileToHash) {
-  let log = getScopedLogger("GMPDownloader.computeHash");
-  let digest;
-  let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
-                   createInstance(Ci.nsIFileInputStream);
-  fileStream.init(fileToHash, FileUtils.MODE_RDONLY,
-                  FileUtils.PERMS_FILE, 0);
-  try {
-    let hash = Cc["@mozilla.org/security/hash;1"].
-               createInstance(Ci.nsICryptoHash);
-    let hashFunction =
-      Ci.nsICryptoHash[hashFunctionName.toUpperCase()];
-    if (!hashFunction) {
-      log.error("could not get hash function");
-      return Promise.reject();
-    }
-    hash.init(hashFunction);
-    hash.updateFromStream(fileStream, -1);
-    digest = binaryToHex(hash.finish(false));
-  } catch (e) {
-    log.warn("failed to compute hash: " + e);
-    digest = "";
-  }
-  fileStream.close();
-  return Promise.resolve(digest);
-},
+
 GMPDownloader.prototype = {
   /**
    * Starts the download process for an addon.
    * @return a promise which will be resolved or rejected
    *         See GMPInstallManager.installAddon for resolve/rejected info
    */
   start: function() {
-    let log = getScopedLogger("GMPDownloader.start");
-    this._deferred = Promise.defer();
-    if (!this._gmpAddon.isValid) {
+    let log = getScopedLogger("GMPDownloader");
+    let gmpAddon = this._gmpAddon;
+
+    if (!gmpAddon.isValid) {
       log.info("gmpAddon is not valid, will not continue");
       return Promise.reject({
         target: this,
         status: status,
         type: "downloaderr"
       });
     }
 
-    let uri = Services.io.newURI(this._gmpAddon.URL, null, null);
-    this._request = Cc["@mozilla.org/network/incremental-download;1"].
-                    createInstance(Ci.nsIIncrementalDownload);
-    let gmpFile = FileUtils.getFile("TmpD", [this._gmpAddon.id + ".zip"]);
-    if (gmpFile.exists()) {
-      gmpFile.remove(false);
-    }
-
-    log.info("downloading from " + uri.spec + " to " + gmpFile.path);
-    this._request.init(uri, gmpFile, DOWNLOAD_CHUNK_BYTES_SIZE,
-                       DOWNLOAD_INTERVAL);
-    this._request.start(this, null);
-    return this._deferred.promise;
-  },
-  // For nsIRequestObserver
-  onStartRequest: function(request, context) {
-  },
-  // For nsIRequestObserver
-  // Called when the GMP addon zip file is downloaded
-  onStopRequest: function(request, context, status) {
-    let log = getScopedLogger("GMPDownloader.onStopRequest");
-    log.info("onStopRequest called");
-    if (!Components.isSuccessCode(status)) {
-      log.info("status failed: " + status);
-      this._deferred.reject({
-        target: this,
-        status: status,
-        type: "downloaderr"
-      });
-      return;
-    }
-
-    let promise = this._verifyDownload();
-    promise.then(() => {
-      log.info("GMP file is ready to unzip");
-      let destination = this._request.destination;
-
-      let zipPath = destination.path;
-      let gmpAddon = this._gmpAddon;
-      let installToDirPath = Cc["@mozilla.org/file/local;1"].
-                          createInstance(Ci.nsIFile);
+    return ProductAddonChecker.downloadAddon(gmpAddon).then((zipPath) => {
       let path = OS.Path.join(OS.Constants.Path.profileDir,
                               gmpAddon.id,
                               gmpAddon.version);
-      installToDirPath.initWithPath(path);
-      log.info("install to directory path: " + installToDirPath.path);
-      let gmpInstaller = new GMPExtractor(zipPath, installToDirPath.path);
+      log.info("install to directory path: " + path);
+      let gmpInstaller = new GMPExtractor(zipPath, path);
       let installPromise = gmpInstaller.install();
-      installPromise.then(extractedPaths => {
+      return installPromise.then(extractedPaths => {
         // Success, set the prefs
         let now = Math.round(Date.now() / 1000);
         GMPPrefs.set(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
         // Reset the trial create pref, so that Gecko knows to do a test
         // run before reporting that the GMP works to content.
         GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_TRIAL_CREATE, gmpAddon.version,
                        gmpAddon.id);
         // Remember our ABI, so that if the profile is migrated to another
         // platform or from 32 -> 64 bit, we notice and don't try to load the
         // unexecutable plugin library.
-        GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ABI, GMPUtils.ABI(), gmpAddon.id);
+        GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI, gmpAddon.id);
         // Setting the version pref signals installation completion to consumers,
         // if you need to set other prefs etc. do it before this.
         GMPPrefs.set(GMPPrefs.KEY_PLUGIN_VERSION, gmpAddon.version,
                      gmpAddon.id);
-        this._deferred.resolve(extractedPaths);
-      }, err => {
-        this._deferred.reject(err);
-      });
-    }, err => {
-      log.warn("verifyDownload check failed");
-      this._deferred.reject({
-        target: this,
-        status: 200,
-        type: "verifyerr"
+        return extractedPaths;
       });
     });
   },
-  /**
-   * Verifies that the downloaded zip file's hash matches the GMPAddon hash.
-   * @return a promise which resolves if the download verifies
-   */
-  _verifyDownload: function() {
-    let verifyDownloadDeferred = Promise.defer();
-    let log = getScopedLogger("GMPDownloader._verifyDownload");
-    log.info("_verifyDownload called");
-    if (!this._request) {
-      return Promise.reject();
-    }
-
-    let destination = this._request.destination;
-    log.info("for path: " + destination.path);
-
-    // Ensure that the file size matches the expected file size.
-    if (this._gmpAddon.size !== undefined &&
-        destination.fileSize != this._gmpAddon.size) {
-      log.warn("Downloader:_verifyDownload downloaded size " +
-               destination.fileSize + " != expected size " +
-               this._gmpAddon.size + ".");
-      return Promise.reject();
-    }
-
-    let promise = GMPDownloader.computeHash(this._gmpAddon.hashFunction, destination);
-    promise.then(digest => {
-        let expectedDigest = this._gmpAddon.hashValue.toLowerCase();
-        if (digest !== expectedDigest) {
-          log.warn("hashes do not match! Got: `" +
-                   digest + "`, expected: `" + expectedDigest +  "`");
-          this._deferred.reject();
-          return;
-        }
-
-        log.info("hashes match!");
-        verifyDownloadDeferred.resolve();
-    }, err => {
-        verifyDownloadDeferred.reject();
-    });
-    return verifyDownloadDeferred.promise;
-  },
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver])
 };
-
-/**
- * Convert a string containing binary values to hex.
- */
-function binaryToHex(input) {
-  let result = "";
-  for (let i = 0; i < input.length; ++i) {
-    let hex = input.charCodeAt(i).toString(16);
-    if (hex.length == 1)
-      hex = "0" + hex;
-    result += hex;
-  }
-  return result;
-}
--- a/toolkit/modules/GMPUtils.jsm
+++ b/toolkit/modules/GMPUtils.jsm
@@ -121,37 +121,16 @@ this.GMPUtils = {
     }
     this.reportedKeys.push(key);
 
     let hist = Services.telemetry.getHistogramById(key);
     if (hist) {
       hist.add(value);
     }
   },
-
-  ABI: function() {
-    // This is copied directly from nsUpdateService.js
-    let abi = null;
-    try {
-      abi = Services.appinfo.XPCOMABI;
-    }
-    catch (e) {
-      return "unknown";
-    }
-    if (AppConstants.platform == "macosx") {
-      // Mac universal build should report a different ABI than either macppc
-      // or mactel.
-      let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
-                     getService(Ci.nsIMacUtils);
-
-      if (macutils.isUniversalBinary)
-        abi += "-u-" + macutils.architecturesInBinary;
-    }
-    return abi;
-  }
 };
 
 /**
  * Manages preferences for GMP addons
  */
 this.GMPPrefs = {
   KEY_EME_ENABLED:              "media.eme.enabled",
   KEY_PLUGIN_ENABLED:           "media.{0}.enabled",
--- a/toolkit/modules/Troubleshoot.jsm
+++ b/toolkit/modules/Troubleshoot.jsm
@@ -176,17 +176,17 @@ var dataProviders = {
       buildID: Services.appinfo.appBuildID,
       userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].
                  getService(Ci.nsIHttpProtocolHandler).
                  userAgent,
       safeMode: Services.appinfo.inSafeMode,
     };
 
     if (AppConstants.MOZ_UPDATER)
-      data.updateChannel = Cu.import("resource://gre/modules/UpdateChannel.jsm", {}).UpdateChannel.get();
+      data.updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
 
     try {
       data.vendor = Services.prefs.getCharPref("app.support.vendor");
     }
     catch (e) {}
     let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
                        getService(Ci.nsIURLFormatter);
     try {
deleted file mode 100644
--- a/toolkit/modules/UpdateChannel.jsm
+++ /dev/null
@@ -1,46 +0,0 @@
-/* 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/. */
-
-this.EXPORTED_SYMBOLS = ["UpdateChannel"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-this.UpdateChannel = {
-  /**
-   * Read the update channel from defaults only.  We do this to ensure that
-   * the channel is tightly coupled with the application and does not apply
-   * to other instances of the application that may use the same profile.
-   *
-   * @param [optional] aIncludePartners
-   *        Whether or not to include the partner bits. Default: true.
-   */
-  get: function UpdateChannel_get(aIncludePartners = true) {
-    let channel = AppConstants.MOZ_UPDATE_CHANNEL;
-    let defaults = Services.prefs.getDefaultBranch(null);
-    try {
-      channel = defaults.getCharPref("app.update.channel");
-    } catch (e) {
-      // use default value when pref not found
-    }
-
-    if (aIncludePartners) {
-      try {
-        let partners = Services.prefs.getChildList("app.partner.").sort();
-        if (partners.length) {
-          channel += "-cck";
-          partners.forEach(function (prefName) {
-            channel += "-" + Services.prefs.getCharPref(prefName);
-          });
-        }
-      } catch (e) {
-        Cu.reportError(e);
-      }
-    }
-
-    return channel;
-  }
-};
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/UpdateUtils.jsm
@@ -0,0 +1,347 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["UpdateUtils"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+
+const FILE_UPDATE_LOCALE                  = "update.locale";
+const PREF_APP_DISTRIBUTION               = "distribution.id";
+const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
+const PREF_APP_B2G_VERSION                = "b2g.version";
+const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";
+const PREF_APP_UPDATE_IMEI_HASH           = "app.update.imei_hash";
+
+
+this.UpdateUtils = {
+  /**
+   * Read the update channel from defaults only.  We do this to ensure that
+   * the channel is tightly coupled with the application and does not apply
+   * to other instances of the application that may use the same profile.
+   *
+   * @param [optional] aIncludePartners
+   *        Whether or not to include the partner bits. Default: true.
+   */
+  getUpdateChannel(aIncludePartners = true) {
+    let channel = AppConstants.MOZ_UPDATE_CHANNEL;
+    let defaults = Services.prefs.getDefaultBranch(null);
+    try {
+      channel = defaults.getCharPref("app.update.channel");
+    } catch (e) {
+      // use default value when pref not found
+    }
+
+    if (aIncludePartners) {
+      try {
+        let partners = Services.prefs.getChildList("app.partner.").sort();
+        if (partners.length) {
+          channel += "-cck";
+          partners.forEach(function (prefName) {
+            channel += "-" + Services.prefs.getCharPref(prefName);
+          });
+        }
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+
+    return channel;
+  },
+
+  get UpdateChannel() {
+    return this.getUpdateChannel();
+  },
+
+  /**
+   * Formats a URL by replacing %...% values with OS, build and locale specific
+   * values.
+   *
+   * @param  url
+   *         The URL to format.
+   * @return The formatted URL.
+   */
+  formatUpdateURL(url) {
+    url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
+    url = url.replace(/%VERSION%/g, Services.appinfo.version);
+    url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
+    url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + this.ABI);
+    url = url.replace(/%OS_VERSION%/g, this.OSVersion);
+    if (/%LOCALE%/.test(url)) {
+      url = url.replace(/%LOCALE%/g, this.Locale);
+    }
+    url = url.replace(/%CHANNEL%/g, this.UpdateChannel);
+    url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
+    url = url.replace(/%DISTRIBUTION%/g,
+                      getDistributionPrefValue(PREF_APP_DISTRIBUTION));
+    url = url.replace(/%DISTRIBUTION_VERSION%/g,
+                      getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
+    url = url.replace(/%CUSTOM%/g, Preferences.get(PREF_APP_UPDATE_CUSTOM, ""));
+    url = url.replace(/\+/g, "%2B");
+
+    if (AppConstants.platform == "gonk") {
+      let sysLibs = {};
+      Cu.import("resource://gre/modules/systemlibs.js", sysLibs);
+      let productDevice = sysLibs.libcutils.property_get("ro.product.device");
+      let buildType = sysLibs.libcutils.property_get("ro.build.type");
+      url = url.replace(/%PRODUCT_MODEL%/g,
+                        sysLibs.libcutils.property_get("ro.product.model"));
+      if (buildType == "user" || buildType == "userdebug") {
+        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice);
+      } else {
+        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice + "-" + buildType);
+      }
+      url = url.replace(/%B2G_VERSION%/g,
+                        Preferences.get(PREF_APP_B2G_VERSION, null));
+      url = url.replace(/%IMEI%/g,
+                        Preferences.get(PREF_APP_UPDATE_IMEI_HASH, "default"));
+    }
+
+    return url;
+  }
+};
+
+/* Get the distribution pref values, from defaults only */
+function getDistributionPrefValue(aPrefName) {
+  var prefValue = "default";
+
+  try {
+    prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
+  } catch (e) {
+    // use default when pref not found
+  }
+
+  return prefValue;
+}
+
+/**
+ * Gets the locale from the update.locale file for replacing %LOCALE% in the
+ * update url. The update.locale file can be located in the application
+ * directory or the GRE directory with preference given to it being located in
+ * the application directory.
+ */
+XPCOMUtils.defineLazyGetter(UpdateUtils, "Locale", function() {
+  let channel;
+  let locale;
+  for (let res of ['app', 'gre']) {
+    channel = Services.io.newChannel2("resource://" + res + "/" + FILE_UPDATE_LOCALE,
+                                      null,
+                                      null,
+                                      null,      // aLoadingNode
+                                      Services.scriptSecurityManager.getSystemPrincipal(),
+                                      null,      // aTriggeringPrincipal
+                                      Ci.nsILoadInfo.SEC_NORMAL,
+                                      Ci.nsIContentPolicy.TYPE_INTERNAL_XMLHTTPREQUEST);
+    try {
+      let inputStream = channel.open();
+      locale = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+    } catch(e) {}
+    if (locale)
+      return locale.trim();
+  }
+
+  Cu.reportError(FILE_UPDATE_LOCALE + " file doesn't exist in either the " +
+                 "application or GRE directories");
+
+  return null;
+});
+
+/* Windows only getter that returns the processor architecture. */
+XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() {
+  // Get processor architecture
+  let arch = "unknown";
+
+  const WORD = ctypes.uint16_t;
+  const DWORD = ctypes.uint32_t;
+
+  // This structure is described at:
+  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+  const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+      [
+      {wProcessorArchitecture: WORD},
+      {wReserved: WORD},
+      {dwPageSize: DWORD},
+      {lpMinimumApplicationAddress: ctypes.voidptr_t},
+      {lpMaximumApplicationAddress: ctypes.voidptr_t},
+      {dwActiveProcessorMask: DWORD.ptr},
+      {dwNumberOfProcessors: DWORD},
+      {dwProcessorType: DWORD},
+      {dwAllocationGranularity: DWORD},
+      {wProcessorLevel: WORD},
+      {wProcessorRevision: WORD}
+      ]);
+
+  let kernel32 = false;
+  try {
+    kernel32 = ctypes.open("Kernel32");
+  } catch (e) {
+    Cu.reportError("Unable to open kernel32! Exception: " + e);
+  }
+
+  if (kernel32) {
+    try {
+      let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
+                                                 ctypes.default_abi,
+                                                 ctypes.void_t,
+                                                 SYSTEM_INFO.ptr);
+      let winSystemInfo = SYSTEM_INFO();
+      // Default to unknown
+      winSystemInfo.wProcessorArchitecture = 0xffff;
+
+      GetNativeSystemInfo(winSystemInfo.address());
+      switch (winSystemInfo.wProcessorArchitecture) {
+        case 9:
+          arch = "x64";
+          break;
+        case 6:
+          arch = "IA64";
+          break;
+        case 0:
+          arch = "x86";
+          break;
+      }
+    } catch (e) {
+      Cu.reportError("Error getting processor architecture. " +
+                     "Exception: " + e);
+    } finally {
+      kernel32.close();
+    }
+  }
+
+  return arch;
+});
+
+XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function() {
+  let abi = null;
+  try {
+    abi = Services.appinfo.XPCOMABI;
+  }
+  catch (e) {
+    Cu.reportError("XPCOM ABI unknown");
+  }
+
+  if (AppConstants.platform == "macosx") {
+    // Mac universal build should report a different ABI than either macppc
+    // or mactel.
+    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
+                   getService(Ci.nsIMacUtils);
+
+    if (macutils.isUniversalBinary) {
+      abi += "-u-" + macutils.architecturesInBinary;
+    }
+  } else if (AppConstants.platform == "win") {
+    // Windows build should report the CPU architecture that it's running on.
+    abi += "-" + gWinCPUArch;
+  }
+  return abi;
+});
+
+XPCOMUtils.defineLazyGetter(UpdateUtils, "OSVersion", function() {
+  let osVersion;
+  try {
+    osVersion = Services.sysinfo.getProperty("name") + " " +
+                Services.sysinfo.getProperty("version");
+  }
+  catch (e) {
+    Cu.reportError("OS Version unknown.");
+  }
+
+  if (osVersion) {
+    if (AppConstants.platform == "win") {
+      const BYTE = ctypes.uint8_t;
+      const WORD = ctypes.uint16_t;
+      const DWORD = ctypes.uint32_t;
+      const WCHAR = ctypes.char16_t;
+      const BOOL = ctypes.int;
+
+      // This structure is described at:
+      // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+      const SZCSDVERSIONLENGTH = 128;
+      const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
+          [
+          {dwOSVersionInfoSize: DWORD},
+          {dwMajorVersion: DWORD},
+          {dwMinorVersion: DWORD},
+          {dwBuildNumber: DWORD},
+          {dwPlatformId: DWORD},
+          {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
+          {wServicePackMajor: WORD},
+          {wServicePackMinor: WORD},
+          {wSuiteMask: WORD},
+          {wProductType: BYTE},
+          {wReserved: BYTE}
+          ]);
+
+      // This structure is described at:
+      // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+      const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+          [
+          {wProcessorArchitecture: WORD},
+          {wReserved: WORD},
+          {dwPageSize: DWORD},
+          {lpMinimumApplicationAddress: ctypes.voidptr_t},
+          {lpMaximumApplicationAddress: ctypes.voidptr_t},
+          {dwActiveProcessorMask: DWORD.ptr},
+          {dwNumberOfProcessors: DWORD},
+          {dwProcessorType: DWORD},
+          {dwAllocationGranularity: DWORD},
+          {wProcessorLevel: WORD},
+          {wProcessorRevision: WORD}
+          ]);
+
+      let kernel32 = false;
+      try {
+        kernel32 = ctypes.open("Kernel32");
+      } catch (e) {
+        Cu.reportError("Unable to open kernel32! " + e);
+        osVersion += ".unknown (unknown)";
+      }
+
+      if (kernel32) {
+        try {
+          // Get Service pack info
+          try {
+            let GetVersionEx = kernel32.declare("GetVersionExW",
+                                                ctypes.default_abi,
+                                                BOOL,
+                                                OSVERSIONINFOEXW.ptr);
+            let winVer = OSVERSIONINFOEXW();
+            winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+            if(0 !== GetVersionEx(winVer.address())) {
+              osVersion += "." + winVer.wServicePackMajor +
+                           "." + winVer.wServicePackMinor;
+            } else {
+              Cu.reportError("Unknown failure in GetVersionEX (returned 0)");
+              osVersion += ".unknown";
+            }
+          } catch (e) {
+            Cu.reportError("Error getting service pack information. Exception: " + e);
+            osVersion += ".unknown";
+          }
+        } finally {
+          kernel32.close();
+        }
+
+        // Add processor architecture
+        osVersion += " (" + gWinCPUArch + ")";
+      }
+    }
+
+    try {
+      osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
+    }
+    catch (e) {
+      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
+    }
+    osVersion = encodeURIComponent(osVersion);
+  }
+  return osVersion;
+});
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -70,17 +70,17 @@ EXTRA_JS_MODULES += [
     'ShortcutUtils.jsm',
     'Sntp.jsm',
     'SpatialNavigation.jsm',
     'Sqlite.jsm',
     'Task.jsm',
     'TelemetryTimestamps.jsm',
     'Timer.jsm',
     'Troubleshoot.jsm',
-    'UpdateChannel.jsm',
+    'UpdateUtils.jsm',
     'WebChannel.jsm',
     'WindowDraggingUtils.jsm',
     'ZipUtils.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'AppConstants.jsm',
     'SessionRecorder.jsm',
--- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
+++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
@@ -6,16 +6,19 @@ const URL_HOST = "http://localhost";
 
 var GMPScope = Cu.import("resource://gre/modules/GMPInstallManager.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm")
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+
+let { computeHash } = Cu.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
 
 do_get_profile();
 
 function run_test() {Cu.import("resource://gre/modules/Preferences.jsm")
   Preferences.set("media.gmp.log.dump", true);
   Preferences.set("media.gmp.log.level", 0);
   run_next_test();
 }
@@ -425,17 +428,17 @@ function* test_checkForAddons_installAdd
   let zipFileName = "test_" + id + "_GMP.zip";
 
   let zipURL = URL_HOST + ":" + testserverPort + "/" + zipFileName;
   do_print("zipURL: " + zipURL);
 
   let data = "e~=0.5772156649";
   let zipFile = createNewZipFile(zipFileName, data);
   let hashFunc = "sha256";
-  let expectedDigest = yield GMPDownloader.computeHash(hashFunc, zipFile);
+  let expectedDigest = yield computeHash(hashFunc, zipFile.path);
   let fileSize = zipFile.fileSize;
   if (wantInstallReject) {
     fileSize = 1;
   }
 
   let responseXML =
     "<?xml version=\"1.0\"?>" +
     "<updates>" +
@@ -451,17 +454,16 @@ function* test_checkForAddons_installAdd
 
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
   let gmpAddons = yield installManager.checkForAddons();
   do_check_eq(gmpAddons.length, 1);
   let gmpAddon = gmpAddons[0];
   do_check_false(gmpAddon.isInstalled);
 
-  GMPInstallManager.overrideLeaveDownloadedZip = true;
   try {
     let extractedPaths = yield installManager.installAddon(gmpAddon);
     if (wantInstallReject) {
       do_check_true(false); // installAddon() should have thrown.
     }
     do_check_eq(extractedPaths.length, 1);
     let extractedPath = extractedPaths[0];
 
@@ -469,50 +471,35 @@ function* test_checkForAddons_installAdd
 
     let extractedFile = Cc["@mozilla.org/file/local;1"].
                         createInstance(Ci.nsIFile);
     extractedFile.initWithPath(extractedPath);
     do_check_true(extractedFile.exists());
     let readData = readStringFromFile(extractedFile);
     do_check_eq(readData, data);
 
-    // Check that the downloaded zip matches the offered zip exactly
-    let downloadedGMPFile = FileUtils.getFile("TmpD",
-      [gmpAddon.id + ".zip"]);
-    do_check_true(downloadedGMPFile.exists());
-    let downloadedBytes = getBinaryFileData(downloadedGMPFile);
-    let sourceBytes = getBinaryFileData(zipFile);
-    do_check_true(compareBinaryData(downloadedBytes, sourceBytes));
-
     // Make sure the prefs are set correctly
     do_check_true(!!GMPScope.GMPPrefs.get(
       GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, "", gmpAddon.id));
     do_check_eq(GMPScope.GMPPrefs.get(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, "",
                                       gmpAddon.id),
                 "1.1");
     do_check_eq(GMPScope.GMPPrefs.get(GMPScope.GMPPrefs.KEY_PLUGIN_ABI, "",
                                       gmpAddon.id),
-                GMPScope.GMPUtils.ABI());
+                UpdateUtils.ABI);
     // Make sure it reports as being installed
     do_check_true(gmpAddon.isInstalled);
 
     // Cleanup
     extractedFile.parent.remove(true);
     zipFile.remove(false);
     httpServer.stop(function() {});
-    do_print("Removing downloaded GMP file: " + downloadedGMPFile.path);
-    downloadedGMPFile.remove(false);
     installManager.uninit();
   } catch(ex) {
     zipFile.remove(false);
-    let downloadedGMPFile = FileUtils.getFile("TmpD",
-      [gmpAddon.id + ".zip"]);
-    do_print("Removing downloaded GMP file from exception handler: " +
-             downloadedGMPFile.path);
-    downloadedGMPFile.remove(false);
     if (!wantInstallReject) {
       do_throw("install update should not reject");
     }
   }
 }
 
 add_task(test_checkForAddons_installAddon.bind(null, "1", true, false));
 add_task(test_checkForAddons_installAddon.bind(null, "2", false, false));
@@ -794,55 +781,16 @@ function overrideXHR(status, response, o
   registrar.registerFactory(overrideXHR.myxhr.classID,
                             overrideXHR.myxhr.classDescription,
                             overrideXHR.myxhr.contractID,
                             overrideXHR.myxhr);
   return overrideXHR.myxhr;
 }
 
 /**
- * Compares binary data of 2 arrays and returns true if they are the same
- *
- * @param arr1 The first array to compare
- * @param arr2 The second array to compare
-*/
-function compareBinaryData(arr1, arr2) {
-  do_check_eq(arr1.length, arr2.length);
-  for (let i = 0; i < arr1.length; i++) {
-    if (arr1[i] != arr2[i]) {
-      do_print("Data differs at index " + i +
-               ", arr1: " + arr1[i] + ", arr2: " + arr2[i]);
-      return false;
-    }
-  }
-  return true;
-}
-
-/**
- * Reads a file's data and returns it
- *
- * @param file The file to read the data from
- * @return array of bytes for the data in the file.
-*/
-function getBinaryFileData(file) {
-  let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
-                   createInstance(Ci.nsIFileInputStream);
-  // Open as RD_ONLY with default permissions.
-  fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
-
-  // Check the returned size versus the expected size.
-  let stream = Cc["@mozilla.org/binaryinputstream;1"].
-               createInstance(Ci.nsIBinaryInputStream);
-  stream.setInputStream(fileStream);
-  let bytes = stream.readByteArray(stream.available());
-  fileStream.close();
-  return bytes;
-}
-
-/**
  * Creates a new zip file containing a file with the specified data
  * @param zipName The name of the zip file
  * @param data The data to go inside the zip for the filename entry1.info
  */
 function createNewZipFile(zipName, data) {
    // Create a zip file which will be used for extracting
     let stream = Cc["@mozilla.org/io/string-input-stream;1"].
                  createInstance(Ci.nsIStringInputStream);
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_UpdateUtils_updatechannel.js
@@ -0,0 +1,38 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+const { utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+
+const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
+const TEST_CHANNEL            = "TestChannel";
+const PREF_PARTNER_A          = "app.partner.test_partner_a";
+const TEST_PARTNER_A          = "TestPartnerA";
+const PREF_PARTNER_B          = "app.partner.test_partner_b";
+const TEST_PARTNER_B          = "TestPartnerB";
+
+add_task(function* test_updatechannel() {
+  let defaultPrefs = new Preferences({ defaultBranch: true });
+  let currentChannel = defaultPrefs.get(PREF_APP_UPDATE_CHANNEL);
+
+  do_check_eq(UpdateUtils.UpdateChannel, currentChannel);
+  do_check_eq(UpdateUtils.getUpdateChannel(true), currentChannel);
+  do_check_eq(UpdateUtils.getUpdateChannel(false), currentChannel);
+
+  defaultPrefs.set(PREF_APP_UPDATE_CHANNEL, TEST_CHANNEL);
+  do_check_eq(UpdateUtils.UpdateChannel, TEST_CHANNEL);
+  do_check_eq(UpdateUtils.getUpdateChannel(true), TEST_CHANNEL);
+  do_check_eq(UpdateUtils.getUpdateChannel(false), TEST_CHANNEL);
+
+  defaultPrefs.set(PREF_PARTNER_A, TEST_PARTNER_A);
+  defaultPrefs.set(PREF_PARTNER_B, TEST_PARTNER_B);
+  do_check_eq(UpdateUtils.UpdateChannel,
+              TEST_CHANNEL + "-cck-" + TEST_PARTNER_A + "-" + TEST_PARTNER_B);
+  do_check_eq(UpdateUtils.getUpdateChannel(true),
+              TEST_CHANNEL + "-cck-" + TEST_PARTNER_A + "-" + TEST_PARTNER_B);
+  do_check_eq(UpdateUtils.getUpdateChannel(false), TEST_CHANNEL);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_UpdateUtils_url.js
@@ -0,0 +1,292 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://testing-common/AppInfo.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+
+const PREF_APP_UPDATE_CHANNEL     = "app.update.channel";
+const PREF_APP_PARTNER_BRANCH     = "app.partner.";
+const PREF_DISTRIBUTION_ID        = "distribution.id";
+const PREF_DISTRIBUTION_VERSION   = "distribution.version";
+
+const URL_PREFIX = "http://localhost/";
+
+const MSG_SHOULD_EQUAL = " should equal the expected value";
+
+updateAppInfo();
+const gAppInfo = getAppInfo();
+const gDefaultPrefBranch = Services.prefs.getDefaultBranch(null);
+
+function setUpdateChannel(aChannel) {
+  gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, aChannel);
+}
+
+function getServicePack() {
+  // NOTE: This function is a helper function and not a test.  Thus,
+  // it uses throw() instead of do_throw().  Any tests that use this function
+  // should catch exceptions thrown in this function and deal with them
+  // appropriately (usually by calling do_throw).
+  const BYTE = ctypes.uint8_t;
+  const WORD = ctypes.uint16_t;
+  const DWORD = ctypes.uint32_t;
+  const WCHAR = ctypes.char16_t;
+  const BOOL = ctypes.int;
+
+  // This structure is described at:
+  // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+  const SZCSDVERSIONLENGTH = 128;
+  const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
+      [
+      {dwOSVersionInfoSize: DWORD},
+      {dwMajorVersion: DWORD},
+      {dwMinorVersion: DWORD},
+      {dwBuildNumber: DWORD},
+      {dwPlatformId: DWORD},
+      {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
+      {wServicePackMajor: WORD},
+      {wServicePackMinor: WORD},
+      {wSuiteMask: WORD},
+      {wProductType: BYTE},
+      {wReserved: BYTE}
+      ]);
+
+  let kernel32 = ctypes.open("kernel32");
+  try {
+    let GetVersionEx = kernel32.declare("GetVersionExW",
+                                        ctypes.default_abi,
+                                        BOOL,
+                                        OSVERSIONINFOEXW.ptr);
+    let winVer = OSVERSIONINFOEXW();
+    winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+    if (0 === GetVersionEx(winVer.address())) {
+      // Using "throw" instead of "do_throw" (see NOTE above)
+      throw("Failure in GetVersionEx (returned 0)");
+    }
+
+    return winVer.wServicePackMajor + "." + winVer.wServicePackMinor;
+  } finally {
+    kernel32.close();
+  }
+}
+
+function getProcArchitecture() {
+  // NOTE: This function is a helper function and not a test.  Thus,
+  // it uses throw() instead of do_throw().  Any tests that use this function
+  // should catch exceptions thrown in this function and deal with them
+  // appropriately (usually by calling do_throw).
+  const WORD = ctypes.uint16_t;
+  const DWORD = ctypes.uint32_t;
+
+  // This structure is described at:
+  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+  const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+      [
+      {wProcessorArchitecture: WORD},
+      {wReserved: WORD},
+      {dwPageSize: DWORD},
+      {lpMinimumApplicationAddress: ctypes.voidptr_t},
+      {lpMaximumApplicationAddress: ctypes.voidptr_t},
+      {dwActiveProcessorMask: DWORD.ptr},
+      {dwNumberOfProcessors: DWORD},
+      {dwProcessorType: DWORD},
+      {dwAllocationGranularity: DWORD},
+      {wProcessorLevel: WORD},
+      {wProcessorRevision: WORD}
+      ]);
+
+  let kernel32 = ctypes.open("kernel32");
+  try {
+    let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
+                                               ctypes.default_abi,
+                                               ctypes.void_t,
+                                               SYSTEM_INFO.ptr);
+    let sysInfo = SYSTEM_INFO();
+    // Default to unknown
+    sysInfo.wProcessorArchitecture = 0xffff;
+
+    GetNativeSystemInfo(sysInfo.address());
+    switch(sysInfo.wProcessorArchitecture) {
+      case 9:
+        return "x64";
+      case 6:
+        return "IA64";
+      case 0:
+        return "x86";
+      default:
+        // Using "throw" instead of "do_throw" (see NOTE above)
+        throw("Unknown architecture returned from GetNativeSystemInfo: " + sysInfo.wProcessorArchitecture);
+    }
+  } finally {
+    kernel32.close();
+  }
+}
+
+// Helper function for formatting a url and getting the result we're
+// interested in
+function getResult(url) {
+  url = UpdateUtils.formatUpdateURL(url);
+  return url.substr(URL_PREFIX.length).split("/")[0];
+}
+
+// url constructed with %PRODUCT%
+add_task(function* test_product() {
+  let url = URL_PREFIX + "%PRODUCT%/";
+  Assert.equal(getResult(url), gAppInfo.name,
+               "the url param for %PRODUCT%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %VERSION%
+add_task(function* test_version() {
+  let url = URL_PREFIX + "%VERSION%/";
+  Assert.equal(getResult(url), gAppInfo.version,
+               "the url param for %VERSION%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %BUILD_ID%
+add_task(function* test_build_id() {
+  let url = URL_PREFIX + "%BUILD_ID%/";
+  Assert.equal(getResult(url), gAppInfo.appBuildID,
+               "the url param for %BUILD_ID%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %BUILD_TARGET%
+// XXX TODO - it might be nice if we tested the actual ABI
+add_task(function* test_build_target() {
+  let url = URL_PREFIX + "%BUILD_TARGET%/";
+
+  let abi;
+  try {
+    abi = gAppInfo.XPCOMABI;
+  } catch (e) {
+    do_throw("nsIXULAppInfo:XPCOMABI not defined\n");
+  }
+
+  if (AppConstants.platform == "macosx") {
+    // Mac universal build should report a different ABI than either macppc
+    // or mactel. This is necessary since nsUpdateService.js will set the ABI to
+    // Universal-gcc3 for Mac universal builds.
+    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
+                   getService(Ci.nsIMacUtils);
+
+    if (macutils.isUniversalBinary) {
+      abi += "-u-" + macutils.architecturesInBinary;
+    }
+  } else if (AppConstants.platform == "win") {
+    // Windows build should report the CPU architecture that it's running on.
+    abi += "-" + getProcArchitecture();
+  }
+
+  Assert.equal(getResult(url), gAppInfo.OS + "_" + abi,
+               "the url param for %BUILD_TARGET%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %LOCALE%
+// Bug 488936 added the update.locale file that stores the update locale
+add_task(function* test_locale() {
+  // The code that gets the locale accesses the profile which is only available
+  // after calling do_get_profile in xpcshell tests. This prevents an error from
+  // being logged.
+  do_get_profile();
+
+  let url = URL_PREFIX + "%LOCALE%/";
+  Assert.equal(getResult(url), AppConstants.INSTALL_LOCALE,
+               "the url param for %LOCALE%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %CHANNEL%
+add_task(function* test_channel() {
+  let url = URL_PREFIX + "%CHANNEL%/";
+  setUpdateChannel("test_channel");
+  Assert.equal(getResult(url), "test_channel",
+               "the url param for %CHANNEL%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %CHANNEL% with distribution partners
+add_task(function* test_channel_distribution() {
+  let url = URL_PREFIX + "%CHANNEL%/";
+  gDefaultPrefBranch.setCharPref(PREF_APP_PARTNER_BRANCH + "test_partner1",
+                                 "test_partner1");
+  gDefaultPrefBranch.setCharPref(PREF_APP_PARTNER_BRANCH + "test_partner2",
+                                 "test_partner2");
+  Assert.equal(getResult(url),
+               "test_channel-cck-test_partner1-test_partner2",
+               "the url param for %CHANNEL%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %PLATFORM_VERSION%
+add_task(function* test_platform_version() {
+  let url = URL_PREFIX + "%PLATFORM_VERSION%/";
+  Assert.equal(getResult(url), gAppInfo.platformVersion,
+               "the url param for %PLATFORM_VERSION%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %OS_VERSION%
+add_task(function* test_os_version() {
+  let url = URL_PREFIX + "%OS_VERSION%/";
+  let osVersion;
+  let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+  osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+
+  if (AppConstants.platform == "win") {
+    try {
+      let servicePack = getServicePack();
+      osVersion += "." + servicePack;
+    } catch (e) {
+      do_throw("Failure obtaining service pack: " + e);
+    }
+
+    if ("5.0" === sysInfo.getProperty("version")) { // Win2K
+      osVersion += " (unknown)";
+    } else {
+      try {
+        osVersion += " (" + getProcArchitecture() + ")";
+      } catch (e) {
+        do_throw("Failed to obtain processor architecture: " + e);
+      }
+    }
+  }
+
+  if (osVersion) {
+    try {
+      osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+    } catch (e) {
+      // Not all platforms have a secondary widget library, so an error is
+      // nothing to worry about.
+    }
+    osVersion = encodeURIComponent(osVersion);
+  }
+
+  Assert.equal(getResult(url), osVersion,
+               "the url param for %OS_VERSION%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %DISTRIBUTION%
+add_task(function* test_distribution() {
+  let url = URL_PREFIX + "%DISTRIBUTION%/";
+  gDefaultPrefBranch.setCharPref(PREF_DISTRIBUTION_ID, "test_distro");
+  Assert.equal(getResult(url), "test_distro",
+               "the url param for %DISTRIBUTION%" + MSG_SHOULD_EQUAL);
+});
+
+// url constructed with %DISTRIBUTION_VERSION%
+add_task(function* test_distribution_version() {
+  let url = URL_PREFIX + "%DISTRIBUTION_VERSION%/";
+  gDefaultPrefBranch.setCharPref(PREF_DISTRIBUTION_VERSION, "test_distro_version");
+  Assert.equal(getResult(url), "test_distro_version",
+               "the url param for %DISTRIBUTION_VERSION%" + MSG_SHOULD_EQUAL);
+});
+
+add_task(function* test_custom() {
+  Services.prefs.setCharPref("app.update.custom", "custom");
+  let url = URL_PREFIX + "%CUSTOM%/";
+  Assert.equal(getResult(url), "custom",
+               "the url query string for %CUSTOM%" + MSG_SHOULD_EQUAL);
+});
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -52,12 +52,14 @@ skip-if = toolkit == 'android'
 [test_sqlite_shutdown.js]
 skip-if = toolkit == 'android'
 [test_task.js]
 skip-if = toolkit == 'android'
 [test_TelemetryTimestamps.js]
 skip-if = toolkit == 'android'
 [test_timer.js]
 skip-if = toolkit == 'android'
+[test_UpdateUtils_url.js]
+[test_UpdateUtils_updatechannel.js]
 [test_web_channel.js]
 [test_web_channel_broker.js]
 [test_ZipUtils.js]
 skip-if = toolkit == 'android'
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -854,16 +854,23 @@ var AddonManagerInternal = {
       AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
     }
 
     this.pendingProviders.delete(aProvider);
     this.providers.add(aProvider);
     logger.debug(`Provider finished startup: ${providerName(aProvider)}`);
   },
 
+  _getProviderByName(aName) {
+    for (let provider of this.providers) {
+      if (providerName(provider) == aName)
+        return provider;
+    }
+  },
+
   /**
    * Initializes the AddonManager, loading any known providers and initializing
    * them.
    */
   startup: function AMI_startup() {
     try {
       if (gStarted)
         return;
@@ -1459,19 +1466,19 @@ var AddonManagerInternal = {
   backgroundUpdateCheck: function AMI_backgroundUpdateCheck() {
     if (!gStarted)
       throw Components.Exception("AddonManager is not initialized",
                                  Cr.NS_ERROR_NOT_INITIALIZED);
 
     let buPromise = Task.spawn(function* backgroundUpdateTask() {
       let hotfixID = this.hotfixID;
 
-      let checkHotfix = hotfixID &&
-                        Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) &&
-                        Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO);
+      let appUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) &&
+                             Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO);
+      let checkHotfix = hotfixID && appUpdateEnabled;
 
       logger.debug("Background update check beginning");
 
       Services.obs.notifyObservers(null, "addons-background-update-start", null);
 
       if (this.updateEnabled) {
         let scope = {};
         Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
@@ -1608,16 +1615,25 @@ var AddonManagerInternal = {
               }
             });
 
             aInstall.install();
           }
         }
       }
 
+      if (appUpdateEnabled) {
+        try {
+          yield AddonManagerInternal._getProviderByName("XPIProvider").updateSystemAddons();
+        }
+        catch (e) {
+          logger.warn("Failed to update system addons", e);
+        }
+      }
+
       logger.debug("Background update check complete");
       Services.obs.notifyObservers(null,
                                    "addons-background-update-complete",
                                    null);
     }.bind(this));
     // Fork the promise chain so we can log the error and let our caller see it too.
     buPromise.then(null, e => logger.warn("Error in background update", e));
     return buPromise;
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -14,16 +14,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/GMPUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(
   this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(
   this, "setTimeout", "resource://gre/modules/Timer.jsm");
 
 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
 const STRING_TYPE_NAME       = "type.%ID%.name";
@@ -485,18 +486,18 @@ GMPWrapper.prototype = {
   },
 
   validate: function() {
     if (!this.isInstalled) {
       // Not installed -> Valid.
       return { installed: false, valid: true };
     }
 
-    let abi = GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ABI, GMPUtils.ABI(), this._plugin.id);
-    if (abi != GMPUtils.ABI()) {
+    let abi = GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI, this._plugin.id);
+    if (abi != UpdateUtils.ABI) {
       // ABI doesn't match. Possibly this is a profile migrated across platforms
       // or from 32 -> 64 bit.
       return {
         installed: true,
         mismatchedABI: true,
         valid: false
       };
     }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
@@ -0,0 +1,322 @@
+/* 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 } = Components;
+
+this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/CertUtils.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+let logger = Log.repository.getLogger("addons.productaddons");
+
+/**
+ * Number of milliseconds after which we need to cancel `downloadXML`.
+ *
+ * Bug 1087674 suggests that the XHR we use in `downloadXML` may
+ * never terminate in presence of network nuisances (e.g. strange
+ * antivirus behavior). This timeout is a defensive measure to ensure
+ * that we fail cleanly in such case.
+ */
+const TIMEOUT_DELAY_MS = 20000;
+// Chunk size for the incremental downloader
+const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
+// Incremental downloader interval
+const DOWNLOAD_INTERVAL  = 0;
+// How much of a file to read into memory at a time for hashing
+const HASH_CHUNK_SIZE = 8192;
+
+/**
+ * Gets the status of an XMLHttpRequest either directly or from its underlying
+ * channel.
+ *
+ * @param  request
+ *         The XMLHttpRequest.
+ * @return an integer status value.
+ */
+function getRequestStatus(request) {
+  let status = null;
+  try {
+    status = request.status;
+  }
+  catch (e) {
+  }
+
+  if (status != null) {
+    return status;
+  }
+
+  return request.channel.QueryInterface(Ci.nsIRequest).status;
+}
+
+/**
+ * Downloads an XML document from a URL optionally testing the SSL certificate
+ * for certain attributes.
+ *
+ * @param  url
+ *         The url to download from.
+ * @param  allowNonBuiltIn
+ *         Whether to trust SSL certificates without a built-in CA issuer.
+ * @param  allowedCerts
+ *         The list of certificate attributes to match the SSL certificate
+ *         against or null to skip checks.
+ * @return a promise that resolves to the DOM document downloaded or rejects
+ *         with a JS exception in case of error.
+ */
+function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
+  return new Promise((resolve, reject) => {
+    let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+                  createInstance(Ci.nsISupports);
+    // This is here to let unit test code override XHR
+    if (request.wrappedJSObject) {
+      request = request.wrappedJSObject;
+    }
+    request.open("GET", url, true);
+    request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
+    // Prevent the request from reading from the cache.
+    request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+    // Prevent the request from writing to the cache.
+    request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+    request.timeout = TIMEOUT_DELAY_MS;
+
+    request.overrideMimeType("text/xml");
+    // The Cache-Control header is only interpreted by proxies and the
+    // final destination. It does not help if a resource is already
+    // cached locally.
+    request.setRequestHeader("Cache-Control", "no-cache");
+    // HTTP/1.0 servers might not implement Cache-Control and
+    // might only implement Pragma: no-cache
+    request.setRequestHeader("Pragma", "no-cache");
+
+    let fail = (event) => {
+      let request = event.target;
+      let status = getRequestStatus(request);
+      let message = "Failed downloading XML, status: " + status +  ", reason: " + event.type;
+      logger.warn(message);
+      let ex = new Error(message);
+      ex.status = status;
+      reject(ex);
+    };
+
+    let success = (event) => {
+      logger.info("Completed downloading document");
+      let request = event.target;
+
+      try {
+        checkCert(request.channel, allowNonBuiltIn, allowedCerts);
+      } catch (ex) {
+        logger.error("Request failed certificate checks: " + ex);
+        ex.status = getRequestStatus(request);
+        reject(ex);
+        return;
+      }
+
+      resolve(request.responseXML);
+    };
+
+    request.addEventListener("error", fail, false);
+    request.addEventListener("abort", fail, false);
+    request.addEventListener("timeout", fail, false);
+    request.addEventListener("load", success, false);
+
+    logger.info("sending request to: " + url);
+    request.send(null);
+  });
+}
+
+/**
+ * Parses a list of add-ons from a DOM document.
+ *
+ * @param  document
+ *         The DOM document to parse.
+ * @return null if there is no <addons> element otherwise an array of the addons
+ *         listed.
+ */
+function parseXML(document) {
+  // Check that the root element is correct
+  if (document.documentElement.localName != "updates") {
+    throw new Error("got node name: " + document.documentElement.localName +
+                    ", expected: updates");
+  }
+
+  // Check if there are any addons elements in the updates element
+  let addons = document.querySelector("updates:root > addons");
+  if (!addons) {
+    return null;
+  }
+
+  let results = [];
+  let addonList = document.querySelectorAll("updates:root > addons > addon");
+  for (let addonElement of addonList) {
+    let addon = {};
+
+    for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
+      if (addonElement.hasAttribute(name)) {
+        addon[name] = addonElement.getAttribute(name);
+      }
+    }
+    addon.size = Number(addon.size) || undefined;
+
+    results.push(addon);
+  }
+
+  return results;
+}
+
+/**
+ * Downloads file from a URL using the incremental file downloader.
+ *
+ * @param  url
+ *         The url to download from.
+ * @return a promise that resolves to the path of a temporary file or rejects
+ *         with a JS exception in case of error.
+ */
+function downloadFile(url) {
+  return new Promise((resolve, reject) => {
+    let observer = {
+      onStartRequest: function() {},
+
+      onStopRequest: function(request, context, status) {
+        if (!Components.isSuccessCode(status)) {
+          logger.warn("File download failed: 0x" + status.toString(16));
+          tmpFile.remove(true);
+          reject(Components.Exception("File download failed", status));
+          return;
+        }
+
+        resolve(tmpFile.path);
+      }
+    };
+
+    let uri = NetUtil.newURI(url);
+    let request = Cc["@mozilla.org/network/incremental-download;1"].
+                  createInstance(Ci.nsIIncrementalDownload);
+    let tmpFile = FileUtils.getFile("TmpD", ["tmpaddon"]);
+    tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+    logger.info("Downloading from " + uri.spec + " to " + tmpFile.path);
+    request.init(uri, tmpFile, DOWNLOAD_CHUNK_BYTES_SIZE, DOWNLOAD_INTERVAL);
+    request.start(observer, null);
+  });
+}
+
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+  let result = "";
+  for (let i = 0; i < input.length; ++i) {
+    let hex = input.charCodeAt(i).toString(16);
+    if (hex.length == 1) {
+      hex = "0" + hex;
+    }
+    result += hex;
+  }
+  return result;
+}
+
+/**
+ * Calculates the hash of a file.
+ *
+ * @param  hashFunction
+ *         The type of hash function to use, must be supported by nsICryptoHash.
+ * @param  path
+ *         The path of the file to hash.
+ * @return a promise that resolves to hash of the file or rejects with a JS
+ *         exception in case of error.
+ */
+let computeHash = Task.async(function*(hashFunction, path) {
+  let file = yield OS.File.open(path, { existing: true, read: true });
+  try {
+    let hasher = Cc["@mozilla.org/security/hash;1"].
+                 createInstance(Ci.nsICryptoHash);
+    hasher.initWithString(hashFunction);
+
+    let bytes;
+    do {
+      bytes = yield file.read(HASH_CHUNK_SIZE);
+      hasher.update(bytes, bytes.length);
+    } while (bytes.length == HASH_CHUNK_SIZE);
+
+    return binaryToHex(hasher.finish(false));
+  }
+  finally {
+    yield file.close();
+  }
+});
+
+/**
+ * Verifies that a downloaded file matches what was expected.
+ *
+ * @param  properties
+ *         The properties to check, `size` and `hashFunction` with `hashValue`
+ *         are supported. Any properties missing won't be checked.
+ * @param  path
+ *         The path of the file to check.
+ * @return a promise that resolves if the file matched or rejects with a JS
+ *         exception in case of error.
+ */
+let verifyFile = Task.async(function*(properties, path) {
+  if (properties.size !== undefined) {
+    let stat = yield OS.File.stat(path);
+    if (stat.size != properties.size) {
+      throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
+    }
+  }
+
+  if (properties.hashFunction !== undefined) {
+    let expectedDigest = properties.hashValue.toLowerCase();
+    let digest = yield computeHash(properties.hashFunction, path);
+    if (digest != expectedDigest) {
+      throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest +  "`.");
+    }
+  }
+});
+
+const ProductAddonChecker = {
+  /**
+   * Downloads a list of add-ons from a URL optionally testing the SSL
+   * certificate for certain attributes.
+   *
+   * @param  url
+   *         The url to download from.
+   * @param  allowNonBuiltIn
+   *         Whether to trust SSL certificates without a built-in CA issuer.
+   * @param  allowedCerts
+   *         The list of certificate attributes to match the SSL certificate
+   *         against or null to skip checks.
+   * @return a promise that resolves to the list of add-ons or rejects with a JS
+   *         exception in case of error.
+   */
+  getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
+    return downloadXML(url, allowNonBuiltIn, allowedCerts).then(parseXML);
+  },
+
+  /**
+   * Downloads an add-on to a local file and checks that it matches the expected
+   * file. The caller is responsible for deleting the temporary file returned.
+   *
+   * @param  addon
+   *         The addon to download.
+   * @return a promise that resolves to the temporary file downloaded or rejects
+   *         with a JS exception in case of error.
+   */
+  downloadAddon: Task.async(function*(addon) {
+    let path = yield downloadFile(addon.URL);
+    try {
+      yield verifyFile(addon, path);
+      return path;
+    }
+    catch (e) {
+      yield OS.File.remove(path);
+      throw e;
+    }
+  })
+}
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -37,16 +37,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess",
                                   "resource:///modules/devtools/client/framework/ToolboxProcess.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
                                   "resource://gre/modules/devtools/shared/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProductAddonChecker",
+                                  "resource://gre/modules/addons/ProductAddonChecker.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
                                    "@mozilla.org/extensions/blocklist;1",
                                    Ci.nsIBlocklistService);
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "ChromeRegistry",
                                    "@mozilla.org/chrome/chrome-registry;1",
                                    "nsIChromeRegistry");
@@ -94,16 +98,17 @@ const PREF_XPI_PERMISSIONS_BRANCH     = 
 const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
 const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
 const PREF_INTERPOSITION_ENABLED      = "extensions.interposition.enabled";
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
+const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 
 const PREF_CHECKCOMAT_THEMEOVERRIDE   = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion";
 
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
@@ -305,16 +310,35 @@ LAZY_OBJECTS.forEach(name => {
       let objs = loadLazyObjects();
       return objs[name];
     },
     configurable: true
   });
 });
 
 
+// Behaves like Promise.all except waits for all promises to resolve/reject
+// before resolving/rejecting itself
+function waitForAllPromises(promises) {
+  return new Promise((resolve, reject) => {
+    let shouldReject = false;
+    let rejectValue = null;
+
+    let newPromises = [
+      for (p of promises)
+        p.catch(value => {
+          shouldReject = true;
+          rejectValue = value;
+        })
+    ]
+    Promise.all(newPromises)
+           .then((results) => shouldReject ? reject(rejectValue) : resolve(results));
+  });
+}
+
 function findMatchingStaticBlocklistItem(aAddon) {
   for (let item of STATIC_BLOCKLIST_PATTERNS) {
     if ("creator" in item && typeof item.creator == "string") {
       if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) ||
           (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) {
         return item;
       }
     }
@@ -2754,16 +2778,101 @@ this.XPIProvider = {
              getService(Ci.nsIWindowWatcher);
     ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
 
     // Ensure any changes to the add-ons list are flushed to disk
     Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
                                !XPIDatabase.writeAddonsList());
   },
 
+  updateSystemAddons: Task.async(function XPI_updateSystemAddons() {
+    // Download the list of system add-ons
+    let url = Preferences.get(PREF_SYSTEM_ADDON_UPDATE_URL, null);
+    if (!url)
+      return;
+
+    url = UpdateUtils.formatUpdateURL(url);
+
+    logger.info(`Starting system add-on update check from ${url}.`);
+    let addonList = yield ProductAddonChecker.getProductAddonList(url);
+
+    // If there was no list then do nothing.
+    if (!addonList) {
+      logger.info("No system add-ons list was returned.");
+      return;
+    }
+
+    addonList = [for (spec of addonList) { spec, path: null, addon: null }];
+
+    // Bug 1204159: If this matches the current set in the profile or app locations
+    // then just switch to those
+
+    let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
+
+    // Download all the add-ons
+    // Bug 1204158: If we already have some of these locally then just use those
+    let downloadAddon = Task.async(function*(item) {
+      try {
+        item.path = yield ProductAddonChecker.downloadAddon(item.spec);
+        item.addon = yield loadManifestFromFile(nsIFile(item.path), systemAddonLocation);
+      }
+      catch (e) {
+        logger.error(`Failed to download system add-on ${item.spec.id}`, e);
+      }
+    });
+    yield Promise.all([for (item of addonList) downloadAddon(item)]);
+
+    // The download promises all resolve regardless, now check if they all
+    // succeeded
+    let validateAddon = (item) => {
+      if (item.spec.id != item.addon.id) {
+        logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`);
+        return false;
+      }
+
+      if (item.spec.version != item.addon.version) {
+        logger.warn(`Expected system add-on ${item.spec.id} to be version ${item.version} but was ${item.addon.version}.`);
+        return false;
+      }
+
+      if (!systemAddonLocation.isValidAddon(item.addon))
+        return false;
+
+      return true;
+    }
+
+    try {
+      if (!addonList.every(item => item.path && item.addon && validateAddon(item))) {
+        throw new Error("Rejecting updated system add-on set that either could not " +
+                        "be downloaded or contained unusable add-ons.");
+      }
+
+      // Install into the install location
+      logger.info("Installing new system add-on set");
+      yield systemAddonLocation.installAddonSet([for (item of addonList) item.addon]);
+
+      // Bug 1204156: Switch to the new system add-ons without requiring a restart
+    }
+    finally {
+      // Delete the temporary files
+      logger.info("Deleting temporary files");
+      for (let item of addonList) {
+        // If this item downloaded delete the temporary file.
+        if (item.path) {
+          try {
+            yield OS.File.remove(item.path);
+          }
+          catch (e) {
+            logger.warn(`Failed to remove temporary file ${item.path}.`, e);
+          }
+        }
+      }
+    }
+  }),
+
   /**
    * Verifies that all installed add-ons are still correctly signed.
    */
   verifySignatures: function XPI_verifySignatures() {
     XPIDatabase.getAddonList(a => true, (addons) => {
       Task.spawn(function*() {
         let changes = {
           enabled: [],
@@ -7351,50 +7460,120 @@ Object.assign(SystemAddonInstallLocation
 
   /**
    * Tests whether updated system add-ons are expected.
    */
   isActive: function() {
     return this._directory != null;
   },
 
+  isValidAddon: function(aAddon) {
+    if (aAddon.appDisabled) {
+      logger.warn(`System add-on ${aAddon.id} isn't compatible with the application.`);
+      return false;
+    }
+
+    if (aAddon.unpack) {
+      logger.warn(`System add-on ${aAddon.id} isn't a packed add-on.`);
+      return false;
+    }
+
+    if (!aAddon.bootstrap) {
+      logger.warn(`System add-on ${aAddon.id} isn't restartless.`);
+      return false;
+    }
+
+    return true;
+  },
+
   /**
    * Tests whether the loaded add-on information matches what is expected.
    */
   isValid: function(aAddons) {
     for (let id of Object.keys(this._addonSet.addons)) {
       if (!aAddons.has(id)) {
-        logger.warn("Expected add-on " + id + " is missing from the system add-on location.");
+        logger.warn(`Expected add-on ${id} is missing from the system add-on location.`);
         return false;
       }
 
       let addon = aAddons.get(id);
-      if (addon.appDisabled) {
-        logger.warn("System add-on " + id + " isn't compatible with the application.");
-        return false;
-      }
-
-      if (addon.unpack) {
-        logger.warn("System add-on " + id + " isn't a packed add-on.");
+      if (addon.version != this._addonSet.addons[id].version) {
+        logger.warn(`Expected system add-on ${id} to be version ${this._addonSet.addons[id].version} but was ${addon.version}.`);
         return false;
       }
 
-      if (!addon.bootstrap) {
-        logger.warn("System add-on " + id + " isn't restartless.");
+      if (!this.isValidAddon(addon))
         return false;
-      }
-
-      if (addon.version != this._addonSet.addons[id].version) {
-        logger.warn("System add-on " + id + " wasn't the correct version.");
-        return false;
-      }
     }
 
     return true;
   },
+
+  /**
+   * Installs a new set of system add-ons into the location and updates the
+   * add-on set in prefs. We wait to switch state until a restart.
+   */
+  installAddonSet: Task.async(function(aAddons) {
+    // Make sure the base dir exists
+    yield OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });
+
+    let newDir = this._baseDir.clone();
+
+    let uuidGen = Cc["@mozilla.org/uuid-generator;1"].
+                  getService(Ci.nsIUUIDGenerator);
+    newDir.append("blank");
+
+    while (true) {
+      newDir.leafName = uuidGen.generateUUID().toString();
+
+      try {
+        yield OS.File.makeDir(newDir.path, { ignoreExisting: false });
+        break;
+      }
+      catch (e) {
+        // Directory already exists, pick another
+      }
+    }
+
+    let copyAddon = Task.async(function*(addon) {
+      let target = OS.Path.join(newDir.path, addon.id + ".xpi");
+      logger.info(`Copying ${addon.id} from ${addon._sourceBundle.path} to ${target}.`);
+      try {
+        yield OS.File.copy(addon._sourceBundle.path, target);
+      }
+      catch (e) {
+        logger.error(`Failed to copy ${addon.id} from ${addon._sourceBundle.path} to ${target}.`, e);
+        throw e;
+      }
+      addon._sourceBundle = new nsIFile(target);
+    });
+
+    try {
+      yield waitForAllPromises([for (addon of aAddons) copyAddon(addon)]);
+    }
+    catch (e) {
+      try {
+        yield OS.File.removeDir(newDir.path, { ignorePermissions: true });
+      }
+      catch (e) {
+        logger.warn(`Failed to remove new system add-on directory ${newDir.path}.`, e);
+      }
+      throw e;
+    }
+
+    // All add-ons in position, create the new state and store it in prefs
+    let state = { schema: 1, directory: newDir.leafName, addons: {} };
+    for (let addon of aAddons) {
+      state.addons[addon.id] = {
+        version: addon.version
+      }
+    }
+
+    this._saveAddonSet(state);
+  }),
 });
 
 #ifdef XP_WIN
 /**
  * An object that identifies a registry install location for add-ons. The location
  * consists of a registry key which contains string values mapping ID to the
  * path where an add-on is installed
  *
--- a/toolkit/mozapps/extensions/internal/moz.build
+++ b/toolkit/mozapps/extensions/internal/moz.build
@@ -7,16 +7,17 @@
 EXTRA_JS_MODULES.addons += [
     'AddonLogging.jsm',
     'AddonRepository.jsm',
     'AddonRepository_SQLiteMigrator.jsm',
     'AddonUpdateChecker.jsm',
     'Content.js',
     'GMPProvider.jsm',
     'LightweightThemeImageOptimizer.jsm',
+    'ProductAddonChecker.jsm',
     'SpellCheckDictionaryBootstrap.js',
     'WebExtensionBootstrap.js',
 ]
 
 # Don't ship unused providers on Android
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     EXTRA_JS_MODULES.addons += [
         'PluginProvider.jsm',
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -18,18 +18,18 @@ try {
   // process. We're used in the child process (for now), so guard against
   // this.
   Components.utils.import("resource://gre/modules/AddonManager.jsm");
 } catch (e) {
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org";
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_APPDIR                      = "XCurProcD";
@@ -554,17 +554,17 @@ Blocklist.prototype = {
     dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
     // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
     if (gApp.version)
       dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
     dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
     dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
     dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
     dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
-    dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get());
+    dsURI = dsURI.replace(/%CHANNEL%/g, UpdateUtils.UpdateChannel);
     dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
     dsURI = dsURI.replace(/%DISTRIBUTION%/g,
                       getDistributionPrefValue(PREF_APP_DISTRIBUTION));
     dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
                       getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
     dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion);
     dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal);
     dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.txt
@@ -0,0 +1,1 @@
+Not an xml file!
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<foobar></barfoo>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad2.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<test></test>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/empty.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<updates>
+  <addons></addons>
+</updates>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<updates>
+  <addons>
+    <addon id="test1" URL="http://example.com/test1.xpi"/>
+    <addon id="test2" URL="http://example.com/test2.xpi" hashFunction="md5" hashValue="djhfgsjdhf"/>
+    <addon id="test3" URL="http://example.com/test3.xpi" version="1.0" size="45"/>
+    <addon id="test4"/>
+    <addon URL="http://example.com/test5.xpi"/>
+  </addons>
+</updates>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/missing.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<updates></updates>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..51b00475a9641ea9d608874a3ab7679da3a4374b
GIT binary patch
literal 452
zc$^FHW@Zs#U}E54_?u^9#dA2DRf~~<A%uy6ftx{;Av3SIBrzvPuP7xgG=!6Z`R(;m
zPY@2RU}5;mD8f)0<mh+UKw!^vQMJ8_JN=layLm<}3F>4Pa^m=!CAB@`#_JRJCLMTP
zI9DyV<<;!;^s=<;=fu~BJ&BG`=N0*wGwVpVQP-#IS7-g;+M)E%WsUK%S<7acl?Nw(
z&oP^SK626~lcn2k1%78f5zUn+<o=|h$w0Vj$<=4!T1TCmO+4Styx{&-<y%?H6YYoF
z!mhnY7WkLQ7jcB0`=;kq&w>+E7tLzE!`U8JT5_uOTuP|ifs?i!UE8+CEYjcYy?ajS
z8plGZ?O!gpMb-1SG`8M($7*+F>!j#D=h``2kGE-L^!%E%W6!hu!CPCtPtFy8#3UhH
zdHeBp*^fM58gJ}(X{=0b>ML;8jErXXkXxv9p6O?ovig*H-&tb1i#~sHx%bD*`FKOi
zcd5uS!$os`<_Ejg2Y53wi8JF0X<h~p0CE|YG=f+t;m!&P_s|Lkh5&C?Hi$|_1~(v`
I0n)+%08F#C6#xJL
deleted file mode 100644
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js
+++ /dev/null
@@ -1,18 +0,0 @@
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-const ID = "system1@tests.mozilla.org";
-const VERSION = "1.0";
-
-function install(data, reason) {
-}
-
-function startup(data, reason) {
-  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
-}
-
-function shutdown(data, reason) {
-  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
-}
-
-function uninstall(data, reason) {
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>system1@tests.mozilla.org</em:id>
-    <em:version>1.0</em:version>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Front End MetaData -->
-    <em:name>System Add-on 1</em:name>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>1</em:minVersion>
-        <em:maxVersion>5</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js
+++ /dev/null
@@ -1,18 +0,0 @@
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-const ID = "system2@tests.mozilla.org";
-const VERSION = "1.0";
-
-function install(data, reason) {
-}
-
-function startup(data, reason) {
-  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
-}
-
-function shutdown(data, reason) {
-  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
-}
-
-function uninstall(data, reason) {
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>system2@tests.mozilla.org</em:id>
-    <em:version>1.0</em:version>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Front End MetaData -->
-    <em:name>System Add-on 2</em:name>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>1</em:minVersion>
-        <em:maxVersion>5</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js
+++ /dev/null
@@ -1,18 +0,0 @@
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-const ID = "system1@tests.mozilla.org";
-const VERSION = "2.0";
-
-function install(data, reason) {
-}
-
-function startup(data, reason) {
-  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
-}
-
-function shutdown(data, reason) {
-  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
-}
-
-function uninstall(data, reason) {
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>system1@tests.mozilla.org</em:id>
-    <em:version>2.0</em:version>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Front End MetaData -->
-    <em:name>System Add-on 1</em:name>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>1</em:minVersion>
-        <em:maxVersion>5</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js
+++ /dev/null
@@ -1,18 +0,0 @@
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-const ID = "system3@tests.mozilla.org";
-const VERSION = "1.0";
-
-function install(data, reason) {
-}
-
-function startup(data, reason) {
-  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
-}
-
-function shutdown(data, reason) {
-  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
-}
-
-function uninstall(data, reason) {
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>system3@tests.mozilla.org</em:id>
-    <em:version>1.0</em:version>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Front End MetaData -->
-    <em:name>System Add-on 3</em:name>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>1</em:minVersion>
-        <em:maxVersion>5</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
deleted file mode 100644
index 60bcde626ffd9307c052b3cecc45812f716c5c0d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
rename from toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi
rename to toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_1.xpi
rename from toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app3/features/system1@tests.mozilla.org.xpi
rename to toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_1_badcert.xpi
rename from toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi
rename to toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_2.xpi
rename from toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi
rename to toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_1.xpi
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7af871d36dec992e405752bc9db49e6dae4ea085
GIT binary patch
literal 858
zc$^FHW@Zs#U}E54P|DDD&pm(r>k|eBh7F7i3_J`n3`zO<CB-F0i3NID#i1db49x$H
zK8OAS;?fFk21b@|KxIJP3=C5z_<J)u@*I8tTl3JIZClQG^@};KkgLi(x+q+-{Bl`J
z+9s8mqN4kEZ(i_7en~~m$^P$go0i-NKC~oh^Dl*K;%B3z-4>qFoo9A--p0-=Pa7`f
zOk}^m=mvXd+NKq&rcKgVqxXQP$EGuB&U|gJkKJG1I2TN-$QOHm=k3$4Wm_ZrKiru&
zrEu%%m7)HeH8*z)ygIjXHsfMpFX4)nar+Hg6VCqCXM3<Y{QM4K?G>k+AFo!*D)wKp
z=*_Q<(@hOzU;7=52#;akBRyg1j@dWe_ZD5>e#QA(pWNk-H_R41mOfa|_W#7`m+#~E
zFb2RwN(B^BLKZqkK<}3_F)(lgLn<?`xFj(rN3SR)4IEG`#-BqOj4=YLb<)|qLk0pZ
z@9VhC=g6w(RV#2U^q3N@zxK**MK+zE>YL1cUtGQ}A>^Q7y8TSuW^3W4bG}DClz+Hq
zOWL$2nbXc_x12ivl&ih-<K`6p3nEj>VyBm#Us-BB%YS+BEW=Y0g<q%rXlA@8{8*jy
z&~bGs?uGe#ZvFFGS`l+#)0<YV+7kas1$>W-H2)ks$>zj!=K8kQ$V*nib7EUf<aPwP
z6f18(<Xj-DKh;Tm)4D$gn=icKJ#-{KUh5^N#KYptT}vb9@V-~xuuaUy;$FyuD8ZTC
zEq*tI6)T>uVf>Sq^+&Nm-?G5xL~8HDsE~L=vA8RyoNFUu#Mm>QH0+++;CfkO=R*Uv
zoqrXY&Az>z-Fu&V=|4>yi!CPipU<mgS=SOh;d}GHDkl9y6T0uQc0XxWdeNts`D4A-
z7xn;eMkYCCTuD@d0R(_dh9!+47HWcJg(O(CI6yWLGb)iy)B;i{5z7jRSPTPM*+80@
NfN(L8UdROE0RUxTQM3R6
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5c8570dcc4086cb1a3e9270a2f8ef64e28899c02
GIT binary patch
literal 858
zc$^FHW@Zs#U}E54Fv!q$Kk&AJ=?Mb^!v;nM1|9|(hNS%blH!u0!~(sn;?NLI24)%K
z&!HkfTw1}+z{v6qs0^r^fnn+de{W_-o}=%7YaW`jZOfUidpb=4)i0KIxvn=lx9shS
zHz}S*+FEtx>5fP3JwE=JB>#SIies!l=j5)lQj2Z`m}KWlv>H|0<j#^!z8pAP*=w_-
zT$B4o{!24Y?pbA{8W<6KKvJSo=;*s2F@at2?&lTV(mTu3{++w4x88j9>8~8~Z!9)i
zx6QJWcZ$N4z1<u0=I)X-uv%!+IQ`XE?qH5<cm5|a*j0b+iQd?>>xT6{twm<vU9?WE
zldq0szP$0BNKfg}(gV90BfECbti7ULUh91~J@=)dY~NQw+4~&-jDNVt`PKh_#2Ww)
zDSco_nQrVTHUfITl!<|X8yHfVdBr7(IXQYoDQVz<lENNPt&`5?9WoGTd0)q6K1a6w
zf?S7_hS1Y%-r8UH2{2FlBYe|a_}3THHWq=Yv!c`X-<;bSbk2J1i|qw*QO4fKGN+x<
zb~@Gnw5zT2<K~q98zOVI=g!_X`N}R?dH?0X^PbwApSgPb8F^l(<2~}KE%WAO=(Ozn
zob_9IYSRvd^PAH3zMQo6I%Mf}dg%vgb!i?$jWur>(@!3p`RI|>l8+o-hP-Zht;Zi&
z#cH+crNsVR?7r}GIFE3B^~xvd2@U$bt378Ojr?!Nkez*Taq}{#a)F6Ey`J>g8oGs_
zlUkC$Y}tFJL{5$=8(&K?M}^Ec6pOuL%Dwi}4jsM|2NZYAJ>Ys-V<)4`q&xK!;-r6-
zojvxxHR!*{JK3mN>%GqxvgEZ?3+#%kw`>kyc>bt-lk2B`r6-Mgl|R;Nec}%AW@M6M
z#+5`R7(f8XWLVM&VxcBjR!D+Hivwg6F{2XML@gkN60xk1h{Z6Fl?|kc2?!Sh>4i)n
F9sqbtL{b0%
rename from toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi
rename to toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_1.xpi
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5b50b8556dcf426f67b134cfc084672e08bb5b53
GIT binary patch
literal 858
zc$^FHW@Zs#U}E54D9X@wPdT;n`4a{Ph7F7i3_J`n3`zO<CB-F0i3NID#i1db49qi(
zKZo`MacKoN10%~fpfaFt28O8<{JohSd5*sSt$Aq9wk>D6?&&lIRKHl-<+|SJ+_JYP
z-lTXMX=~M$r#l|C_xSi@lKlI<DUPxJoRhoGN-eq(V3M6H(P~s}lRHZ``EuZFWv|VS
za!u|V`7g~pxo4G)YG6d{0ZEBUp`-7f!~}N5yPsEdOYbaC`*-fH-g@)Zr@wN@-(GCA
zZkuH#?-Ye8d%HL0&D|wwV71Vsar&#T+`$~z?)*<;u&e&s6TPu%*A44^T8qrSyJ($S
zCtn@Oe0k$Lk)G0{r3ZF1Mt1F<S$jphyw>|}dhSa@*}kuWviCXu8UJvP^Q-^=h&KQp
zQf0u9ik&uPwh_?#rA!PA+`y2^%quQQ%*oL!N=X9;)O_p#)jH{H-XQ~lmiKjB=5u7*
zFUWN`X$U>N=B@p8p8)f;Kf*V?g@1i9ZDSFbIx9MD|IN9bLFcU3zSv$67iH{yEOXi!
zZKqTHPrKSWKW<LxzacVbd+zLQldtTOmG@sBJnyN^`I)P?pONQvI^H9%+A?omhEB`A
z&so2fr#9_SIKL@f@5@PBuS1qzr<Z<^R+r{6)L8SDG5zGhnU5Z6E&0gdWytH6*LwVc
zRjgL4UP|oG#qJB$*Gs7V`xbQa`~imeSz(p4jz<2sW5~|FxVU+lQ@OyzonB9RYz^JQ
z&q*!GU$*Q$Qz9qFl#Q>Yn4?1G8;ZqVG38#XRv0NWq0za(y0Lp%U=g#0%Fh1^d(FPQ
zoz;7vd+9&TJJwrF;-}Ah%yEa~yF$s{e?@HTo#uDjvvfa^cRj%z`{_e`=o9e(Z$>6L
zW?V^Bf&m18Ook<mAQozZWrZYIv^YRE5i=^0P1FKXC=tsFiC7E+S=m6En1FCGkY30H
G;sF5a)<`M<
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..91a7d9ffc1e848636ef732a8864a6d4cb0ee8869
GIT binary patch
literal 858
zc$^FHW@Zs#U}E54sLs%KKl$RHz!L@rh7F7i3_J`n3`zO<CB-F0i3NID#i1db49u&H
zKZh;^;?fFk21b@|KxIJP3=C5z_<J)u@*I8tTl3JIZClQ8?TutvSoLD5Xm?!d9pBp%
zXP%st5*G63-MOZ&`U#Ids@mVHJ~=7LpL258*;maG@g~{160JttD{h%oA5#mO&g`|>
zRj#HxNnT~v$wjN8ycTWP)nIJ!Nu{f>Zk@(a{Y#tq4@N%P8@Z==`}FI#ucp}^Dy|MX
zc2#eAn62uE)b~y+te5Ap_4sM{K3H1y_mD`F>G%B-4d>%*AN#FYpvT|0zH7x<+lgJ9
zULTLUcA#X9HEVKgq3nk|hiQ-U(&WFMioGAiA8B3Xb3FNWLtj42f5|_}`pfs%e`E`Q
zhg20Xq*zT=ju-*GU&_S5zzqzk%)H`~#GD+xqLegnKrP1}P_2{B<~17#w7jq5nm(tO
zb4$B|lT_!kYu?&l-!)8*_{6_yy3?;OrE?sFmIm!K{C)Nv&!PxkuSfZLa}BB_&puX(
zn#v(6f0gZbXW8i&3>ThF`Bp3cJu>IbymR8+tiD0BmK`y(e$*#*!(q#8SC0Af63*=0
z^;zq)@{*K9$5|@pr>ve_E%vimLR_?btElb|i5a=R?59_3w%Wg=XrZiuW@l2=n;p#W
ztzxxyoN^7`wU~L`rtlBK<<%<}q$f1#`<~YEJCsv1zhSN6g9DqkvE2T6AV#(1*gTGd
z(%B{t%<n!Z=UbC>z(Dc3=`78-y0(alyCz4rpE|V3z(JV(%sb09x5K(-9h~yyUvtl`
zE9Dv4<qv}X1y_`4EZnbW`<!uy;5$dNo?oXW_i!ca{SwfbP%q-hU*h<;K6FuhfHxzP
z95b#YD!~8(KqkYIMi2`%!LmXUELt2On}`{e$R=t5DU^t1g+wfdfvjvGO-w+z7)UQ<
G0`UOO<xU0w
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a11a8eb494f1cb2e25f1016a71b43bc7f9c936cd
GIT binary patch
literal 857
zc$^FHW@Zs#U}E54NY2o9f4d<%>Ink_!v;nM1|9|(hNS%blH!u0!~(sn;?NLI2IflR
z&!I&?Tw1}+z{v6qs0^r^fnn+de{W_-o}=%7YaW`jZOa+0icK6&Zx>G265D&qV)i!W
z&68D6u37Qp?p#*U{|ZMRd)MvxK8dx|R@Fz#EW0Dg`Qn<bi6W=oeB6>**5m7*+oy4s
zt&&6TsGUc;+UHfDrnn@QGNvay@f6uTx!6^+PS&<vVy^1FnLn+|ckj8kM(?_EO-}!*
z9k-r6F%4)6_@;MkSLv&?120-GvBa&quJ5B5QT)G~@yDC1#(77%UM0Q%v8v<N9pRN4
zyX)Sh^7<U#6>M-#^g82vwiL~GY2O0Z-}$OrI)CfZ<0Tf?+)HYdf1H0HUupUGZ?|~>
zJfsqVA+`MRgVTmU@0T($FmMAyDl@ORBrzvPuP7xA98jg$1FCh>*}Ovr0xj?BxXkDD
zI(zZED2j9zy<R%CDqe_r+8^PY-on4Wn6|M9Oq~^-w*Thb&LADDwJ)|8#6=nVt35V}
zoYEp9zuJ^n`0?|TavM)t=HD*6W7og-_Q#VsW{)$EZSL7wb^6amg_1Kr0-avG>^v6p
z!g~ES`ykB@?oB3hix&F73lusR7`3Kl{hS<wBMUZcWS^tXp8ly@d-^eDjiV+KJ4NN1
zUze;Du$o@_vR|%s-oB(6KW=-o+A}lPWJa|uD!jIz|G=@RFE18oUX*o7Stn$f?B8>7
z>t?o>vsb>{?GVApl=^1Zu?DfH={>H~OV2v3&WsUbPkA74$M3<W%qeRa+Eu>GFS*{f
zFE;q+-hfy05AAjK{(SX&-+|r&ey8iN_0vCSecAcLd4tElz!sYeto;8&J?j_)ycwC~
zm~o|02?h`VG8vXMf>@~Ol@*d+(V_s^M9i2(Hc<;mp~NdIBwjHLWMu<sVgka&Kzaca
Ghz9`i<5GnH
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f4e07bf300d17fb2306c1dc189b5fafea56a952a
GIT binary patch
literal 857
zc$^FHW@Zs#U}E54$jQ)lf3j88<0%6J!v;nM1|9|(hNS%blH!u0!~(sn;?NLI2Ig+#
z&!M$ITw1}+z{v6qs0^r^fnn+de{W_-o}=%7YaW`jZOfTg2fJMbviSFgY<v-V?B*8P
znR9hK(_D|ff2XtJ2fL<R{HMozr|YbEt(h3KG16H?x+`rh@8W>8{^h=#lXI-Hid{0z
zPB?$i_~vM3JTrXiauu%C+!oBr{)=wRoulpb@%0PKBQJD5o;!1Y=k2Fo(@J;BJxs0+
zId;`deSM=)%cgR}YionencdW<sC@|9TbIanaMtg5o&)LY=M`#)1&CK44|l!tY@Ubf
z&9BM&*$GzH<~D6yw}Y?3T*150d~<)*({=ePf3MNCU3Pe*-J-|R2kY7XpE&*Uef%EA
z0C-Sk1A}UMx=)P((Ep`O3=G`BpvufEE=kPE(JM+x1BX;6_K<3wbT;phfk4aqIxh1$
zz0Og5E{Y<ZMX#4mt%?_7p7uxhrnm5~FQ#oQ0#j#2r|rKvw=+n`YVC{d1#wZv{%Vg+
zBB!*7$gei#6@L8uq};}nmif2K?%4IOz5VfIj@jePW1D++R-OKHQK96_k3gpvFFTLL
zys%!s%|1x8gL{)n?L(*ecNVM6S-d5JGv4xMLWg5wGQWjC-?=B^;d;I90bQ8}McOuO
z*Ix!Hmgv2{ByY=QTYJ>t<M!!Xb!==uE^Xm)eY~b#zM*&Pi;IrIi>y0N#Hbb>n<ue2
zJDvBEdGO`(#tlp?r*4$?GU}c>FVU^{dRA-L<sCYFCk`m?n0p}gvPL8$pXWP!kM+E@
zyO({cUU<d6wYq)Uv(@iq8>Ju0wXC~-J~O|?PQ0E&?4*6y1omAI9`4sVsUP6Y$Rx*%
zD}_ohfB=xmu%r>hLQSu%ko1Zc1IQ*~MkKO{+CT~=T3I2{ieVrt8%Prq5H1GN3z$GW
E01_-l`v3p{
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js
@@ -0,0 +1,264 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
+Components.utils.import("resource://testing-common/httpd.js");
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
+const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", AM_Ci.nsIFile, "initWithPath");
+
+let testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/productaddons"));
+testserver.start();
+let root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+
+/**
+ * Compares binary data of 2 arrays and returns true if they are the same
+ *
+ * @param arr1 The first array to compare
+ * @param arr2 The second array to compare
+*/
+function compareBinaryData(arr1, arr2) {
+  do_check_eq(arr1.length, arr2.length);
+  for (let i = 0; i < arr1.length; i++) {
+    if (arr1[i] != arr2[i]) {
+      do_print("Data differs at index " + i +
+               ", arr1: " + arr1[i] + ", arr2: " + arr2[i]);
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Reads a file's data and returns it
+ *
+ * @param file The file to read the data from
+ * @return array of bytes for the data in the file.
+*/
+function getBinaryFileData(file) {
+  let fileStream = AM_Cc["@mozilla.org/network/file-input-stream;1"].
+                   createInstance(AM_Ci.nsIFileInputStream);
+  // Open as RD_ONLY with default permissions.
+  fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+
+  let stream = AM_Cc["@mozilla.org/binaryinputstream;1"].
+               createInstance(AM_Ci.nsIBinaryInputStream);
+  stream.setInputStream(fileStream);
+  let bytes = stream.readByteArray(stream.available());
+  fileStream.close();
+  return bytes;
+}
+
+/**
+ * Compares binary data of 2 files and returns true if they are the same
+ *
+ * @param file1 The first file to compare
+ * @param file2 The second file to compare
+*/
+function compareFiles(file1, file2) {
+  return compareBinaryData(getBinaryFileData(file1), getBinaryFileData(file2));
+}
+
+add_task(function* test_404() {
+  try {
+    let addons = yield ProductAddonChecker.getProductAddonList(root + "404.xml");
+    do_throw("Should not have returned anything");
+  }
+  catch (e) {
+    do_check_true(true, "Expected to throw for a missing update file");
+  }
+});
+
+add_task(function* test_not_xml() {
+  try {
+    let addons = yield ProductAddonChecker.getProductAddonList(root + "bad.txt");
+    do_throw("Should not have returned anything");
+  }
+  catch (e) {
+    do_check_true(true, "Expected to throw for a non XML result");
+  }
+});
+
+add_task(function* test_invalid_xml() {
+  try {
+    let addons = yield ProductAddonChecker.getProductAddonList(root + "bad.xml");
+    do_throw("Should not have returned anything");
+  }
+  catch (e) {
+    do_check_true(true, "Expected to throw for invalid XML");
+  }
+});
+
+add_task(function* test_wrong_xml() {
+  try {
+    let addons = yield ProductAddonChecker.getProductAddonList(root + "bad2.xml");
+    do_throw("Should not have returned anything");
+  }
+  catch (e) {
+    do_check_true(true, "Expected to throw for a missing <updates> tag");
+  }
+});
+
+add_task(function* test_missing() {
+  let addons = yield ProductAddonChecker.getProductAddonList(root + "missing.xml");
+  do_check_eq(addons, null);
+});
+
+add_task(function* test_empty() {
+  let addons = yield ProductAddonChecker.getProductAddonList(root + "empty.xml");
+  do_check_true(Array.isArray(addons));
+  do_check_eq(addons.length, 0);
+});
+
+add_task(function* test_good_xml() {
+  let addons = yield ProductAddonChecker.getProductAddonList(root + "good.xml");
+  do_check_true(Array.isArray(addons));
+
+  // There are three valid entries in the XML
+  do_check_eq(addons.length, 5);
+
+  let addon = addons[0];
+  do_check_eq(addon.id, "test1");
+  do_check_eq(addon.URL, "http://example.com/test1.xpi");
+  do_check_eq(addon.hashFunction, undefined);
+  do_check_eq(addon.hashValue, undefined);
+  do_check_eq(addon.version, undefined);
+  do_check_eq(addon.size, undefined);
+
+  addon = addons[1];
+  do_check_eq(addon.id, "test2");
+  do_check_eq(addon.URL, "http://example.com/test2.xpi");
+  do_check_eq(addon.hashFunction, "md5");
+  do_check_eq(addon.hashValue, "djhfgsjdhf");
+  do_check_eq(addon.version, undefined);
+  do_check_eq(addon.size, undefined);
+
+  addon = addons[2];
+  do_check_eq(addon.id, "test3");
+  do_check_eq(addon.URL, "http://example.com/test3.xpi");
+  do_check_eq(addon.hashFunction, undefined);
+  do_check_eq(addon.hashValue, undefined);
+  do_check_eq(addon.version, "1.0");
+  do_check_eq(addon.size, 45);
+
+  addon = addons[3];
+  do_check_eq(addon.id, "test4");
+  do_check_eq(addon.URL, undefined);
+  do_check_eq(addon.hashFunction, undefined);
+  do_check_eq(addon.hashValue, undefined);
+  do_check_eq(addon.version, undefined);
+  do_check_eq(addon.size, undefined);
+
+  addon = addons[4];
+  do_check_eq(addon.id, undefined);
+  do_check_eq(addon.URL, "http://example.com/test5.xpi");
+  do_check_eq(addon.hashFunction, undefined);
+  do_check_eq(addon.hashValue, undefined);
+  do_check_eq(addon.version, undefined);
+  do_check_eq(addon.size, undefined);
+});
+
+add_task(function* test_download_nourl() {
+  try {
+    let path = yield ProductAddonChecker.downloadAddon({});
+
+    yield OS.File.remove(path);
+    do_throw("Should not have downloaded a file with a missing url");
+  }
+  catch (e) {
+    do_check_true(true, "Should have thrown when downloading a file with a missing url.");
+  }
+});
+
+add_task(function* test_download_missing() {
+  try {
+    let path = yield ProductAddonChecker.downloadAddon({
+      URL: root + "nofile.xpi",
+    });
+
+    yield OS.File.remove(path);
+    do_throw("Should not have downloaded a missing file");
+  }
+  catch (e) {
+    do_check_true(true, "Should have thrown when downloading a missing file.");
+  }
+});
+
+add_task(function* test_download_noverify() {
+  let path = yield ProductAddonChecker.downloadAddon({
+    URL: root + "unsigned.xpi",
+  });
+
+  let stat = yield OS.File.stat(path);
+  do_check_false(stat.isDir);
+  do_check_eq(stat.size, 452)
+
+  do_check_true(compareFiles(do_get_file("data/productaddons/unsigned.xpi"), new LocalFile(path)));
+
+  yield OS.File.remove(path);
+});
+
+add_task(function* test_download_badsize() {
+  try {
+    let path = yield ProductAddonChecker.downloadAddon({
+      URL: root + "unsigned.xpi",
+      size: 400,
+    });
+
+    yield OS.File.remove(path);
+    do_throw("Should not have downloaded a file with a bad size");
+  }
+  catch (e) {
+    do_check_true(true, "Should have thrown when downloading a file with a bad size.");
+  }
+});
+
+add_task(function* test_download_badhashfn() {
+  try {
+    let path = yield ProductAddonChecker.downloadAddon({
+      URL: root + "unsigned.xpi",
+      hashFunction: "sha2567",
+      hashValue: "9b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3",
+    });
+
+    yield OS.File.remove(path);
+    do_throw("Should not have downloaded a file with a bad hash function");
+  }
+  catch (e) {
+    do_check_true(true, "Should have thrown when downloading a file with a bad hash function.");
+  }
+});
+
+add_task(function* test_download_badhash() {
+  try {
+    let path = yield ProductAddonChecker.downloadAddon({
+      URL: root + "unsigned.xpi",
+      hashFunction: "sha256",
+      hashValue: "8b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3",
+    });
+
+    yield OS.File.remove(path);
+    do_throw("Should not have downloaded a file with a bad hash");
+  }
+  catch (e) {
+    do_check_true(true, "Should have thrown when downloading a file with a bad hash.");
+  }
+});
+
+add_task(function* test_download_works() {
+  let path = yield ProductAddonChecker.downloadAddon({
+    URL: root + "unsigned.xpi",
+    size: 452,
+    hashFunction: "sha256",
+    hashValue: "9b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3",
+  });
+
+  let stat = yield OS.File.stat(path);
+  do_check_false(stat.isDir);
+
+  do_check_true(compareFiles(do_get_file("data/productaddons/unsigned.xpi"), new LocalFile(path)));
+
+  yield OS.File.remove(path);
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 var GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
 var gMockAddons = new Map();
@@ -333,17 +334,17 @@ add_task(function* test_pluginRegistrati
     yield promiseRestartManager();
     Assert.equal(addedPaths.indexOf(file.path), -1);
     Assert.deepEqual(removedPaths, [file.path]);
 
     // Setting the ABI to expected ABI should cause registration at startup.
     clearPaths();
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
                        TEST_VERSION);
-    gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ABI, addon.id), GMPScope.GMPUtils.ABI());
+    gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ABI, addon.id), UpdateUtils.ABI);
     yield promiseRestartManager();
     Assert.notEqual(addedPaths.indexOf(file.path), -1);
     Assert.deepEqual(removedPaths, []);
 
     // Check that clearing the version doesn't trigger registration.
     clearPaths();
     gPrefs.clearUserPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id));
     Assert.deepEqual(addedPaths, []);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -1,19 +1,31 @@
 // Tests that we reset to the default system add-ons correctly when switching
 // application versions
 const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
 
 // Enable signature checks for these tests
 Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
 
-const featureDir = gProfD.clone();
-featureDir.append("features");
+const featureDir = FileUtils.getDir("ProfD", ["features"]);
+
+// Build the test sets
+let dir = FileUtils.getDir("ProfD", ["sysfeatures", "app1", "features"], true);
+do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
 
-const distroDir = do_get_file("data/system_addons/app0");
+dir = FileUtils.getDir("ProfD", ["sysfeatures", "app2", "features"], true);
+do_get_file("data/system_addons/system1_2.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system3_1.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi");
+
+dir = FileUtils.getDir("ProfD", ["sysfeatures", "app3", "features"], true);
+do_get_file("data/system_addons/system1_1_badcert.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system3_1.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi");
+
+const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true);
 registerDirectory("XREAppDist", distroDir);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
 
 function makeUUID() {
   let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"].
                 getService(AM_Ci.nsIUUIDGenerator);
   return uuidGen.generateUUID().toString();
@@ -131,20 +143,20 @@ add_task(function* test_downgrade() {
 // Fake a mid-cycle install
 add_task(function* test_updated() {
   // Create a random dir to install into
   let dirname = makeUUID();
   FileUtils.getDir("ProfD", ["features", dirname], true);
   featureDir.append(dirname);
 
   // Copy in the system add-ons
-  let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi");
-  file.copyTo(featureDir, file.leafName);
-  file = do_get_file("data/system_addons/app2/features/system3@tests.mozilla.org.xpi");
-  file.copyTo(featureDir, file.leafName);
+  let file = do_get_file("data/system_addons/system2_1.xpi");
+  file.copyTo(featureDir, "system2@tests.mozilla.org.xpi");
+  file = do_get_file("data/system_addons/system3_1.xpi");
+  file.copyTo(featureDir, "system3@tests.mozilla.org.xpi");
 
   // Inject it into the system set
   let addonSet = {
     schema: 1,
     directory: featureDir.leafName,
     addons: {
       "system2@tests.mozilla.org": {
         version: "1.0"
@@ -161,18 +173,18 @@ add_task(function* test_updated() {
   yield check_installed(true, null, "1.0", "1.0");
 
   yield promiseShutdownManager();
 });
 
 // An additional add-on in the directory should be ignored
 add_task(function* test_skips_additional() {
   // Copy in the system add-ons
-  let file = do_get_file("data/system_addons/app1/features/system1@tests.mozilla.org.xpi");
-  file.copyTo(featureDir, file.leafName);
+  let file = do_get_file("data/system_addons/system1_1.xpi");
+  file.copyTo(featureDir, "system1@tests.mozilla.org.xpi");
 
   startupManager(false);
 
   yield check_installed(true, null, "1.0", "1.0");
 
   yield promiseShutdownManager();
 });
 
@@ -186,18 +198,18 @@ add_task(function* test_revert() {
   // the default set which is system add-ons 1 and 2.
   yield check_installed(false, "1.0", "1.0", null);
 
   yield promiseShutdownManager();
 });
 
 // Putting it back will make the set work again
 add_task(function* test_reuse() {
-  let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi");
-  file.copyTo(featureDir, file.leafName);
+  let file = do_get_file("data/system_addons/system2_1.xpi");
+  file.copyTo(featureDir, "system2@tests.mozilla.org.xpi");
 
   startupManager(false);
 
   yield check_installed(true, null, "1.0", "1.0");
 
   yield promiseShutdownManager();
 });
 
@@ -209,18 +221,18 @@ add_task(function* test_corrupt_pref() {
 
   yield check_installed(false, "1.0", "1.0", null);
 
   yield promiseShutdownManager();
 });
 
 // An add-on with a bad certificate should cause us to use the default set
 add_task(function* test_bad_profile_cert() {
-  let file = do_get_file("data/system_addons/app3/features/system1@tests.mozilla.org.xpi");
-  file.copyTo(featureDir, file.leafName);
+  let file = do_get_file("data/system_addons/system1_1_badcert.xpi");
+  file.copyTo(featureDir, "system1@tests.mozilla.org.xpi");
 
   // Inject it into the system set
   let addonSet = {
     schema: 1,
     directory: featureDir.leafName,
     addons: {
       "system1@tests.mozilla.org": {
         version: "2.0"
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
@@ -0,0 +1,469 @@
+// Tests that we reset to the default system add-ons correctly when switching
+// application versions
+const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
+const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
+const PREF_XPI_STATE                  = "extensions.xpiState";
+const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
+
+Components.utils.import("resource://testing-common/httpd.js");
+const { computeHash } = Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
+
+// Enable signature checks for these tests
+//Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
+
+const featureDir = FileUtils.getDir("ProfD", ["features"]);
+
+// Build the test sets
+let dir = FileUtils.getDir("ProfD", ["features", "prefilled"], true);
+do_get_file("data/system_addons/system2_2.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system3_2.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi");
+
+// Mark these in the past so the startup file scan notices when files have changed properly
+FileUtils.getFile("ProfD", ["features", "prefilled", "system2@tests.mozilla.org.xpi"]).lastModifiedTime -= 10000;
+FileUtils.getFile("ProfD", ["features", "prefilled", "system3@tests.mozilla.org.xpi"]).lastModifiedTime -= 10000;
+
+const prefilledSet = {
+  schema: 1,
+  directory: dir.leafName,
+  addons: {
+    "system2@tests.mozilla.org": {
+      version: "2.0"
+    },
+    "system3@tests.mozilla.org": {
+      version: "2.0"
+    },
+  }
+};
+
+dir = FileUtils.getDir("ProfD", ["sysfeatures", "hidden", "features"], true);
+do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
+
+dir = FileUtils.getDir("ProfD", ["sysfeatures", "prefilled", "features"], true);
+do_get_file("data/system_addons/system2_2.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system3_2.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi");
+
+const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppDist", distroDir);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+let testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+let root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+function makeUUID() {
+  let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"].
+                getService(AM_Ci.nsIUUIDGenerator);
+  return uuidGen.generateUUID().toString();
+}
+
+function* serve_update(xml, perform_update) {
+  testserver.registerPathHandler("/data/update.xml", (request, response) => {
+    response.write(xml);
+  });
+
+  try {
+    yield perform_update();
+  }
+  finally {
+    testserver.registerPathHandler("/data/update.xml", null);
+  }
+}
+
+// Runs an update check making it use the passed in xml string. Uses the direct
+// call to the update function so we get rejections on failure.
+function* install_system_addons(xml) {
+  do_print("Triggering system add-on update check.");
+
+  yield serve_update(xml, function*() {
+    let { XPIProvider } = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
+    yield XPIProvider.updateSystemAddons();
+  });
+}
+
+// Runs a full add-on update check which will in some cases do a system add-on
+// update check. Always succeeds.
+function* update_all_addons(xml) {
+  do_print("Triggering full add-on update check.");
+
+  yield serve_update(xml, function() {
+    return new Promise(resolve => {
+      Services.obs.addObserver(function() {
+        Services.obs.removeObserver(arguments.callee, "addons-background-update-complete");
+
+        resolve();
+      }, "addons-background-update-complete", false);
+
+      // Trigger the background update timer handler
+      gInternalManager.notify(null);
+    });
+  });
+}
+
+// Builds an update.xml file for an update check based on the data passed.
+function* build_xml(addons) {
+  let xml = `<?xml version="1.0" encoding="UTF-8"?>\n\n<updates>\n`;
+  if (addons) {
+    xml += `  <addons>\n`;
+    for (let addon of addons) {
+      xml += `    <addon id="${addon.id}" URL="${root + addon.path}" version="${addon.version}"`;
+      if (addon.size)
+        xml += ` size="${addon.size}"`;
+      if (addon.hashFunction)
+        xml += ` hashFunction="${addon.hashFunction}"`;
+      if (addon.hashValue)
+        xml += ` hashValue="${addon.hashValue}"`;
+      xml += `/>\n`;
+    }
+    xml += `  </addons>\n`;
+  }
+  xml += `</updates>\n`;
+
+  return xml;
+}
+
+function* check_installed(inProfile, ...versions) {
+  for (let i = 0; i < versions.length; i++) {
+    let id = "system" + (i + 1) + "@tests.mozilla.org";
+    let addon = yield promiseAddonByID(id);
+
+    if (versions[i]) {
+      // Add-on should be installed
+      do_check_neq(addon, null);
+      do_check_eq(addon.version, versions[i]);
+      do_check_true(addon.isActive);
+      do_check_false(addon.foreignInstall);
+
+      // Verify the add-ons file is in the right place
+      let uri = addon.getResourceURI(null);
+      do_check_true(uri instanceof AM_Ci.nsIFileURL);
+
+      let file = uri.file.parent;
+      if (inProfile) {
+        file = file.parent;
+        do_check_eq(file.leafName, "features");
+        file = file.parent;
+        do_check_eq(file.path, gProfD.path);
+      }
+      else {
+        do_check_eq(file.leafName, "features");
+        file = file.parent;
+        do_check_eq(file.path, distroDir.path);
+      }
+
+      //do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
+
+      // Verify the add-on actually started
+      let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
+      do_check_eq(installed, versions[i]);
+    }
+    else {
+      if (inProfile) {
+        // Add-on should not be installed
+        do_check_eq(addon, null);
+      }
+      else {
+        // Either add-on should not be installed or it shouldn't be active
+        do_check_true(!addon || !addon.isActive);
+      }
+
+      try {
+        Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
+        do_throw("Expected pref to be missing");
+      }
+      catch (e) {
+      }
+    }
+  }
+}
+
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup: function*() {
+      Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET);
+      distroDir.leafName = "empty";
+    },
+    initialState: [false, null, null, null, null, null],
+  },
+
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup: function*() {
+      Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET);
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [false, null, "2.0", "2.0", null, null],
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup: function*() {
+      Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(prefilledSet));
+      distroDir.leafName = "empty";
+    },
+    initialState: [true, null, "2.0", "2.0", null, null],
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup: function*() {
+      Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(prefilledSet));
+      distroDir.leafName = "hidden";
+    },
+    initialState: [true, null, "2.0", "2.0", null, null],
+  },
+};
+
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Test that an error response does nothing
+  error: {
+    test: function*() {
+      try {
+        yield install_system_addons("foobar");
+        do_throw("Expected to fail the update check");
+      }
+      catch (e) {
+        do_check_true(true, "Expected to fail the update check");
+      }
+    },
+  },
+
+  // Test that a blank response does nothing
+  blank: {
+    updateList: null,
+  },
+
+  // Test that an empty list updates to an empty set of system add-ons
+  empty: {
+    updateList: [],
+    finalState: [true, null, null, null, null, null]
+  },
+
+  // Tests that a new set of system add-ons gets installed
+  newset: {
+    updateList: [
+      { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" },
+      { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi" }
+    ],
+    finalState: [true, null, null, null, "1.0", "1.0"]
+  },
+
+  // Tests that an upgraded set of system add-ons gets installed
+  upgrades: {
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi" },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
+    ],
+    finalState: [true, null, "3.0", "3.0", null, null]
+  },
+
+  // Tests that a set of system add-ons, some new, some existing gets installed
+  overlapping: {
+    updateList: [
+      { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_2.xpi" },
+      { id: "system2@tests.mozilla.org", version: "1.0", path: "system2_2.xpi" },
+      { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_3.xpi" },
+      { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
+    ],
+    finalState: [true, "2.0", "2.0", "3.0", "1.0", null]
+  },
+
+  // Specifying an incorrect version should stop us updating anything
+  badVersion: {
+    fails: true,
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "4.0", path: "system2_3.xpi" },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
+    ],
+  },
+
+  // Specifying an invalid size should stop us updating anything
+  badSize: {
+    fails: true,
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 2 },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
+    ],
+  },
+
+  // Specifying an incorrect hash should stop us updating anything
+  badHash: {
+    fails: true,
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi" },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "205a4c49bd513ebd30594e380c19e86bba1f83e2" }
+    ],
+  },
+
+  // Correct sizes and hashes should work
+  checkSizeHash: {
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 858 },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "105a4c49bd513ebd30594e380c19e86bba1f83e2" },
+      { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 857, hashFunction: "sha1", hashValue: "664e9218be3c9acbb9029e715c1e5d2fbb4ea2cc" }
+    ],
+    finalState: [true, null, "3.0", "3.0", null, "1.0"]
+  },
+}
+
+add_task(function* setup() {
+  // Initialise the profile
+  startupManager();
+  yield promiseShutdownManager();
+})
+
+function* setup_conditions(setup) {
+  do_print("Setting up conditions.");
+  yield setup.setup();
+
+  // Blow away the cache to force a rescan of the filesystem
+  Services.prefs.clearUserPref(PREF_XPI_STATE);
+  startupManager(false);
+
+  // Make sure the initial state is correct
+  do_print("Checking initial state.");
+  yield check_installed(...setup.initialState);
+}
+
+function* verify_state(finalState) {
+  do_print("Checking final state.");
+
+  // Bug 1204156: Currently switching to the new state requires a restart
+  // yield check_installed(...finalState);
+
+  // Check that the new state is active after a restart
+  yield promiseRestartManager();
+  yield check_installed(...finalState);
+}
+
+function* exec_test(setup, test) {
+  yield setup_conditions(setup);
+
+  try {
+    if ("test" in test) {
+      yield test.test();
+    }
+    else {
+      yield install_system_addons(yield build_xml(test.updateList));
+    }
+
+    if (test.fails) {
+      do_throw("Expected this test to fail");
+    }
+  }
+  catch (e) {
+    if (!test.fails) {
+      do_throw(e);
+    }
+  }
+
+  yield verify_state(test.finalState ? test.finalState : setup.initialState);
+
+  yield promiseShutdownManager();
+}
+
+for (let setup of Object.keys(TEST_CONDITIONS)) {
+  for (let test of Object.keys(TESTS)) {
+    add_task(function*() {
+      do_print("Running test " + setup + " " + test);
+
+      yield exec_test(TEST_CONDITIONS[setup], TESTS[test]);
+    });
+  }
+}
+
+// Some custom tests
+
+// Test that the update check is performed as part of the regular add-on update
+// check
+add_task(function* test_addon_update() {
+  yield setup_conditions(TEST_CONDITIONS.blank);
+
+  yield update_all_addons(yield build_xml([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ]));
+
+  yield verify_state([true, null, "2.0", "2.0", null, null]);
+
+  yield promiseShutdownManager();
+});
+
+// Disabling app updates should block system add-on updates
+add_task(function* test_app_update_disabled() {
+  yield setup_conditions(TEST_CONDITIONS.blank);
+
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
+  yield update_all_addons(yield build_xml([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ]));
+  Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
+
+  yield verify_state(TEST_CONDITIONS.blank.initialState);
+
+  yield promiseShutdownManager();
+});
+
+// Tests that a set that matches the hidden default set works
+add_task(function* test_match_default() {
+  yield setup_conditions(TEST_CONDITIONS.withBothSets);
+
+  yield install_system_addons(yield build_xml([
+    { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1.xpi" },
+    { id: "system2@tests.mozilla.org", version: "1.0", path: "system2_1.xpi" }
+  ]));
+
+  // Bug 1204159: This should revert to the default set instead of installing
+  // new versions into the updated set.
+  //yield verify_state([false, "1.0", "1.0", null, null, null]);
+  yield verify_state([true, "1.0", "1.0", null, null, null]);
+
+  yield promiseShutdownManager();
+});
+
+// Tests that a set that matches the current set works
+add_task(function* test_match_current() {
+  yield setup_conditions(TEST_CONDITIONS.withBothSets);
+
+  yield install_system_addons(yield build_xml([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ]));
+
+  // Bug 1204159: This should remain with the current set instead of creating
+  // a new copy
+  //let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
+  //do_check_eq(set.directory, "prefilled");
+
+  yield verify_state([true, null, "2.0", "2.0", null, null]);
+
+  yield promiseShutdownManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -18,14 +18,16 @@ skip-if = appname != "firefox"
 [test_hotfix_cert.js]
 [test_isReady.js]
 [test_metadata_update.js]
 [test_pluginInfoURL.js]
 [test_provider_markSafe.js]
 [test_provider_shutdown.js]
 [test_provider_unsafe_access_shutdown.js]
 [test_provider_unsafe_access_startup.js]
+[test_ProductAddonChecker.js]
 [test_shutdown.js]
+[test_system_update.js]
 [test_system_reset.js]
 [test_XPIcancel.js]
 [test_XPIStates.js]
 
 [include:xpcshell-shared.ini]
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -240,19 +240,16 @@ var Harness = {
 
   // Install blocked handling
 
   installDisabled: function(installInfo) {
     ok(!!this.installDisabledCallback, "Installation shouldn't have been disabled");
     if (this.installDisabledCallback)
       this.installDisabledCallback(installInfo);
     this.expectingCancelled = true;
-    installInfo.installs.forEach(function(install) {
-      install.cancel();
-    });
     this.expectingCancelled = false;
     this.endTest();
   },
 
   installCancelled: function(installInfo) {
     if (this.expectingCancelled)
       return;
 
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -25,18 +25,16 @@ const PREF_APP_UPDATE_BACKGROUND_INTERVA
 const PREF_APP_UPDATE_BACKGROUNDERRORS    = "app.update.backgroundErrors";
 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
 const PREF_APP_UPDATE_CANCELATIONS        = "app.update.cancelations";
 const PREF_APP_UPDATE_CERTS_BRANCH        = "app.update.certs.";
 const PREF_APP_UPDATE_CERT_CHECKATTRS     = "app.update.cert.checkAttributes";
 const PREF_APP_UPDATE_CERT_ERRORS         = "app.update.cert.errors";
 const PREF_APP_UPDATE_CERT_MAXERRORS      = "app.update.cert.maxErrors";
 const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
-const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";
-const PREF_APP_UPDATE_IMEI_HASH           = "app.update.imei_hash";
 const PREF_APP_UPDATE_ENABLED             = "app.update.enabled";
 const PREF_APP_UPDATE_IDLETIME            = "app.update.idletime";
 const PREF_APP_UPDATE_INCOMPATIBLE_MODE   = "app.update.incompatible.mode";
 const PREF_APP_UPDATE_INTERVAL            = "app.update.interval";
 const PREF_APP_UPDATE_LOG                 = "app.update.log";
 const PREF_APP_UPDATE_MODE                = "app.update.mode";
 const PREF_APP_UPDATE_NEVER_BRANCH        = "app.update.never.";
 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
@@ -49,21 +47,16 @@ const PREF_APP_UPDATE_URL               
 const PREF_APP_UPDATE_URL_DETAILS         = "app.update.url.details";
 const PREF_APP_UPDATE_URL_OVERRIDE        = "app.update.url.override";
 const PREF_APP_UPDATE_SERVICE_ENABLED     = "app.update.service.enabled";
 const PREF_APP_UPDATE_SERVICE_ERRORS      = "app.update.service.errors";
 const PREF_APP_UPDATE_SERVICE_MAX_ERRORS  = "app.update.service.maxErrors";
 const PREF_APP_UPDATE_SOCKET_ERRORS       = "app.update.socket.maxErrors";
 const PREF_APP_UPDATE_RETRY_TIMEOUT       = "app.update.socket.retryTimeout";
 
-const PREF_APP_DISTRIBUTION               = "distribution.id";
-const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
-
-const PREF_APP_B2G_VERSION                = "b2g.version";
-
 const PREF_EM_HOTFIX_ID                   = "extensions.hotfix.id";
 
 const URI_UPDATE_PROMPT_DIALOG  = "chrome://mozapps/content/update/updates.xul";
 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
 const URI_BRAND_PROPERTIES      = "chrome://branding/locale/brand.properties";
 const URI_UPDATES_PROPERTIES    = "chrome://mozapps/locale/update/updates.properties";
 const URI_UPDATE_NS             = "http://www.mozilla.org/2005/app-update";
 
@@ -82,17 +75,16 @@ const FILE_UPDATE_VERSION = "update.vers
 const FILE_UPDATE_ARCHIVE = "update.mar";
 const FILE_UPDATE_LINK    = "update.link";
 const FILE_UPDATE_LOG     = "update.log";
 const FILE_UPDATES_DB     = "updates.xml";
 const FILE_UPDATE_ACTIVE  = "active-update.xml";
 const FILE_PERMS_TEST     = "update.test";
 const FILE_LAST_LOG       = "last-update.log";
 const FILE_BACKUP_LOG     = "backup-update.log";
-const FILE_UPDATE_LOCALE  = "update.locale";
 
 const STATE_NONE            = "null";
 const STATE_DOWNLOADING     = "downloading";
 const STATE_PENDING         = "pending";
 const STATE_PENDING_SVC     = "pending-service";
 const STATE_APPLYING        = "applying";
 const STATE_APPLIED         = "applied";
 const STATE_APPLIED_OS      = "applied-os";
@@ -205,227 +197,34 @@ var gSDCardMountLock = null;
 // Gonk only
 XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
   if (AppConstants.platform != "gonk") {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   }
   return Services.env.get("EXTERNAL_STORAGE");
 });
 
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-                                  "resource://gre/modules/UpdateChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                  "resource://gre/modules/UpdateUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
   return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
   return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
 });
 
 // shared code for suppressing bad cert dialogs
 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
   let temp = { };
   Cu.import("resource://gre/modules/CertUtils.jsm", temp);
   return temp;
 });
 
-XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() {
-  let abi = null;
-  try {
-    abi = Services.appinfo.XPCOMABI;
-  }
-  catch (e) {
-    LOG("gABI - XPCOM ABI unknown: updates are not possible.");
-  }
-
-  if (AppConstants.platform == "macosx") {
-    // Mac universal build should report a different ABI than either macppc
-    // or mactel.
-    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
-                   getService(Ci.nsIMacUtils);
-
-    if (macutils.isUniversalBinary) {
-      abi += "-u-" + macutils.architecturesInBinary;
-    }
-  } else if (AppConstants.platform == "win") {
-    // Windows build should report the CPU architecture that it's running on.
-    abi += "-" + gWinCPUArch;
-  }
-  return abi;
-});
-
-XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
-  let osVersion;
-  try {
-    osVersion = Services.sysinfo.getProperty("name") + " " +
-                Services.sysinfo.getProperty("version");
-  }
-  catch (e) {
-    LOG("gOSVersion - OS Version unknown: updates are not possible.");
-  }
-
-  if (osVersion) {
-    if (AppConstants.platform == "win") {
-      const BYTE = ctypes.uint8_t;
-      const WORD = ctypes.uint16_t;
-      const DWORD = ctypes.uint32_t;
-      const WCHAR = ctypes.char16_t;
-      const BOOL = ctypes.int;
-
-      // This structure is described at:
-      // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
-      const SZCSDVERSIONLENGTH = 128;
-      const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
-          [
-          {dwOSVersionInfoSize: DWORD},
-          {dwMajorVersion: DWORD},
-          {dwMinorVersion: DWORD},
-          {dwBuildNumber: DWORD},
-          {dwPlatformId: DWORD},
-          {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
-          {wServicePackMajor: WORD},
-          {wServicePackMinor: WORD},
-          {wSuiteMask: WORD},
-          {wProductType: BYTE},
-          {wReserved: BYTE}
-          ]);
-
-      // This structure is described at:
-      // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
-      const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
-          [
-          {wProcessorArchitecture: WORD},
-          {wReserved: WORD},
-          {dwPageSize: DWORD},
-          {lpMinimumApplicationAddress: ctypes.voidptr_t},
-          {lpMaximumApplicationAddress: ctypes.voidptr_t},
-          {dwActiveProcessorMask: DWORD.ptr},
-          {dwNumberOfProcessors: DWORD},
-          {dwProcessorType: DWORD},
-          {dwAllocationGranularity: DWORD},
-          {wProcessorLevel: WORD},
-          {wProcessorRevision: WORD}
-          ]);
-
-      let kernel32 = false;
-      try {
-        kernel32 = ctypes.open("Kernel32");
-      } catch (e) {
-        LOG("gOSVersion - Unable to open kernel32! " + e);
-        osVersion += ".unknown (unknown)";
-      }
-
-      if (kernel32) {
-        try {
-          // Get Service pack info
-          try {
-            let GetVersionEx = kernel32.declare("GetVersionExW",
-                                                ctypes.default_abi,
-                                                BOOL,
-                                                OSVERSIONINFOEXW.ptr);
-            let winVer = OSVERSIONINFOEXW();
-            winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
-
-            if(0 !== GetVersionEx(winVer.address())) {
-              osVersion += "." + winVer.wServicePackMajor +
-                           "." + winVer.wServicePackMinor;
-            } else {
-              LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
-              osVersion += ".unknown";
-            }
-          } catch (e) {
-            LOG("gOSVersion - error getting service pack information. Exception: " + e);
-            osVersion += ".unknown";
-          }
-        } finally {
-          kernel32.close();
-        }
-
-        // Add processor architecture
-        osVersion += " (" + gWinCPUArch + ")";
-      }
-    }
-
-    try {
-      osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
-    }
-    catch (e) {
-      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
-    }
-    osVersion = encodeURIComponent(osVersion);
-  }
-  return osVersion;
-});
-
-/* Windows only getter that returns the processor architecture. */
-XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() {
-  // Get processor architecture
-  let arch = "unknown";
-
-  const WORD = ctypes.uint16_t;
-  const DWORD = ctypes.uint32_t;
-
-  // This structure is described at:
-  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
-  const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
-      [
-      {wProcessorArchitecture: WORD},
-      {wReserved: WORD},
-      {dwPageSize: DWORD},
-      {lpMinimumApplicationAddress: ctypes.voidptr_t},
-      {lpMaximumApplicationAddress: ctypes.voidptr_t},
-      {dwActiveProcessorMask: DWORD.ptr},
-      {dwNumberOfProcessors: DWORD},
-      {dwProcessorType: DWORD},
-      {dwAllocationGranularity: DWORD},
-      {wProcessorLevel: WORD},
-      {wProcessorRevision: WORD}
-      ]);
-
-  let kernel32 = false;
-  try {
-    kernel32 = ctypes.open("Kernel32");
-  } catch (e) {
-    LOG("gWinCPUArch - Unable to open kernel32! Exception: " + e);
-  }
-
-  if (kernel32) {
-    try {
-      let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
-                                                 ctypes.default_abi,
-                                                 ctypes.void_t,
-                                                 SYSTEM_INFO.ptr);
-      let winSystemInfo = SYSTEM_INFO();
-      // Default to unknown
-      winSystemInfo.wProcessorArchitecture = 0xffff;
-
-      GetNativeSystemInfo(winSystemInfo.address());
-      switch (winSystemInfo.wProcessorArchitecture) {
-        case 9:
-          arch = "x64";
-          break;
-        case 6:
-          arch = "IA64";
-          break;
-        case 0:
-          arch = "x86";
-          break;
-      }
-    } catch (e) {
-      LOG("gWinCPUArch - error getting processor architecture. " +
-          "Exception: " + e);
-    } finally {
-      kernel32.close();
-    }
-  }
-
-  return arch;
-});
-
 /**
  * Tests to make sure that we can write to a given directory.
  *
  * @param updateTestFile a test file in the directory that needs to be tested.
  * @param createDirectory whether a test directory should be created.
  * @throws if we don't have right access to the directory.
  */
 function testWriteAccess(updateTestFile, createDirectory) {
@@ -722,23 +521,23 @@ XPCOMUtils.defineLazyGetter(this, "gCanC
   var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
   if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
     LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
         "the preference is disabled and admistratively locked.");
     return false;
   }
 
   // If we don't know the binary platform we're updating, we can't update.
-  if (!gABI) {
+  if (!UpdateUtils.ABI) {
     LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
     return false;
   }
 
   // If we don't know the OS version we're updating, we can't update.
-  if (!gOSVersion) {
+  if (!UpdateUtils.OSVersion) {
     LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
         "version");
     return false;
   }
 
   LOG("gCanCheckForUpdates - able to check for updates");
   return true;
 });
@@ -1238,68 +1037,16 @@ function cleanupActiveUpdate() {
   um.activeUpdate = null;
   um.saveUpdates();
 
   // Now trash the updates directory, since we're done with it
   cleanUpUpdatesDir();
 }
 
 /**
- * Gets the locale from the update.locale file for replacing %LOCALE% in the
- * update url. The update.locale file can be located in the application
- * directory or the GRE directory with preference given to it being located in
- * the application directory.
- */
-function getLocale() {
-  if (gLocale) {
-    return gLocale;
-  }
-
-  let channel;
-  for (let res of ['app', 'gre']) {
-    channel = Services.io.newChannel2("resource://" + res + "/" + FILE_UPDATE_LOCALE,
-                                      null,
-                                      null,
-                                      null,      // aLoadingNode
-                                      Services.scriptSecurityManager.getSystemPrincipal(),
-                                      null,      // aTriggeringPrincipal
-                                      Ci.nsILoadInfo.SEC_NORMAL,
-                                      Ci.nsIContentPolicy.TYPE_INTERNAL_XMLHTTPREQUEST);
-    try {
-      var inputStream = channel.open();
-      gLocale = readStringFromInputStream(inputStream);
-    } catch(e) {}
-    if (gLocale)
-      break;
-  }
-
-  if (!gLocale)
-    throw Components.Exception(FILE_UPDATE_LOCALE + " file doesn't exist in " +
-                               "either the application or GRE directories",
-                               Cr.NS_ERROR_FILE_NOT_FOUND);
-
-  LOG("getLocale - getting locale from file: " + channel.originalURI.spec +
-      ", locale: " + gLocale);
-  return gLocale;
-}
-
-/* Get the distribution pref values, from defaults only */
-function getDistributionPrefValue(aPrefName) {
-  var prefValue = "default";
-
-  try {
-    prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
-  } catch (e) {
-    // use default when pref not found
-  }
-
-  return prefValue;
-}
-
-/**
  * An enumeration of items in a JS array.
  * @constructor
  */
 function ArrayEnumerator(aItems) {
   this._index = 0;
   if (aItems) {
     for (var i = 0; i < aItems.length; ++i) {
       if (!aItems[i])
@@ -2522,19 +2269,19 @@ UpdateService.prototype = {
     let validUpdateURL = true;
     try {
       this.backgroundChecker.getUpdateURL(false);
     } catch (e) {
       validUpdateURL = false;
     }
     // The following checks are done here so they can be differentiated from
     // foreground checks.
-    if (!gOSVersion) {
+    if (!UpdateUtils.OSVersion) {
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_VERSION);
-    } else if (!gABI) {
+    } else if (!UpdateUtils.ABI) {
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_ABI);
     } else if (!validUpdateURL) {
       if (overridePrefHasValue) {
         if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE)) {
           AUSTLMY.pingCheckCode(this._pingSuffix,
                                 AUSTLMY.CHK_INVALID_USER_OVERRIDE_URL);
         } else {
           AUSTLMY.pingCheckCode(this._pingSuffix,
@@ -3266,24 +3013,24 @@ UpdateManager.prototype = {
     return this._updates.length;
   },
 
   /**
    * See nsIUpdateService.idl
    */
   get activeUpdate() {
     if (this._activeUpdate &&
-        this._activeUpdate.channel != UpdateChannel.get()) {
+        this._activeUpdate.channel != UpdateUtils.UpdateChannel) {
       LOG("UpdateManager:get activeUpdate - channel has changed, " +
           "reloading default preferences to workaround bug 802022");
       // Workaround to get distribution preferences loaded (Bug 774618). This
       // can be removed after bug 802022 is fixed.
       let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
       prefSvc.observe(null, "reload-default-prefs", null);
-      if (this._activeUpdate.channel != UpdateChannel.get()) {
+      if (this._activeUpdate.channel != UpdateUtils.UpdateChannel) {
         // User switched channels, clear out any old active updates and remove
         // partial downloads
         this._activeUpdate = null;
         this.saveUpdates();
 
         // Destroy the updates directory, since we're done with it.
         cleanUpUpdatesDir();
       }
@@ -3511,50 +3258,17 @@ Checker.prototype = {
       }
     }
 
     if (!url || url == "") {
       LOG("Checker:getUpdateURL - update URL not defined");
       return null;
     }
 
-    url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
-    url = url.replace(/%VERSION%/g, Services.appinfo.version);
-    url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
-    url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + gABI);
-    url = url.replace(/%OS_VERSION%/g, gOSVersion);
-    if (/%LOCALE%/.test(url)) {
-      url = url.replace(/%LOCALE%/g, getLocale());
-    }
-    url = url.replace(/%CHANNEL%/g, UpdateChannel.get());
-    url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
-    url = url.replace(/%DISTRIBUTION%/g,
-                      getDistributionPrefValue(PREF_APP_DISTRIBUTION));
-    url = url.replace(/%DISTRIBUTION_VERSION%/g,
-                      getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
-    url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, ""));
-    url = url.replace(/\+/g, "%2B");
-
-    if (AppConstants.platform == "gonk") {
-      let sysLibs = {};
-      Cu.import("resource://gre/modules/systemlibs.js", sysLibs);
-      let productDevice = sysLibs.libcutils.property_get("ro.product.device");
-      let buildType = sysLibs.libcutils.property_get("ro.build.type");
-      url = url.replace(/%PRODUCT_MODEL%/g,
-                        sysLibs.libcutils.property_get("ro.product.model"));
-      if (buildType == "user" || buildType == "userdebug") {
-        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice);
-      } else {
-        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice + "-" + buildType);
-      }
-      url = url.replace(/%B2G_VERSION%/g,
-                        getPref("getCharPref", PREF_APP_B2G_VERSION, null));
-      url = url.replace(/%IMEI%/g,
-                        getPref("getCharPref", PREF_APP_UPDATE_IMEI_HASH, "default"));
-    }
+    url = UpdateUtils.formatUpdateURL(url);
 
     if (force) {
       url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
     }
 
     LOG("Checker:getUpdateURL - update URL: " + url);
     return url;
   },
@@ -3636,17 +3350,17 @@ Checker.prototype = {
       let update;
       try {
         update = new Update(updateElement);
       } catch (e) {
         LOG("Checker:_updates get - invalid <update/>, ignoring...");
         continue;
       }
       update.serviceURL = this.getUpdateURL(this._forced);
-      update.channel = UpdateChannel.get();
+      update.channel = UpdateUtils.UpdateChannel;
       updates.push(update);
     }
 
     return updates;
   },
 
   /**
    * Returns the status code for the XMLHttpRequest