Bug 1359733 - (pt. 2) Refactor gMenuButtonUpdateBadge r=rstrong
authorDoug Thayer <dothayer@mozilla.com>
Fri, 19 May 2017 08:56:22 -0700
changeset 360331 2d6b455cc316b03b7a6b95084e7ccb350821a657
parent 360330 0574649a765336c30fbf91f5aadf71db7a3d0a79
child 360332 ec0fb144db75a3eb1b247090c348709de5ded6d1
push id90639
push userarchaeopteryx@coole-files.de
push dateWed, 24 May 2017 09:27:52 +0000
treeherdermozilla-inbound@7fc3bfbb3e59 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong
bugs1359733
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1359733 - (pt. 2) Refactor gMenuButtonUpdateBadge r=rstrong 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