Bug 1359733 - (pt. 2) Refactor gMenuButtonUpdateBadge draft
authorDoug Thayer <dothayer@mozilla.com>
Fri, 19 May 2017 08:56:22 -0700
changeset 583046 b15c9b889898d6e7e3e1ec5c87b481e073549e55
parent 583045 a32b82659a8103ad39709dae888006bf618fcdcf
child 583047 1438ba81708d072e1850d22f4b80304429c226de
push id60284
push userbmo:dothayer@mozilla.com
push dateTue, 23 May 2017 17:20:55 +0000
bugs1359733
milestone55.0a1
Bug 1359733 - (pt. 2) Refactor gMenuButtonUpdateBadge Since we now have a store of notifications that is global across all windows, it no longer makes sense to consume the API from within browser.js. This patch moves the browser.js logic out into a jsm file that is wired up through nsBrowserGlue, such that it will be lazily instantiated on the first update event it would receive[1]. We decided to move this into toolkit, as this piece of the system is fairly generic and shouldn't differ between applications. [1]: There is a change to nsBrowserGlue to use "global[module]" instead of this[module]. This mirrors the code for all the other types of notifications, and I suspect it was just a latent bug, since the original diff that includes this line makes no use of it. MozReview-Commit-ID: 8EQdM9BOpgl
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/moz.build
browser/base/content/test/appUpdate/.eslintrc.js
browser/base/content/test/appUpdate/browser.ini
browser/base/content/test/appUpdate/browser_updatesBackgroundWindow.js
browser/base/content/test/appUpdate/browser_updatesBackgroundWindowFailures.js
browser/base/content/test/appUpdate/browser_updatesBasicPrompt.js
browser/base/content/test/appUpdate/browser_updatesBasicPromptNoStaging.js
browser/base/content/test/appUpdate/browser_updatesCantApply.js
browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadSizes.js
browser/base/content/test/appUpdate/browser_updatesCompletePatchApplyFailure.js
browser/base/content/test/appUpdate/browser_updatesCompletePatchWithBadCompleteSize.js
browser/base/content/test/appUpdate/browser_updatesDownloadFailures.js
browser/base/content/test/appUpdate/browser_updatesMalformedXml.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailure.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteValidationFailure.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchWithBadPartialSize.js
browser/base/content/test/appUpdate/head.js
browser/base/content/test/appUpdate/testConstants.js
browser/base/moz.build
browser/components/customizableui/content/panelUI.js
browser/components/nsBrowserGlue.js
toolkit/mozapps/update/UpdateListener.jsm
toolkit/mozapps/update/moz.build
toolkit/mozapps/update/tests/browser/.eslintrc.js
toolkit/mozapps/update/tests/browser/browser.ini
toolkit/mozapps/update/tests/browser/browser_updatesBackgroundWindow.js
toolkit/mozapps/update/tests/browser/browser_updatesBackgroundWindowFailures.js
toolkit/mozapps/update/tests/browser/browser_updatesBasicPrompt.js
toolkit/mozapps/update/tests/browser/browser_updatesBasicPromptNoStaging.js
toolkit/mozapps/update/tests/browser/browser_updatesCantApply.js
toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadSizes.js
toolkit/mozapps/update/tests/browser/browser_updatesCompletePatchApplyFailure.js
toolkit/mozapps/update/tests/browser/browser_updatesCompletePatchWithBadCompleteSize.js
toolkit/mozapps/update/tests/browser/browser_updatesDownloadFailures.js
toolkit/mozapps/update/tests/browser/browser_updatesMalformedXml.js
toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailure.js
toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailureWithCompleteValidationFailure.js
toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchWithBadPartialSize.js
toolkit/mozapps/update/tests/moz.build
toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -133,16 +133,20 @@ pref("app.update.log", false);
 pref("app.update.backgroundMaxErrors", 10);
 
 // Whether or not app updates are enabled
 pref("app.update.enabled", true);
 
 // Whether or not to use the doorhanger application update UI.
 pref("app.update.doorhanger", true);
 
+// Ids of the links to the "What's new" update documentation
+pref("app.update.link.updateAvailableWhatsNew", "update-available-whats-new");
+pref("app.update.link.updateManualWhatsNew", "update-manual-whats-new");
+
 // How many times we should let downloads fail before prompting the user to
 // download a fresh installer.
 pref("app.update.download.promptMaxAttempts", 2);
 
 // How many times we should let an elevation prompt fail before prompting the user to
 // download a fresh installer.
 pref("app.update.elevation.promptMaxAttempts", 2);
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1632,18 +1632,16 @@ var gBrowserInit = {
     // initialize the sync UI
     gSync.init();
 
     if (AppConstants.MOZ_DATA_REPORTING)
       gDataNotificationInfoBar.init();
 
     gBrowserThumbnails.init();
 
-    gMenuButtonUpdateBadge.init();
-
     gExtensionsNotifications.init();
 
     let wasMinimized = window.windowState == window.STATE_MINIMIZED;
     window.addEventListener("sizemodechange", () => {
       let isMinimized = window.windowState == window.STATE_MINIMIZED;
       if (wasMinimized != isMinimized) {
         wasMinimized = isMinimized;
         UpdatePopupNotificationsVisibility();
@@ -1795,18 +1793,16 @@ var gBrowserInit = {
     CompactTheme.uninit();
 
     TrackingProtection.uninit();
 
     RefreshBlocker.uninit();
 
     CaptivePortalWatcher.uninit();
 
-    gMenuButtonUpdateBadge.uninit();
-
     SidebarUI.uninit();
 
     // Now either cancel delayedStartup, or clean up the services initialized from
     // it.
     if (this._boundDelayedStartup) {
       this._cancelDelayedStartup();
     } else {
       if (Win7Features)
@@ -2852,234 +2848,16 @@ function UpdatePopupNotificationsVisibil
   PopupNotifications.anchorVisibilityChange();
 }
 
 function PageProxyClickHandler(aEvent) {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
-// Setup the hamburger button badges for updates, if enabled.
-var gMenuButtonUpdateBadge = {
-  kTopics: [
-    "update-staged",
-    "update-downloaded",
-    "update-available",
-    "update-error",
-  ],
-
-  timeouts: [],
-
-  get enabled() {
-    return Services.prefs.getBoolPref("app.update.doorhanger", false);
-  },
-
-  get badgeWaitTime() {
-    return Services.prefs.getIntPref("app.update.badgeWaitTime", 4 * 24 * 3600); // 4 days
-  },
-
-  init() {
-    if (this.enabled) {
-      this.kTopics.forEach(t => {
-        Services.obs.addObserver(this, t);
-      });
-    }
-  },
-
-  uninit() {
-    if (this.enabled) {
-      this.kTopics.forEach(t => {
-        Services.obs.removeObserver(this, t);
-      });
-    }
-
-    this.reset();
-  },
-
-  reset() {
-    PanelUI.removeNotification(/^update-/);
-    this.clearCallbacks();
-  },
-
-  clearCallbacks() {
-    this.timeouts.forEach(t => clearTimeout(t));
-    this.timeouts = [];
-  },
-
-  addTimeout(time, callback) {
-    this.timeouts.push(setTimeout(() => {
-      this.clearCallbacks();
-      callback();
-    }, time));
-  },
-
-  replaceReleaseNotes(update, whatsNewId) {
-    let whatsNewLink = document.getElementById(whatsNewId);
-    if (update && update.detailsURL) {
-      whatsNewLink.href = update.detailsURL;
-    } else {
-      whatsNewLink.href = Services.urlFormatter.formatURLPref("app.update.url.details");
-    }
-  },
-
-  requestRestart() {
-    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
-                     .createInstance(Ci.nsISupportsPRBool);
-    Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
-
-    if (!cancelQuit.data) {
-      Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
-    }
-  },
-
-  openManualUpdateUrl() {
-    let manualUpdateUrl = Services.urlFormatter.formatURLPref("app.update.url.manual");
-    openUILinkIn(manualUpdateUrl, "tab");
-  },
-
-  showUpdateNotification(type, dismissed, mainAction) {
-    let action = {
-      callback(fromDoorhanger) {
-        if (fromDoorhanger) {
-          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER").add(type);
-        } else {
-          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_MENU").add(type);
-        }
-        mainAction();
-      }
-    };
-
-    let secondaryAction = {
-      callback() {
-        Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_DISMISSED").add(type);
-      },
-      dismiss: true
-    };
-
-    PanelUI.showNotification("update-" + type, action, [secondaryAction], { dismissed });
-    Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_SHOWN").add(type);
-  },
-
-  showRestartNotification(dismissed) {
-    this.showUpdateNotification("restart", dismissed, () => gMenuButtonUpdateBadge.requestRestart());
-  },
-
-  showUpdateAvailableNotification(update, dismissed) {
-    this.replaceReleaseNotes(update, "update-available-whats-new");
-    this.showUpdateNotification("available", dismissed, () => {
-      let updateService = Cc["@mozilla.org/updates/update-service;1"]
-                          .getService(Ci.nsIApplicationUpdateService);
-      updateService.downloadUpdate(update, true);
-    });
-  },
-
-  showManualUpdateNotification(update, dismissed) {
-    this.replaceReleaseNotes(update, "update-manual-whats-new");
-
-    this.showUpdateNotification("manual", dismissed, () => gMenuButtonUpdateBadge.openManualUpdateUrl());
-  },
-
-  handleUpdateError(update, status) {
-    switch (status) {
-      case "download-attempt-failed":
-        this.clearCallbacks();
-        this.showUpdateAvailableNotification(update, false);
-        break;
-      case "download-attempts-exceeded":
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-      case "elevation-attempt-failed":
-        this.clearCallbacks();
-        this.showRestartNotification(update, false);
-        break;
-      case "elevation-attempts-exceeded":
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-      case "check-attempts-exceeded":
-      case "unknown":
-        // Background update has failed, let's show the UI responsible for
-        // prompting the user to update manually.
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-    }
-  },
-
-  handleUpdateStagedOrDownloaded(update, status) {
-    switch (status) {
-      case "applied":
-      case "pending":
-      case "applied-service":
-      case "pending-service":
-      case "success":
-        this.clearCallbacks();
-
-        let badgeWaitTimeMs = this.badgeWaitTime * 1000;
-        let doorhangerWaitTimeMs = update.promptWaitTime * 1000;
-
-        if (badgeWaitTimeMs < doorhangerWaitTimeMs) {
-          this.addTimeout(badgeWaitTimeMs, () => {
-            this.showRestartNotification(true);
-
-            // doorhangerWaitTimeMs is relative to when we initially received
-            // the event. Since we've already waited badgeWaitTimeMs, subtract
-            // that from doorhangerWaitTimeMs.
-            let remainingTime = doorhangerWaitTimeMs - badgeWaitTimeMs;
-            this.addTimeout(remainingTime, () => {
-              this.showRestartNotification(false);
-            });
-          });
-        } else {
-          this.addTimeout(doorhangerWaitTimeMs, () => {
-            this.showRestartNotification(false);
-          });
-        }
-        break;
-    }
-  },
-
-  handleUpdateAvailable(update, status) {
-    switch (status) {
-      case "show-prompt":
-        // If an update is available and had the showPrompt flag set, then
-        // show an update available doorhanger.
-        this.clearCallbacks();
-        this.showUpdateAvailableNotification(update, false);
-        break;
-      case "cant-apply":
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-    }
-  },
-
-  observe(subject, topic, status) {
-    if (!this.enabled) {
-      return;
-    }
-
-    let update = subject && subject.QueryInterface(Ci.nsIUpdate);
-
-    switch (topic) {
-      case "update-available":
-        this.handleUpdateAvailable(update, status);
-        break;
-      case "update-staged":
-      case "update-downloaded":
-        this.handleUpdateStagedOrDownloaded(update, status);
-        break;
-      case "update-error":
-        this.handleUpdateError(update, status);
-        break;
-    }
-  }
-};
-
 // Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED   = 2;
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED = 3;
 const TLS_ERROR_REPORT_TELEMETRY_MANUAL_SEND    = 4;
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_SEND      = 5;
 
 const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -23,19 +23,16 @@ with Files("newtab/**"):
     BUG_COMPONENT = ("Firefox", "New Tab Page")
 
 with Files("pageinfo/**"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
 with Files("test/alerts/**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
-with Files("test/appUpdate/**"):
-    BUG_COMPONENT = ("Toolkit", "Application Update")
-
 with Files("test/captivePortal/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/chrome/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/forms/**"):
     BUG_COMPONENT = ("Core", "Layout: Form Controls")
--- a/browser/base/content/test/appUpdate/head.js
+++ b/browser/base/content/test/appUpdate/head.js
@@ -1,33 +1,40 @@
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
+                                  "resource://gre/modules/AppMenuNotifications.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateListener",
+                                  "resource://gre/modules/UpdateListener.jsm");
+
 const IS_MACOSX = ("nsILocalFileMac" in Ci);
 const IS_WIN = ("@mozilla.org/windows-registry-key;1" in Cc);
 
 const BIN_SUFFIX = (IS_WIN ? ".exe" : "");
 const FILE_UPDATER_BIN = "updater" + (IS_MACOSX ? ".app" : BIN_SUFFIX);
 const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
 
 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
 const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
 
 let gRembemberedPrefs = [];
 
-const DATA_URI_SPEC =  "chrome://mochitests/content/browser/browser/base/content/test/appUpdate/";
+const DATA_URI_SPEC =  "chrome://mochitests/content/browser/toolkit/mozapps/update/tests/browser/";
 
 var DEBUG_AUS_TEST = true;
 var gUseTestUpdater = false;
 
 const LOG_FUNCTION = info;
 
+const MAX_UPDATE_COPY_ATTEMPTS = 10;
+
 /* import-globals-from testConstants.js */
 Services.scriptloader.loadSubScript(DATA_URI_SPEC + "testConstants.js", this);
-/* import-globals-from ../../../../../toolkit/mozapps/update/tests/data/shared.js */
+/* import-globals-from ../data/shared.js */
 Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
 
 var gURLData = URL_HOST + "/" + REL_PATH_DATA;
 const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
 
 const gEnv = Cc["@mozilla.org/process/environment;1"].
              getService(Components.interfaces.nsIEnvironment);
 
@@ -99,18 +106,17 @@ function setUpdateTimerPrefs() {
  *         and additional validation/cleanup callbacks.
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
  */
 function runUpdateTest(updateParams, checkAttempts, steps) {
   return (async function() {
     registerCleanupFunction(() => {
       gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
-      gMenuButtonUpdateBadge.uninit();
-      gMenuButtonUpdateBadge.init();
+      UpdateListener.reset();
       cleanUpUpdates();
     });
 
     gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
     setUpdateTimerPrefs();
     await SpecialPowers.pushPrefEnv({
       set: [
         [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
@@ -156,17 +162,17 @@ function runUpdateTest(updateParams, che
  *         and additional validation/cleanup callbacks.
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
  */
 function runUpdateProcessingTest(updates, steps) {
   return (async function() {
     registerCleanupFunction(() => {
       gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
-      gMenuButtonUpdateBadge.reset();
+      UpdateListener.reset();
       cleanUpUpdates();
     });
 
     setUpdateTimerPrefs();
     gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
     SpecialPowers.pushPrefEnv({
       set: [
         [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
@@ -198,17 +204,17 @@ function processStep(step) {
   if (typeof(step) == "function") {
     return step();
   }
 
   const {notificationId, button, beforeClick, cleanup} = step;
   return (async function() {
 
     await BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popupshown");
-    const shownNotification = PanelUI.activeNotification.id;
+    const shownNotification = AppMenuNotifications.activeNotification.id;
 
     is(shownNotification, notificationId, "The right notification showed up.");
     if (shownNotification != notificationId) {
       if (cleanup) {
         await cleanup();
       }
       return;
     }
@@ -330,36 +336,39 @@ function moveRealUpdater() {
   })();
 }
 
 /**
  * Copies the test updater so it can be used by tests and tries again on failure
  * since Windows debug builds at times leave the file in use. After success it
  * will call runTest to continue the test.
  */
-function copyTestUpdater() {
+function copyTestUpdater(attempt = 0) {
   return (async function() {
     try {
       // Copy the test updater
       let baseAppDir = getAppBaseDir();
       let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile);
       let relPath = REL_PATH_DATA;
       let pathParts = relPath.split("/");
       for (let i = 0; i < pathParts.length; ++i) {
         testUpdaterDir.append(pathParts[i]);
       }
 
       let testUpdater = testUpdaterDir.clone();
       testUpdater.append(FILE_UPDATER_BIN);
+
       testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN);
     } catch (e) {
-      logTestInfo("Attempt to copy the test updater failed... " +
-                  "will try again, Exception: " + e);
-      await delay();
-      await copyTestUpdater();
+      if (attempt < MAX_UPDATE_COPY_ATTEMPTS) {
+        logTestInfo("Attempt to copy the test updater failed... " +
+                    "will try again, Exception: " + e);
+        await delay();
+        await copyTestUpdater(attempt + 1);
+      }
     }
   })();
 }
 
 /**
  * Restores the updater that was backed up. This is called in setupTestUpdater
  * before the backup of the real updater is done in case the previous test
  * failed to restore the updater, in finishTestDefaultWaitForWindowClosed when
--- a/browser/base/content/test/appUpdate/testConstants.js
+++ b/browser/base/content/test/appUpdate/testConstants.js
@@ -1,4 +1,4 @@
-const REL_PATH_DATA = "browser/browser/base/content/test/appUpdate/";
+const REL_PATH_DATA = "browser/toolkit/mozapps/update/tests/browser/";
 const URL_HOST = "http://example.com";
 const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "update.sjs";
 const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -36,33 +36,23 @@ BROWSER_CHROME_MANIFESTS += [
     'content/test/tabcrashed/browser.ini',
     'content/test/tabPrompts/browser.ini',
     'content/test/tabs/browser.ini',
     'content/test/urlbar/browser.ini',
     'content/test/webextensions/browser.ini',
     'content/test/webrtc/browser.ini',
 ]
 
-if CONFIG['MOZ_UPDATER']:
-    BROWSER_CHROME_MANIFESTS += ['content/test/appUpdate/browser.ini']
-
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
 
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
     DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
     DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
 
-TEST_HARNESS_FILES.testing.mochitest.browser.browser.base.content.test.appUpdate += [
-    '/toolkit/mozapps/update/tests/chrome/update.sjs',
-    '/toolkit/mozapps/update/tests/data/shared.js',
-    '/toolkit/mozapps/update/tests/data/sharedUpdateXML.js',
-    '/toolkit/mozapps/update/tests/data/simple.mar',
-]
-
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -724,16 +724,20 @@ const PanelUI = {
 
   _showNotificationPanel(notification) {
     this._refreshNotificationPanel(notification);
 
     if (this.isNotificationPanelOpen) {
       return;
     }
 
+    if (notification.options.beforeShowDoorhanger) {
+      notification.options.beforeShowDoorhanger(document);
+    }
+
     let anchor = this._getPanelAnchor(this.menuButton);
 
     this.notificationPanel.hidden = false;
     this.notificationPanel.openPopup(anchor, "bottomcenter topright");
   },
 
   _clearNotificationPanel() {
     for (let popupnotification of this.notificationPanel.children) {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -31,17 +31,17 @@ XPCOMUtils.defineLazyGetter(this, "Weave
           FileUtils:false, FormValidationHandler:false, Integration:false,
           LightweightThemeManager:false, LoginHelper:false, LoginManagerParent:false,
           NetUtil:false, NewTabUtils:false, OS:false,
           PageThumbs:false, PdfJs:false, PermissionUI:false, PlacesBackups:false,
           PlacesUtils:false, PluralForm:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, ReaderParent:false, RecentWindow:false,
           RemotePrompt:false, SessionStore:false,
           ShellService:false, SimpleServiceDiscovery:false, TabCrashHandler:false,
-          Task:false, UITour:false, WebChannel:false,
+          Task:false, UITour:false, UpdateListener:false, WebChannel:false,
           WindowsRegistry:false, webrtcUI:false */
 
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
 
 let initializedModules = {};
@@ -84,16 +84,17 @@ let initializedModules = {};
   ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
   ["RemotePrompt", "resource:///modules/RemotePrompt.jsm"],
   ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
   ["ShellService", "resource:///modules/ShellService.jsm"],
   ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
+  ["UpdateListener", "resource://gre/modules/UpdateListener.jsm", "init"],
   ["WebChannel", "resource://gre/modules/WebChannel.jsm"],
   ["WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"],
   ["webrtcUI", "resource:///modules/webrtcUI.jsm", "init"],
 ].forEach(([name, resource, init]) => {
   if (init) {
     XPCOMUtils.defineLazyGetter(this, name, () => {
       Cu.import(resource, initializedModules);
       initializedModules[name][init]();
@@ -119,16 +120,23 @@ XPCOMUtils.defineLazyGetter(this, "gBran
 
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/browser.properties");
 });
 
 const global = this;
 
 const listeners = {
+  observers: {
+    "update-staged": ["UpdateListener"],
+    "update-downloaded": ["UpdateListener"],
+    "update-available": ["UpdateListener"],
+    "update-error": ["UpdateListener"],
+  },
+
   ppmm: {
     // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN ContentPrefServiceParent.init
     "ContentPrefs:FunctionCall": ["ContentPrefServiceParent"],
     "ContentPrefs:AddObserverForName": ["ContentPrefServiceParent"],
     "ContentPrefs:RemoveObserverForName": ["ContentPrefServiceParent"],
     // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN ContentPrefServiceParent.init
     "FeedConverter:addLiveBookmark": ["Feeds"],
     "WCCR:setAutoHandler": ["Feeds"],
@@ -160,29 +168,43 @@ const listeners = {
     "rtcpeer:CancelRequest": ["webrtcUI"],
     "rtcpeer:Request": ["webrtcUI"],
     "webrtc:CancelRequest": ["webrtcUI"],
     "webrtc:Request": ["webrtcUI"],
     "webrtc:StopRecording": ["webrtcUI"],
     "webrtc:UpdateBrowserIndicators": ["webrtcUI"],
   },
 
+  observe(subject, topic, data) {
+    for (let module of this.observers[topic]) {
+      try {
+        global[module].observe(subject, topic, data);
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+  },
+
   receiveMessage(modules, data) {
     let val;
     for (let module of modules[data.name]) {
       try {
         val = global[module].receiveMessage(data) || val;
       } catch (e) {
         Cu.reportError(e);
       }
     }
     return val;
   },
 
   init() {
+    for (let observer of Object.keys(this.observers)) {
+      Services.obs.addObserver(this, observer);
+    }
+
     let receiveMessageMM = this.receiveMessage.bind(this, this.mm);
     for (let message of Object.keys(this.mm)) {
       Services.mm.addMessageListener(message, receiveMessageMM);
     }
 
     let receiveMessagePPMM = this.receiveMessage.bind(this, this.ppmm);
     for (let message of Object.keys(this.ppmm)) {
       Services.ppmm.addMessageListener(message, receiveMessagePPMM);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/UpdateListener.jsm
@@ -0,0 +1,222 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["UpdateListener"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
+                                  "resource://gre/modules/AppMenuNotifications.jsm");
+
+// Setup the hamburger button badges for updates, if enabled.
+var UpdateListener = {
+  timeouts: [],
+
+  get enabled() {
+    return Services.prefs.getBoolPref("app.update.doorhanger", false);
+  },
+
+  get badgeWaitTime() {
+    return Services.prefs.getIntPref("app.update.badgeWaitTime", 4 * 24 * 3600); // 4 days
+  },
+
+  init() {
+  },
+
+  uninit() {
+    this.reset();
+  },
+
+  reset() {
+    AppMenuNotifications.removeNotification(/^update-/);
+    this.clearCallbacks();
+  },
+
+  clearCallbacks() {
+    this.timeouts.forEach(t => clearTimeout(t));
+    this.timeouts = [];
+  },
+
+  addTimeout(time, callback) {
+    this.timeouts.push(setTimeout(() => {
+      this.clearCallbacks();
+      callback();
+    }, time));
+  },
+
+  replaceReleaseNotes(doc, update, whatsNewId) {
+    let whatsNewLinkId = Services.prefs.getCharPref(`app.update.link.${whatsNewId}`, "");
+    if (whatsNewLinkId) {
+      let whatsNewLink = doc.getElementById(whatsNewLinkId);
+      if (update && update.detailsURL) {
+        whatsNewLink.href = update.detailsURL;
+      } else {
+        whatsNewLink.href = Services.urlFormatter.formatURLPref("app.update.url.details");
+      }
+    }
+  },
+
+  requestRestart() {
+    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+                     createInstance(Ci.nsISupportsPRBool);
+    Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+    if (!cancelQuit.data) {
+      Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
+    }
+  },
+
+  openManualUpdateUrl(win) {
+    let manualUpdateUrl = Services.urlFormatter.formatURLPref("app.update.url.manual");
+    win.openURL(manualUpdateUrl);
+  },
+
+  showUpdateNotification(type, dismissed, mainAction, beforeShowDoorhanger) {
+    let action = {
+      callback(win, fromDoorhanger) {
+        if (fromDoorhanger) {
+          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER").add(type);
+        } else {
+          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_MENU").add(type);
+        }
+        mainAction(win);
+      }
+    };
+
+    let secondaryAction = {
+      callback() {
+        Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_DISMISSED").add(type);
+      },
+      dismiss: true
+    };
+
+    AppMenuNotifications.showNotification("update-" + type,
+                                          action,
+                                          secondaryAction,
+                                          { dismissed, beforeShowDoorhanger });
+    Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_SHOWN").add(type);
+  },
+
+  showRestartNotification(dismissed) {
+    this.showUpdateNotification("restart", dismissed, () => this.requestRestart());
+  },
+
+  showUpdateAvailableNotification(update, dismissed) {
+    this.showUpdateNotification("available", dismissed, () => {
+      let updateService = Cc["@mozilla.org/updates/update-service;1"]
+                          .getService(Ci.nsIApplicationUpdateService);
+      updateService.downloadUpdate(update, true);
+    }, doc => this.replaceReleaseNotes(doc, update, "updateAvailableWhatsNew"));
+  },
+
+  showManualUpdateNotification(update, dismissed) {
+    this.showUpdateNotification("manual",
+                                dismissed,
+                                win => this.openManualUpdateUrl(win),
+                                doc => this.replaceReleaseNotes(doc, update, "updateManualWhatsNew"));
+  },
+
+  handleUpdateError(update, status) {
+    switch (status) {
+      case "download-attempt-failed":
+        this.clearCallbacks();
+        this.showUpdateAvailableNotification(update, false);
+        break;
+      case "download-attempts-exceeded":
+        this.clearCallbacks();
+        this.showManualUpdateNotification(update, false);
+        break;
+      case "elevation-attempt-failed":
+        this.clearCallbacks();
+        this.showRestartNotification(update, false);
+        break;
+      case "elevation-attempts-exceeded":
+        this.clearCallbacks();
+        this.showManualUpdateNotification(update, false);
+        break;
+      case "check-attempts-exceeded":
+      case "unknown":
+        // Background update has failed, let's show the UI responsible for
+        // prompting the user to update manually.
+        this.clearCallbacks();
+        this.showManualUpdateNotification(update, false);
+        break;
+    }
+  },
+
+  handleUpdateStagedOrDownloaded(update, status) {
+    switch (status) {
+      case "applied":
+      case "pending":
+      case "applied-service":
+      case "pending-service":
+      case "success":
+        this.clearCallbacks();
+
+        let badgeWaitTimeMs = this.badgeWaitTime * 1000;
+        let doorhangerWaitTimeMs = update.promptWaitTime * 1000;
+
+        if (badgeWaitTimeMs < doorhangerWaitTimeMs) {
+          this.addTimeout(badgeWaitTimeMs, () => {
+            this.showRestartNotification(true);
+
+            // doorhangerWaitTimeMs is relative to when we initially received
+            // the event. Since we've already waited badgeWaitTimeMs, subtract
+            // that from doorhangerWaitTimeMs.
+            let remainingTime = doorhangerWaitTimeMs - badgeWaitTimeMs;
+            this.addTimeout(remainingTime, () => {
+              this.showRestartNotification(false);
+            });
+          });
+        } else {
+          this.addTimeout(doorhangerWaitTimeMs, () => {
+            this.showRestartNotification(false);
+          });
+        }
+        break;
+    }
+  },
+
+  handleUpdateAvailable(update, status) {
+    switch (status) {
+      case "show-prompt":
+        // If an update is available and had the showPrompt flag set, then
+        // show an update available doorhanger.
+        this.clearCallbacks();
+        this.showUpdateAvailableNotification(update, false);
+        break;
+      case "cant-apply":
+        this.clearCallbacks();
+        this.showManualUpdateNotification(update, false);
+        break;
+    }
+  },
+
+  observe(subject, topic, status) {
+    if (!this.enabled) {
+      return;
+    }
+
+    let update = subject && subject.QueryInterface(Ci.nsIUpdate);
+
+    switch (topic) {
+      case "update-available":
+        this.handleUpdateAvailable(update, status);
+        break;
+      case "update-staged":
+      case "update-downloaded":
+        this.handleUpdateStagedOrDownloaded(update, status);
+        break;
+      case "update-error":
+        this.handleUpdateError(update, status);
+        break;
+    }
+  }
+};
--- a/toolkit/mozapps/update/moz.build
+++ b/toolkit/mozapps/update/moz.build
@@ -19,15 +19,16 @@ TEST_DIRS += ['tests']
 
 EXTRA_COMPONENTS += [
     'nsUpdateService.js',
     'nsUpdateService.manifest',
     'nsUpdateServiceStub.js',
 ]
 
 EXTRA_JS_MODULES += [
+    'UpdateListener.jsm',
     'UpdateTelemetry.jsm',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Application Update')
rename from browser/base/content/test/appUpdate/.eslintrc.js
rename to toolkit/mozapps/update/tests/browser/.eslintrc.js
rename from browser/base/content/test/appUpdate/browser.ini
rename to toolkit/mozapps/update/tests/browser/browser.ini
rename from browser/base/content/test/appUpdate/browser_updatesBackgroundWindow.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesBackgroundWindow.js
--- a/browser/base/content/test/appUpdate/browser_updatesBackgroundWindow.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesBackgroundWindow.js
@@ -21,14 +21,14 @@ add_task(async function testUpdatesBackg
       checkWhatsNewLink("update-available-whats-new");
       let buttonEl = getNotificationButton(window, "update-available", "button");
       buttonEl.click();
     },
     {
       notificationId: "update-restart",
       button: "secondarybutton",
       cleanup() {
-        PanelUI.removeNotification(/.*/);
+        AppMenuNotifications.removeNotification(/.*/);
       }
     },
   ]);
 });
 
rename from browser/base/content/test/appUpdate/browser_updatesBackgroundWindowFailures.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesBackgroundWindowFailures.js
--- a/browser/base/content/test/appUpdate/browser_updatesBackgroundWindowFailures.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesBackgroundWindowFailures.js
@@ -35,13 +35,12 @@ add_task(async function testBackgroundWi
     {
       notificationId: "update-manual",
       button: "button",
       async cleanup() {
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         is(gBrowser.selectedBrowser.currentURI.spec,
            URL_MANUAL_UPDATE, "Landed on manual update page.");
         gBrowser.removeTab(gBrowser.selectedTab);
-        gMenuButtonUpdateBadge.reset();
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesBasicPrompt.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesBasicPrompt.js
--- a/browser/base/content/test/appUpdate/browser_updatesBasicPrompt.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesBasicPrompt.js
@@ -10,13 +10,13 @@ add_task(async function testBasicPrompt(
       beforeClick() {
         checkWhatsNewLink("update-available-whats-new");
       }
     },
     {
       notificationId: "update-restart",
       button: "secondarybutton",
       cleanup() {
-        PanelUI.removeNotification(/.*/);
+        AppMenuNotifications.removeNotification(/.*/);
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesBasicPromptNoStaging.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesBasicPromptNoStaging.js
--- a/browser/base/content/test/appUpdate/browser_updatesBasicPromptNoStaging.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesBasicPromptNoStaging.js
@@ -10,13 +10,13 @@ add_task(async function testBasicPromptN
       beforeClick() {
         checkWhatsNewLink("update-available-whats-new");
       }
     },
     {
       notificationId: "update-restart",
       button: "secondarybutton",
       cleanup() {
-        PanelUI.removeNotification(/.*/);
+        AppMenuNotifications.removeNotification(/.*/);
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesCantApply.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesCantApply.js
rename from browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
--- a/browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
@@ -1,13 +1,13 @@
 add_task(async function testCompleteAndPartialPatchesWithBadCompleteSize() {
   let updateParams = "invalidCompleteSize=1&promptWaitTime=0";
 
   await runUpdateTest(updateParams, 1, [
     {
       notificationId: "update-restart",
       button: "secondarybutton",
       cleanup() {
-        PanelUI.removeNotification(/.*/);
+        AppMenuNotifications.removeNotification(/.*/);
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
--- a/browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
@@ -1,13 +1,13 @@
 add_task(async function testCompleteAndPartialPatchesWithBadPartialSize() {
   let updateParams = "invalidPartialSize=1&promptWaitTime=0";
 
   await runUpdateTest(updateParams, 1, [
     {
       notificationId: "update-restart",
       button: "secondarybutton",
       cleanup() {
-        PanelUI.removeNotification(/.*/);
+        AppMenuNotifications.removeNotification(/.*/);
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadSizes.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesCompleteAndPartialPatchesWithBadSizes.js
rename from browser/base/content/test/appUpdate/browser_updatesCompletePatchApplyFailure.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesCompletePatchApplyFailure.js
rename from browser/base/content/test/appUpdate/browser_updatesCompletePatchWithBadCompleteSize.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesCompletePatchWithBadCompleteSize.js
rename from browser/base/content/test/appUpdate/browser_updatesDownloadFailures.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesDownloadFailures.js
--- a/browser/base/content/test/appUpdate/browser_updatesDownloadFailures.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesDownloadFailures.js
@@ -20,13 +20,12 @@ add_task(async function testDownloadFail
     {
       notificationId: "update-manual",
       button: "button",
       async cleanup() {
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         is(gBrowser.selectedBrowser.currentURI.spec,
            URL_MANUAL_UPDATE, "Landed on manual update page.");
         gBrowser.removeTab(gBrowser.selectedTab);
-        gMenuButtonUpdateBadge.reset();
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesMalformedXml.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesMalformedXml.js
--- a/browser/base/content/test/appUpdate/browser_updatesMalformedXml.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesMalformedXml.js
@@ -17,13 +17,12 @@ add_task(async function testMalformedXml
       beforeClick() {
         checkWhatsNewLink("update-manual-whats-new", updateDetailsUrl);
       },
       async cleanup() {
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         is(gBrowser.selectedBrowser.currentURI.spec,
            URL_MANUAL_UPDATE, "Landed on manual update page.")
         gBrowser.removeTab(gBrowser.selectedTab);
-        gMenuButtonUpdateBadge.reset();
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailure.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailure.js
rename from browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
--- a/browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
@@ -10,13 +10,13 @@ add_task(async function testPartialPatch
                                      null, null, null, null, "false",
                                      null, null, null, null, promptWaitTime);
 
   await runUpdateProcessingTest(updates, [
     {
       notificationId: "update-restart",
       button: "secondarybutton",
       cleanup() {
-        PanelUI.removeNotification(/.*/);
+        AppMenuNotifications.removeNotification(/.*/);
       }
     },
   ]);
 });
rename from browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteValidationFailure.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchApplyFailureWithCompleteValidationFailure.js
rename from browser/base/content/test/appUpdate/browser_updatesPartialPatchWithBadPartialSize.js
rename to toolkit/mozapps/update/tests/browser/browser_updatesPartialPatchWithBadPartialSize.js
--- a/toolkit/mozapps/update/tests/moz.build
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -3,16 +3,19 @@
 # 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/.
 
 HAS_MISC_RULE = True
 
 FINAL_TARGET = '_tests/xpcshell/toolkit/mozapps/update/tests/data'
 
+if not CONFIG['MOZ_SUITE']:
+  BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
+
 MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini']
 
 XPCSHELL_TESTS_MANIFESTS += [
     'unit_aus_update/xpcshell.ini',
     'unit_base_updater/xpcshell.ini'
 ]
 
 if CONFIG['MOZ_MAINTENANCE_SERVICE']:
@@ -55,16 +58,23 @@ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DEFINES['UNICODE'] = True
     DEFINES['_UNICODE'] = True
     USE_STATIC_LIBS = True
     if CONFIG['GNU_CC']:
         WIN32_EXE_LDFLAGS += ['-municode']
 
+TEST_HARNESS_FILES.testing.mochitest.browser.toolkit.mozapps.update.tests.browser += [
+    'chrome/update.sjs',
+    'data/shared.js',
+    'data/sharedUpdateXML.js',
+    'data/simple.mar',
+]
+
 TEST_HARNESS_FILES.testing.mochitest.chrome.toolkit.mozapps.update.tests.data += [
     'data/shared.js',
     'data/sharedUpdateXML.js',
     'data/simple.mar',
 ]
 
 FINAL_TARGET_FILES += [
     'data/complete.exe',
--- a/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
@@ -4,17 +4,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # For changes here, also consider ../Makefile.in
 
 XPCSHELLTESTDIR = $(topobjdir)/_tests/xpcshell/toolkit/mozapps/update/tests
 MOCHITESTCHROMEDIR = $(topobjdir)/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests
 
 ifeq (,$(MOZ_SUITE)$(MOZ_THUNDERBIRD))
-MOCHITESTBROWSERDIR = $(topobjdir)/_tests/testing/mochitest/browser/browser/base/content/test/appUpdate
+MOCHITESTBROWSERDIR = $(topobjdir)/_tests/testing/mochitest/browser/toolkit/mozapps/update/tests/browser
 endif
 
 include $(topsrcdir)/config/rules.mk
 
 ifndef MOZ_WINCONSOLE
 ifdef MOZ_DEBUG
 MOZ_WINCONSOLE = 1
 else