[tor-commits] [tor-browser] 67/76: Bug 4234: Use the Firefox Update Process for Tor Browser.

gitolite role git at cupani.torproject.org
Fri Sep 23 17:16:54 UTC 2022


This is an automated email from the git hooks/post-receive script.

richard pushed a commit to branch tor-browser-102.3.0esr-12.0-1
in repository tor-browser.

commit bcde885958095fadf1206ffd4534f3406e438fcd
Author: Kathy Brade <brade at pearlcrescent.com>
AuthorDate: Fri Jan 13 11:40:24 2017 -0500

    Bug 4234: Use the Firefox Update Process for Tor Browser.
    
    The following files are never updated:
      TorBrowser/Data/Browser/profiles.ini
      TorBrowser/Data/Browser/profile.default/bookmarks.html
      TorBrowser/Data/Tor/torrc
    Mac OS: Store update metadata under TorBrowser/UpdateInfo.
    Removed the %OS_VERSION% component from the update URL (13047) and
      added support for minSupportedOSVersion, an attribute of the
      <update> element that may be used to trigger Firefox's
      "unsupported platform" behavior.
    Hide the "What's new" links (set app.releaseNotesURL value to about:blank).
    Windows: disable "runas" code path in updater (15201).
    Windows: avoid writing to the registry (16236).
    Also includes fixes for tickets 13047, 13301, 13356, 13594, 15406,
      16014, 16909, 24476, and 25909.
    
    Also fix Bug 26049: reduce the delay before the update prompt is displayed.
    Instead of Firefox's 2 days, we use 1 hour (after which time the update
    doorhanger will be displayed).
    
    Also fix bug 27221: purge the startup cache if the Tor Browser
    version changed (even if the Firefox version and build ID did
    not change), e.g., after a minor Tor Browser update.
    
    Also fix 32616: Disable GetSecureOutputDirectoryPath() functionality.
    
    Bug 26048: potentially confusing "restart to update" message
    
    Within the update doorhanger, remove the misleading message that mentions
    that windows will be restored after an update is applied, and replace the
    "Restart and Restore" button label with an existing
    "Restart to update Tor Browser" string.
    
    Bug 28885: notify users that update is downloading
    
    Add a "Downloading Tor Browser update" item which appears in the
    hamburger (app) menu while the update service is downloading a MAR
    file. Before this change, the browser did not indicate to the user
    that an update was in progress, which is especially confusing in
    Tor Browser because downloads often take some time. If the user
    clicks on the new menu item, the about dialog is opened to allow
    the user to see download progress.
    
    As part of this fix, the update service was changed to always show
    update-related messages in the hamburger menu, even if the update
    was started in the foreground via the about dialog or via the
    "Check for Tor Browser Update" toolbar menu item. This change is
    consistent with the Tor Browser goal of making sure users are
    informed about the update process.
    
    Removed #28885 parts of this patch which have been uplifted to Firefox.
---
 browser/app/Makefile.in                            |   2 +
 browser/app/profile/firefox.js                     |  10 +-
 browser/base/content/aboutDialog-appUpdater.js     |   2 +-
 browser/base/content/aboutDialog.js                |  14 +-
 browser/components/BrowserContentHandler.jsm       |  35 ++-
 .../customizableui/content/panelUI.inc.xhtml       |   2 +-
 browser/confvars.sh                                |  35 +--
 browser/installer/package-manifest.in              |   2 +
 build/application.ini.in                           |   2 +-
 build/moz.configure/init.configure                 |   3 +-
 config/createprecomplete.py                        |  18 +-
 .../client/aboutdebugging/src/actions/runtimes.js  |   5 +
 toolkit/modules/UpdateUtils.jsm                    |  31 +-
 toolkit/mozapps/extensions/AddonManager.jsm        |  25 ++
 toolkit/mozapps/extensions/test/browser/head.js    |   1 +
 .../extensions/test/xpcshell/head_addons.js        |   1 +
 toolkit/mozapps/update/UpdateService.jsm           | 200 ++++++++++---
 toolkit/mozapps/update/UpdateServiceStub.jsm       |   4 +
 toolkit/mozapps/update/common/updatehelper.cpp     |   8 +
 toolkit/mozapps/update/updater/launchchild_osx.mm  |   2 +
 toolkit/mozapps/update/updater/moz.build           |   2 +-
 toolkit/mozapps/update/updater/updater.cpp         | 325 ++++++++++++++++++---
 toolkit/xre/MacLaunchHelper.h                      |   2 +
 toolkit/xre/MacLaunchHelper.mm                     |   2 +
 toolkit/xre/nsAppRunner.cpp                        |  22 +-
 toolkit/xre/nsUpdateDriver.cpp                     | 130 ++++++++-
 toolkit/xre/nsXREDirProvider.cpp                   |  38 ++-
 tools/update-packaging/common.sh                   |  64 ++--
 tools/update-packaging/make_full_update.sh         |  25 ++
 tools/update-packaging/make_incremental_update.sh  |  71 ++++-
 30 files changed, 905 insertions(+), 178 deletions(-)

diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in
index 8dd3a9a65661..3a5550c96c15 100644
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -97,10 +97,12 @@ tools repackage:: $(DIST)/bin/$(MOZ_APP_NAME) $(objdir)/macbuild/Contents/MacOS-
 	rsync -aL $(DIST)/bin/$(MOZ_APP_NAME) '$(dist_dest)/Contents/MacOS'
 	cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/firefox.icns '$(dist_dest)/Contents/Resources/firefox.icns'
 	cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/document.icns '$(dist_dest)/Contents/Resources/document.icns'
+ifndef TOR_BROWSER_UPDATE
 	$(MKDIR) -p '$(dist_dest)/Contents/Library/LaunchServices'
 ifdef MOZ_UPDATER
 	mv -f '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' '$(dist_dest)/Contents/Library/LaunchServices'
 	ln -s ../../../../Library/LaunchServices/org.mozilla.updater '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater'
+endif
 endif
 	printf APPLTORB > '$(dist_dest)/Contents/PkgInfo'
 endif
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
index 70e71c0b993e..b86b202646cd 100644
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -140,14 +140,8 @@ pref("app.update.elevation.promptMaxAttempts", 2);
 pref("app.update.notifyDuringDownload", false);
 
 // If set to true, the Update Service will automatically download updates if the
-// user can apply updates. This pref is no longer used on Windows, except as the
-// default value to migrate to the new location that this data is now stored
-// (which is in a file in the update directory). Because of this, this pref
-// should no longer be used directly. Instead, getAppUpdateAutoEnabled and
-// getAppUpdateAutoEnabled from UpdateUtils.jsm should be used.
-#ifndef XP_WIN
-  pref("app.update.auto", true);
-#endif
+// user can apply updates.
+pref("app.update.auto", true);
 
 // If set to true, the Update Service will apply updates in the background
 // when it finishes downloading them.
diff --git a/browser/base/content/aboutDialog-appUpdater.js b/browser/base/content/aboutDialog-appUpdater.js
index b29726c1aab2..d75c5444b84f 100644
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -174,7 +174,7 @@ appUpdater.prototype = {
       if (aChildID == "downloadAndInstall") {
         let updateVersion = gAppUpdater.update.displayVersion;
         // Include the build ID if this is an "a#" (nightly or aurora) build
-        if (/a\d+$/.test(updateVersion)) {
+        if (!AppConstants.TOR_BROWSER_UPDATE && /a\d+$/.test(updateVersion)) {
           let buildID = gAppUpdater.update.buildID;
           let year = buildID.slice(0, 4);
           let month = buildID.slice(4, 6);
diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js
index 69e7c510b305..458a52201e82 100644
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -54,15 +54,15 @@ async function init(aEvent) {
     bits: Services.appinfo.is64Bit ? 64 : 32,
   };
 
+  // Adjust version text to show the Tor Browser version
+  versionAttributes.version =
+    AppConstants.TOR_BROWSER_VERSION +
+    " (based on Mozilla Firefox " +
+    AppConstants.MOZ_APP_VERSION_DISPLAY +
+    ")";
+
   let version = Services.appinfo.version;
   if (/a\d+$/.test(version)) {
-    versionId = "aboutDialog-version-nightly";
-    let buildID = Services.appinfo.appBuildID;
-    let year = buildID.slice(0, 4);
-    let month = buildID.slice(4, 6);
-    let day = buildID.slice(6, 8);
-    versionAttributes.isodate = `${year}-${month}-${day}`;
-
     document.getElementById("experimental").hidden = false;
     document.getElementById("communityDesc").hidden = true;
   }
diff --git a/browser/components/BrowserContentHandler.jsm b/browser/components/BrowserContentHandler.jsm
index 58edd37d9599..714a5fac8bf0 100644
--- a/browser/components/BrowserContentHandler.jsm
+++ b/browser/components/BrowserContentHandler.jsm
@@ -37,6 +37,9 @@ XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal", () =>
 );
 XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
 
+const kTBSavedVersionPref =
+  "browser.startup.homepage_override.torbrowser.version";
+
 // One-time startup homepage override configurations
 const ONCE_DOMAINS = ["mozilla.org", "firefox.com"];
 const ONCE_PREF = "browser.startup.homepage_override.once";
@@ -100,7 +103,8 @@ const OVERRIDE_NEW_BUILD_ID = 3;
  * Returns:
  *  OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
  *  OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
- *                      Gecko milestone (i.e. right after an upgrade).
+ *                      Gecko milestone or Tor Browser version (i.e. right
+ *                      after an upgrade).
  *  OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
  *                        same Gecko milestone (i.e. after a nightly upgrade).
  *  OVERRIDE_NONE otherwise.
@@ -117,6 +121,8 @@ function needHomepageOverride(prefb) {
 
   var mstone = Services.appinfo.platformVersion;
 
+  var savedTBVersion = prefb.getCharPref(kTBSavedVersionPref, null);
+
   var savedBuildID = prefb.getCharPref(
     "browser.startup.homepage_override.buildID",
     ""
@@ -138,7 +144,21 @@ function needHomepageOverride(prefb) {
 
     prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
-    return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
+    prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION);
+
+    // After an upgrade from an older release of Tor Browser (<= 5.5a1), the
+    // savedmstone will be undefined because those releases included the
+    // value "ignore" for the browser.startup.homepage_override.mstone pref.
+    // To correctly detect an upgrade vs. a new profile, we check for the
+    // presence of the "app.update.postupdate" pref.
+    let updated = prefb.prefHasUserValue("app.update.postupdate");
+    return savedmstone || updated ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
+  }
+
+  if (AppConstants.TOR_BROWSER_VERSION != savedTBVersion) {
+    prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+    prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION);
+    return OVERRIDE_NEW_MSTONE;
   }
 
   if (buildID != savedBuildID) {
@@ -651,6 +671,10 @@ nsBrowserContentHandler.prototype = {
         "browser.startup.homepage_override.buildID",
         "unknown"
       );
+
+      // We do the same for the Tor Browser version.
+      let old_tbversion = prefb.getCharPref(kTBSavedVersionPref, null);
+
       override = needHomepageOverride(prefb);
       if (override != OVERRIDE_NONE) {
         switch (override) {
@@ -677,9 +701,10 @@ nsBrowserContentHandler.prototype = {
               "startup.homepage_override_url"
             );
             let update = UpdateManager.readyUpdate;
+            let old_version = old_tbversion ? old_tbversion : old_mstone;
             if (
               update &&
-              Services.vc.compare(update.appVersion, old_mstone) > 0
+              Services.vc.compare(update.appVersion, old_version) > 0
             ) {
               overridePage = getPostUpdateOverridePage(update, overridePage);
               // Send the update ping to signal that the update was successful.
@@ -687,6 +712,10 @@ nsBrowserContentHandler.prototype = {
             }
 
             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
+            overridePage = overridePage.replace(
+              "%OLD_TOR_BROWSER_VERSION%",
+              old_tbversion
+            );
             break;
           case OVERRIDE_NEW_BUILD_ID:
             if (UpdateManager.readyUpdate) {
diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/browser/components/customizableui/content/panelUI.inc.xhtml
index 028dbf26e636..fdcfa732aa59 100644
--- a/browser/components/customizableui/content/panelUI.inc.xhtml
+++ b/browser/components/customizableui/content/panelUI.inc.xhtml
@@ -151,7 +151,7 @@
                        hasicon="true"
                        hidden="true">
       <popupnotificationcontent id="update-restart-notification-content" orient="vertical">
-        <description id="update-restart-description" data-lazy-l10n-id="appmenu-update-restart-message2"></description>
+        <description id="update-restart-description"> </description>
       </popupnotificationcontent>
     </popupnotification>
 
diff --git a/browser/confvars.sh b/browser/confvars.sh
index 6fa324be930b..83979ddf4d5b 100755
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -5,26 +5,6 @@
 
 MOZ_APP_VENDOR=Mozilla
 
-if test "$OS_ARCH" = "WINNT"; then
-  if ! test "$HAVE_64BIT_BUILD"; then
-    if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \
-            "$MOZ_UPDATE_CHANNEL" = "nightly-try" -o \
-            "$MOZ_UPDATE_CHANNEL" = "aurora" -o \
-            "$MOZ_UPDATE_CHANNEL" = "beta" -o \
-            "$MOZ_UPDATE_CHANNEL" = "release"; then
-      if ! test "$MOZ_DEBUG"; then
-        if ! test "$USE_STUB_INSTALLER"; then
-          # Expect USE_STUB_INSTALLER from taskcluster for downstream task consistency
-          echo "ERROR: STUB installer expected to be enabled but"
-          echo "ERROR: USE_STUB_INSTALLER is not specified in the environment"
-          exit 1
-        fi
-        MOZ_STUB_INSTALLER=1
-      fi
-    fi
-  fi
-fi
-
 BROWSER_CHROME_URL=chrome://browser/content/browser.xhtml
 
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
@@ -37,6 +17,21 @@ MOZ_BRANDING_DIRECTORY=browser/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
 MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
 
+# ACCEPTED_MAR_CHANNEL_IDS should usually be the same as the value MAR_CHANNEL_ID.
+# If more than one ID is needed, then you should use a comma separated list
+# of values.
+# The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
+if test "$MOZ_UPDATE_CHANNEL" = "alpha"; then
+  ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-alpha
+  MAR_CHANNEL_ID=torbrowser-torproject-alpha
+elif test "$MOZ_UPDATE_CHANNEL" = "nightly"; then
+  ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-nightly
+  MAR_CHANNEL_ID=torbrowser-torproject-nightly
+else
+  ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-release
+  MAR_CHANNEL_ID=torbrowser-torproject-release
+fi
+
 MOZ_PROFILE_MIGRATOR=1
 
 # Include the DevTools client, not just the server (which is the default)
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index 84939f55d210..1cffb5564030 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -36,8 +36,10 @@
 ; Mac bundle stuff
 @APPNAME@/Contents/Info.plist
 #ifdef MOZ_UPDATER
+#ifndef TOR_BROWSER_UPDATE
 @APPNAME@/Contents/Library/LaunchServices
 #endif
+#endif
 @APPNAME@/Contents/PkgInfo
 @RESPATH@/firefox.icns
 @RESPATH@/document.icns
diff --git a/build/application.ini.in b/build/application.ini.in
index 6df13230a45b..9a0b162d447d 100644
--- a/build/application.ini.in
+++ b/build/application.ini.in
@@ -52,5 +52,5 @@ ServerURL=@MOZ_CRASHREPORTER_URL@/submit?id=@MOZ_APP_ID@&version=@MOZ_APP_VERSIO
 
 #if MOZ_UPDATER
 [AppUpdate]
-URL=https://@MOZ_APPUPDATE_HOST@/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml
+URL=https://aus1.torproject.org/torbrowser/update_3/%CHANNEL%/%BUILD_TARGET%/%VERSION%/%LOCALE%
 #endif
diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure
index 81f500a0b752..a85df3393c16 100644
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -957,7 +957,6 @@ def version_path(path):
 # set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in
 # The logic works like this:
 # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
-# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
 # - otherwise, we're building Release/Beta (define RELEASE_OR_BETA)
 @depends(build_environment, build_project, version_path, "--help")
 @imports(_from="__builtin__", _import="open")
@@ -1004,7 +1003,7 @@ def milestone(build_env, build_project, version_path, _):
 
     if "a1" in milestone:
         is_nightly = True
-    elif "a" not in milestone:
+    else:
         is_release_or_beta = True
 
     major_version = milestone.split(".")[0]
diff --git a/config/createprecomplete.py b/config/createprecomplete.py
index dda4efcdf8e1..e7405b21b61b 100644
--- a/config/createprecomplete.py
+++ b/config/createprecomplete.py
@@ -5,6 +5,7 @@
 # update instructions which is used to remove files and directories that are no
 # longer present in a complete update. The current working directory is used for
 # the location to enumerate and to create the precomplete file.
+# For symlinks, remove instructions are always generated.
 
 from __future__ import absolute_import
 from __future__ import unicode_literals
@@ -13,9 +14,17 @@ import os
 import io
 
 
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
+# we should remove all lines in this file that contain:
+#      TorBrowser/Data
+
 def get_build_entries(root_path):
     """Iterates through the root_path, creating a list for each file and
     directory. Excludes any file paths ending with channel-prefs.js.
+    To support Tor Browser updates, excludes:
+      TorBrowser/Data/Browser/profiles.ini
+      TorBrowser/Data/Browser/profile.default/bookmarks.html
+      TorBrowser/Data/Tor/torrc
     """
     rel_file_path_set = set()
     rel_dir_path_set = set()
@@ -27,6 +36,10 @@ def get_build_entries(root_path):
             if not (
                 rel_path_file.endswith("channel-prefs.js")
                 or rel_path_file.endswith("update-settings.ini")
+                or rel_path_file == "TorBrowser/Data/Browser/profiles.ini"
+                or rel_path_file
+                == "TorBrowser/Data/Browser/profile.default/bookmarks.html"
+                or rel_path_file == "TorBrowser/Data/Tor/torrc"
                 or rel_path_file.find("distribution/") != -1
             ):
                 rel_file_path_set.add(rel_path_file)
@@ -36,7 +49,10 @@ def get_build_entries(root_path):
             rel_path_dir = os.path.join(parent_dir_rel_path, dir_name)
             rel_path_dir = rel_path_dir.replace("\\", "/") + "/"
             if rel_path_dir.find("distribution/") == -1:
-                rel_dir_path_set.add(rel_path_dir)
+                if os.path.islink(rel_path_dir[:-1]):
+                    rel_file_path_set.add(rel_path_dir[:-1])
+                else:
+                    rel_dir_path_set.add(rel_path_dir)
 
     rel_file_path_list = list(rel_file_path_set)
     rel_file_path_list.sort(reverse=True)
diff --git a/devtools/client/aboutdebugging/src/actions/runtimes.js b/devtools/client/aboutdebugging/src/actions/runtimes.js
index 0eb7468e2847..dcf1bde3a574 100644
--- a/devtools/client/aboutdebugging/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging/src/actions/runtimes.js
@@ -71,6 +71,11 @@ async function getRuntimeIcon(runtime, channel) {
     }
   }
 
+  // Use the release build skin for devtools within Tor Browser alpha releases.
+  if (channel === "alpha") {
+    return "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg";
+  }
+
   return channel === "release" || channel === "beta" || channel === "aurora"
     ? `chrome://devtools/skin/images/aboutdebugging-firefox-${channel}.svg`
     : "chrome://devtools/skin/images/aboutdebugging-firefox-nightly.svg";
diff --git a/toolkit/modules/UpdateUtils.jsm b/toolkit/modules/UpdateUtils.jsm
index 10e17cf57375..06e349780fa3 100644
--- a/toolkit/modules/UpdateUtils.jsm
+++ b/toolkit/modules/UpdateUtils.jsm
@@ -87,7 +87,7 @@ var UpdateUtils = {
           case "PRODUCT":
             return Services.appinfo.name;
           case "VERSION":
-            return Services.appinfo.version;
+            return AppConstants.TOR_BROWSER_VERSION;
           case "BUILD_ID":
             return Services.appinfo.appBuildID;
           case "BUILD_TARGET":
@@ -167,7 +167,8 @@ var UpdateUtils = {
    * downloads and installs updates. This corresponds to whether or not the user
    * has selected "Automatically install updates" in about:preferences.
    *
-   * On Windows, this setting is shared across all profiles for the installation
+   * On Windows (except in Tor Browser), this setting is shared across all profiles
+   * for the installation
    * and is read asynchronously from the file. On other operating systems, this
    * setting is stored in a pref and is thus a per-profile setting.
    *
@@ -183,7 +184,8 @@ var UpdateUtils = {
    * updates" and "Check for updates but let you choose to install them" options
    * in about:preferences.
    *
-   * On Windows, this setting is shared across all profiles for the installation
+   * On Windows (except in Tor Browser), this setting is shared across all profiles
+   * for the installation
    * and is written asynchronously to the file. On other operating systems, this
    * setting is stored in a pref and is thus a per-profile setting.
    *
@@ -255,7 +257,10 @@ var UpdateUtils = {
     // setting is just to propagate it from a pref observer. This ensures that
     // the expected observers still get notified, even if a user manually
     // changes the pref value.
-    if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) {
+    if (
+      AppConstants.TOR_BROWSER_UPDATE ||
+      !UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED
+    ) {
       let initialConfig = {};
       for (const [prefName, pref] of Object.entries(
         UpdateUtils.PER_INSTALLATION_PREFS
@@ -324,7 +329,10 @@ var UpdateUtils = {
       }
     }
 
-    if (!this.PER_INSTALLATION_PREFS_SUPPORTED) {
+    if (
+      AppConstants.TOR_BROWSER_UPDATE ||
+      !this.PER_INSTALLATION_PREFS_SUPPORTED
+    ) {
       // If we don't have per-installation prefs, we use regular preferences.
       let prefValue = prefTypeFns.getProfilePref(prefName, pref.defaultValue);
       return Promise.resolve(prefValue);
@@ -419,7 +427,10 @@ var UpdateUtils = {
       );
     }
 
-    if (!this.PER_INSTALLATION_PREFS_SUPPORTED) {
+    if (
+      AppConstants.TOR_BROWSER_UPDATE ||
+      !this.PER_INSTALLATION_PREFS_SUPPORTED
+    ) {
       // If we don't have per-installation prefs, we use regular preferences.
       if (options.setDefaultOnly) {
         prefTypeFns.setProfileDefaultPref(prefName, value);
@@ -555,14 +566,6 @@ UpdateUtils.PER_INSTALLATION_PREFS = {
     migrate: true,
     observerTopic: "auto-update-config-change",
     policyFn: () => {
-      if (!Services.policies.isAllowed("app-auto-updates-off")) {
-        // We aren't allowed to turn off auto-update - it is forced on.
-        return true;
-      }
-      if (!Services.policies.isAllowed("app-auto-updates-on")) {
-        // We aren't allowed to turn on auto-update - it is forced off.
-        return false;
-      }
       return null;
     },
   },
diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm
index f525710f9053..424387dd02dd 100644
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -40,6 +40,7 @@ const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility";
 const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
 const PREF_SYS_ADDON_UPDATE_ENABLED = "extensions.systemAddon.update.enabled";
 const PREF_REMOTESETTINGS_DISABLED = "extensions.remoteSettings.disabled";
+const PREF_EM_LAST_TORBROWSER_VERSION = "extensions.lastTorBrowserVersion";
 
 const PREF_MIN_WEBEXT_PLATFORM_VERSION =
   "extensions.webExtensionsMinPlatformVersion";
@@ -644,6 +645,30 @@ var AddonManagerInternal = {
         );
       }
 
+      // To ensure that extension and plugin code gets a chance to run
+      // after each browser update, set appChanged = true when the
+      // Tor Browser version has changed even if the Mozilla app
+      // version has not changed.
+      let tbChanged = undefined;
+      try {
+        tbChanged =
+          AppConstants.TOR_BROWSER_VERSION !==
+          Services.prefs.getCharPref(PREF_EM_LAST_TORBROWSER_VERSION);
+      } catch (e) {}
+      if (tbChanged !== false) {
+        // Because PREF_EM_LAST_TORBROWSER_VERSION was not present in older
+        // versions of Tor Browser, an app change is indicated when tbChanged
+        // is undefined or true.
+        if (appChanged === false) {
+          appChanged = true;
+        }
+
+        Services.prefs.setCharPref(
+          PREF_EM_LAST_TORBROWSER_VERSION,
+          AppConstants.TOR_BROWSER_VERSION
+        );
+      }
+
       if (!MOZ_COMPATIBILITY_NIGHTLY) {
         PREF_EM_CHECK_COMPATIBILITY =
           PREF_EM_CHECK_COMPATIBILITY_BASE +
diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js
index 95321e7ad3bd..2e7a1312c540 100644
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -41,6 +41,7 @@ var PREF_CHECK_COMPATIBILITY;
   var channel = Services.prefs.getCharPref("app.update.channel", "default");
   if (
     channel != "aurora" &&
+    channel != "alpha" &&
     channel != "beta" &&
     channel != "release" &&
     channel != "esr"
diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
index 88aa66c1c412..46d6651bcf4a 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -386,6 +386,7 @@ function isNightlyChannel() {
 
   return (
     channel != "aurora" &&
+    channel != "alpha" &&
     channel != "beta" &&
     channel != "release" &&
     channel != "esr"
diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm
index a81317f484ec..ddef19a3782c 100644
--- a/toolkit/mozapps/update/UpdateService.jsm
+++ b/toolkit/mozapps/update/UpdateService.jsm
@@ -12,6 +12,19 @@ const { AppConstants } = ChromeUtils.import(
 const { AUSTLMY } = ChromeUtils.import(
   "resource://gre/modules/UpdateTelemetry.jsm"
 );
+
+const { TorProtocolService } = ChromeUtils.import(
+  "resource:///modules/TorProtocolService.jsm"
+);
+
+function _shouldRegisterBootstrapObserver(errorCode) {
+  return (
+    errorCode == PROXY_SERVER_CONNECTION_REFUSED &&
+    !TorProtocolService.isBootstrapDone() &&
+    TorProtocolService.ownsTorDaemon
+  );
+}
+
 const {
   Bits,
   BitsRequest,
@@ -245,6 +258,7 @@ const SERVICE_ERRORS = [
 // Custom update error codes
 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
 const NETWORK_ERROR_OFFLINE = 111;
+const PROXY_SERVER_CONNECTION_REFUSED = 2152398920;
 
 // Error codes should be < 1000. Errors above 1000 represent http status codes
 const HTTP_ERROR_OFFSET = 1000;
@@ -662,6 +676,11 @@ function areDirectoryEntriesWriteable(aDir) {
  * @return true if elevation is required, false otherwise
  */
 function getElevationRequired() {
+  if (AppConstants.TOR_BROWSER_UPDATE) {
+    // To avoid potential security holes associated with running the updater
+    // process with elevated privileges, Tor Browser does not support elevation.
+    return false;
+  }
   if (AppConstants.platform != "macosx") {
     return false;
   }
@@ -741,20 +760,22 @@ function getCanApplyUpdates() {
     return false;
   }
 
-  if (AppConstants.platform == "macosx") {
-    LOG(
-      "getCanApplyUpdates - bypass the write since elevation can be used " +
-        "on Mac OS X"
-    );
-    return true;
-  }
+  if (!AppConstants.TOR_BROWSER_UPDATE) {
+    if (AppConstants.platform == "macosx") {
+      LOG(
+        "getCanApplyUpdates - bypass the write since elevation can be used " +
+          "on Mac OS X"
+      );
+      return true;
+    }
 
-  if (shouldUseService()) {
-    LOG(
-      "getCanApplyUpdates - bypass the write checks because the Windows " +
-        "Maintenance Service can be used"
-    );
-    return true;
+    if (shouldUseService()) {
+      LOG(
+        "getCanApplyUpdates - bypass the write checks because the Windows " +
+          "Maintenance Service can be used"
+      );
+      return true;
+    }
   }
 
   try {
@@ -1567,28 +1588,32 @@ function handleUpdateFailure(update, errorCode) {
     cancelations++;
     Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
     if (AppConstants.platform == "macosx") {
-      let osxCancelations = Services.prefs.getIntPref(
-        PREF_APP_UPDATE_CANCELATIONS_OSX,
-        0
-      );
-      osxCancelations++;
-      Services.prefs.setIntPref(
-        PREF_APP_UPDATE_CANCELATIONS_OSX,
-        osxCancelations
-      );
-      let maxCancels = Services.prefs.getIntPref(
-        PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
-        DEFAULT_CANCELATIONS_OSX_MAX
-      );
-      // Prevent the preference from setting a value greater than 5.
-      maxCancels = Math.min(maxCancels, 5);
-      if (osxCancelations >= maxCancels) {
-        cleanupReadyUpdate();
+      if (AppConstants.TOR_BROWSER_UPDATE) {
+        cleanupActiveUpdates();
       } else {
-        writeStatusFile(
-          getReadyUpdateDir(),
-          (update.state = STATE_PENDING_ELEVATE)
+        let osxCancelations = Services.prefs.getIntPref(
+          PREF_APP_UPDATE_CANCELATIONS_OSX,
+          0
+        );
+        osxCancelations++;
+        Services.prefs.setIntPref(
+          PREF_APP_UPDATE_CANCELATIONS_OSX,
+          osxCancelations
         );
+        let maxCancels = Services.prefs.getIntPref(
+          PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+          DEFAULT_CANCELATIONS_OSX_MAX
+        );
+        // Prevent the preference from setting a value greater than 5.
+        maxCancels = Math.min(maxCancels, 5);
+        if (osxCancelations >= maxCancels) {
+          cleanupReadyUpdate();
+        } else {
+          writeStatusFile(
+            getReadyUpdateDir(),
+            (update.state = STATE_PENDING_ELEVATE)
+          );
+        }
       }
       update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
     } else {
@@ -1809,6 +1834,15 @@ function updateIsAtLeastAsOldAs(update, version, buildID) {
   );
 }
 
+/**
+ * This returns the current version of the browser to use to check updates.
+ */
+function getCompatVersion() {
+  return AppConstants.TOR_BROWSER_VERSION
+    ? AppConstants.TOR_BROWSER_VERSION
+    : Services.appinfo.version;
+}
+
 /**
  * This returns true if the passed update is the same version or older than
  * currently installed Firefox version.
@@ -1816,7 +1850,7 @@ function updateIsAtLeastAsOldAs(update, version, buildID) {
 function updateIsAtLeastAsOldAsCurrentVersion(update) {
   return updateIsAtLeastAsOldAs(
     update,
-    Services.appinfo.version,
+    getCompatVersion(),
     Services.appinfo.appBuildID
   );
 }
@@ -2105,7 +2139,31 @@ function Update(update) {
     this._patches.push(patch);
   }
 
-  if (!this._patches.length && !update.hasAttribute("unsupported")) {
+  if (update.hasAttribute("unsupported")) {
+    this.unsupported = "true" == update.getAttribute("unsupported");
+  } else if (update.hasAttribute("minSupportedOSVersion")) {
+    let minOSVersion = update.getAttribute("minSupportedOSVersion");
+    try {
+      let osVersion = Services.sysinfo.getProperty("version");
+      this.unsupported = Services.vc.compare(osVersion, minOSVersion) < 0;
+    } catch (e) {}
+  }
+  if (!this.unsupported && update.hasAttribute("minSupportedInstructionSet")) {
+    let minInstructionSet = update.getAttribute("minSupportedInstructionSet");
+    if (
+      ["MMX", "SSE", "SSE2", "SSE3", "SSE4A", "SSE4_1", "SSE4_2"].includes(
+        minInstructionSet
+      )
+    ) {
+      try {
+        this.unsupported = !Services.sysinfo.getProperty(
+          "has" + minInstructionSet
+        );
+      } catch (e) {}
+    }
+  }
+
+  if (!this._patches.length && !this.unsupported) {
     throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
   }
 
@@ -2143,9 +2201,7 @@ function Update(update) {
       if (!isNaN(attr.value)) {
         this.promptWaitTime = parseInt(attr.value);
       }
-    } else if (attr.name == "unsupported") {
-      this.unsupported = attr.value == "true";
-    } else {
+    } else if (attr.name != "unsupported") {
       switch (attr.name) {
         case "appVersion":
         case "buildID":
@@ -2170,7 +2226,7 @@ function Update(update) {
   }
 
   if (!this.previousAppVersion) {
-    this.previousAppVersion = Services.appinfo.version;
+    this.previousAppVersion = getCompatVersion();
   }
 
   if (!this.elevationFailure) {
@@ -2529,6 +2585,9 @@ UpdateService.prototype = {
       case "network:offline-status-changed":
         this._offlineStatusChanged(data);
         break;
+      case "torconnect:bootstrap-complete":
+        this._bootstrapComplete();
+        break;
       case "nsPref:changed":
         if (data == PREF_APP_UPDATE_LOG || data == PREF_APP_UPDATE_LOG_FILE) {
           gLogEnabled; // Assigning this before it is lazy-loaded is an error.
@@ -2985,6 +3044,35 @@ UpdateService.prototype = {
     this._attemptResume();
   },
 
+  _registerBootstrapObserver: function AUS__registerBootstrapObserver() {
+    if (this._registeredBootstrapObserver) {
+      LOG(
+        "UpdateService:_registerBootstrapObserver - observer already registered"
+      );
+      return;
+    }
+
+    LOG(
+      "UpdateService:_registerBootstrapObserver - waiting for tor bootstrap to " +
+        "be complete, then forcing another check"
+    );
+
+    Services.obs.addObserver(this, "torconnect:bootstrap-complete");
+    this._registeredBootstrapObserver = true;
+  },
+
+  _bootstrapComplete: function AUS__bootstrapComplete() {
+    Services.obs.removeObserver(this, "torconnect:bootstrap-complete");
+    this._registeredBootstrapObserver = false;
+
+    LOG(
+      "UpdateService:_bootstrapComplete - bootstrapping complete, forcing " +
+        "another background check"
+    );
+
+    this._attemptResume();
+  },
+
   onCheckComplete: async function AUS_onCheckComplete(request, updates) {
     await this._selectAndInstallUpdate(updates);
   },
@@ -3004,6 +3092,11 @@ UpdateService.prototype = {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE);
       }
       return;
+    } else if (_shouldRegisterBootstrapObserver(update.errorCode)) {
+      // Register boostrap observer to try again, but only when we own the
+      // tor process.
+      this._registerBootstrapObserver();
+      return;
     }
 
     // Send the error code to telemetry
@@ -3833,7 +3926,7 @@ UpdateService.prototype = {
         "UpdateService:downloadUpdate - canceling download of update since " +
           "it is for an earlier or same application version and build ID.\n" +
           "current application version: " +
-          Services.appinfo.version +
+          getCompatVersion() +
           "\n" +
           "update application version : " +
           update.appVersion +
@@ -4507,7 +4600,7 @@ Checker.prototype = {
   _callback: null,
 
   _getCanMigrate: function UC__getCanMigrate() {
-    if (AppConstants.platform != "win") {
+    if (AppConstants.platform != "win" || AppConstants.TOR_BROWSER_VERSION) {
       return false;
     }
 
@@ -5769,6 +5862,7 @@ Downloader.prototype = {
     var state = this._patch.state;
     var shouldShowPrompt = false;
     var shouldRegisterOnlineObserver = false;
+    var shouldRegisterBootstrapObserver = false;
     var shouldRetrySoon = false;
     var deleteActiveUpdate = false;
     let migratedToReadyUpdate = false;
@@ -5880,7 +5974,20 @@ Downloader.prototype = {
       );
       shouldRegisterOnlineObserver = true;
       deleteActiveUpdate = false;
-
+    } else if (_shouldRegisterBootstrapObserver(status)) {
+      // Register a bootstrap observer to try again.
+      // The bootstrap observer will continue the incremental download by
+      // calling downloadUpdate on the active update which continues
+      // downloading the file from where it was.
+      LOG(
+        "Downloader:onStopRequest - not bootstrapped, register bootstrap observer: true"
+      );
+      AUSTLMY.pingDownloadCode(
+        this.isCompleteUpdate,
+        AUSTLMY.DWNLD_RETRY_OFFLINE
+      );
+      shouldRegisterBootstrapObserver = true;
+      deleteActiveUpdate = false;
       // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED,
       // NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned
       // when disconnecting the internet while a download of a MAR is in
@@ -5994,7 +6101,11 @@ Downloader.prototype = {
 
     // Only notify listeners about the stopped state if we
     // aren't handling an internal retry.
-    if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
+    if (
+      !shouldRetrySoon &&
+      !shouldRegisterOnlineObserver &&
+      !shouldRegisterBootstrapObserver
+    ) {
       this.updateService.forEachDownloadListener(listener => {
         listener.onStopRequest(request, status);
       });
@@ -6179,6 +6290,9 @@ Downloader.prototype = {
     if (shouldRegisterOnlineObserver) {
       LOG("Downloader:onStopRequest - Registering online observer");
       this.updateService._registerOnlineObserver();
+    } else if (shouldRegisterBootstrapObserver) {
+      LOG("Downloader:onStopRequest - Registering bootstrap observer");
+      this.updateService._registerBootstrapObserver();
     } else if (shouldRetrySoon) {
       LOG("Downloader:onStopRequest - Retrying soon");
       this.updateService._consecutiveSocketErrors++;
diff --git a/toolkit/mozapps/update/UpdateServiceStub.jsm b/toolkit/mozapps/update/UpdateServiceStub.jsm
index fdccbc19db2c..69d89f466862 100644
--- a/toolkit/mozapps/update/UpdateServiceStub.jsm
+++ b/toolkit/mozapps/update/UpdateServiceStub.jsm
@@ -80,8 +80,12 @@ function UpdateServiceStub() {
   // contains the status file's path
 
   // We may need to migrate update data
+  // In Tor Browser we skip this because we do not use an update agent and we
+  // do not want to store any data outside of the browser installation directory.
+  // For more info, see https://bugzilla.mozilla.org/show_bug.cgi?id=1458314
   if (
     AppConstants.platform == "win" &&
+    !AppConstants.TOR_BROWSER_UPDATE &&
     !Services.prefs.getBoolPref(prefUpdateDirMigrated, false)
   ) {
     Services.prefs.setBoolPref(prefUpdateDirMigrated, true);
diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp
index b094d9eb75e9..c825d3c1ea8e 100644
--- a/toolkit/mozapps/update/common/updatehelper.cpp
+++ b/toolkit/mozapps/update/common/updatehelper.cpp
@@ -66,6 +66,13 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
  * @return TRUE if successful
  */
 BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) {
+#ifdef TOR_BROWSER_UPDATE
+  // This function is used to support the maintenance service and elevated
+  // updates and is therefore not called by Tor Browser's updater. We stub
+  // it out to avoid any chance that the Tor Browser updater will create
+  // files under C:\Program Files (x86)\ or a similar location.
+  return FALSE;
+#else
   PWSTR progFilesX86;
   if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_CREATE,
                                   nullptr, &progFilesX86))) {
@@ -99,6 +106,7 @@ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) {
   }
 
   return TRUE;
+#endif
 }
 
 /**
diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm
index 7721da8e5976..735c98ee5a90 100644
--- a/toolkit/mozapps/update/updater/launchchild_osx.mm
+++ b/toolkit/mozapps/update/updater/launchchild_osx.mm
@@ -372,6 +372,7 @@ bool ObtainUpdaterArguments(int* argc, char*** argv) {
 
 @end
 
+#ifndef TOR_BROWSER_UPDATE
 bool ServeElevatedUpdate(int argc, const char** argv) {
   MacAutoreleasePool pool;
 
@@ -387,6 +388,7 @@ bool ServeElevatedUpdate(int argc, const char** argv) {
   [updater release];
   return didSucceed;
 }
+#endif
 
 bool IsOwnedByGroupAdmin(const char* aAppBundle) {
   MacAutoreleasePool pool;
diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build
index 40d7a77a6b62..ac7f82a4f9ad 100644
--- a/toolkit/mozapps/update/updater/moz.build
+++ b/toolkit/mozapps/update/updater/moz.build
@@ -51,7 +51,7 @@ xpcshell_cert.script = "gen_cert_header.py:create_header"
 dep1_cert.script = "gen_cert_header.py:create_header"
 dep2_cert.script = "gen_cert_header.py:create_header"
 
-if CONFIG["MOZ_UPDATE_CHANNEL"] in ("beta", "release", "esr"):
+if CONFIG["MOZ_UPDATE_CHANNEL"] in ("alpha", "beta", "release", "esr"):
     primary_cert.inputs += ["release_primary.der"]
     secondary_cert.inputs += ["release_secondary.der"]
 elif CONFIG["MOZ_UPDATE_CHANNEL"] in (
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index 2c13b6e45226..2dc08e19957c 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -16,7 +16,7 @@
  *  updatev3.manifest
  *  -----------------
  *  method   = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
- *             "remove" | "rmdir" | "rmrfdir" | type
+ *             "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type
  *
  *  'add-if-not' adds a file if it doesn't exist.
  *
@@ -75,7 +75,9 @@ bool IsRecursivelyWritable(const char* aPath);
 void LaunchChild(int argc, const char** argv);
 void LaunchMacPostProcess(const char* aAppBundle);
 bool ObtainUpdaterArguments(int* argc, char*** argv);
+#  ifndef TOR_BROWSER_UPDATE
 bool ServeElevatedUpdate(int argc, const char** argv);
+#  endif
 void SetGroupOwnershipAndPermissions(const char* aAppBundle);
 bool PerformInstallationFromDMG(int argc, char** argv);
 struct UpdateServerThreadArgs {
@@ -490,7 +492,8 @@ static const NS_tchar* get_relative_path(const NS_tchar* fullpath) {
  *         Whether the path is a directory path. Defaults to false.
  * @return valid filesystem path or nullptr if the path checks fail.
  */
-static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
+static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false,
+                                bool islinktarget = false) {
   NS_tchar* path = mstrtok(kQuote, line);
   if (!path) {
     LOG(("get_valid_path: unable to determine path: " LOG_S, *line));
@@ -526,10 +529,12 @@ static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
     path[NS_tstrlen(path) - 1] = NS_T('\0');
   }
 
-  // Don't allow relative paths that resolve to a parent directory.
-  if (NS_tstrstr(path, NS_T("..")) != nullptr) {
-    LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
-    return nullptr;
+  if (!islinktarget) {
+    // Don't allow relative paths that resolve to a parent directory.
+    if (NS_tstrstr(path, NS_T("..")) != nullptr) {
+      LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
+      return nullptr;
+    }
   }
 
   return path;
@@ -569,7 +574,7 @@ static void ensure_write_permissions(const NS_tchar* path) {
   (void)_wchmod(path, _S_IREAD | _S_IWRITE);
 #else
   struct stat fs;
-  if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) {
+  if (!lstat(path, &fs) && !S_ISLNK(fs.st_mode) && !(fs.st_mode & S_IWUSR)) {
     (void)chmod(path, fs.st_mode | S_IWUSR);
   }
 #endif
@@ -756,11 +761,9 @@ static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) {
     return READ_ERROR;
   }
 
-#  ifdef XP_UNIX
   if (S_ISLNK(ss.st_mode)) {
     return ensure_copy_symlink(path, dest);
   }
-#  endif
 
   AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
   if (!infile) {
@@ -847,12 +850,19 @@ static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest,
     return READ_ERROR;
   }
 
-#ifdef XP_UNIX
+#ifndef XP_WIN
   if (S_ISLNK(sInfo.st_mode)) {
     return ensure_copy_symlink(path, dest);
   }
 #endif
 
+#ifdef XP_UNIX
+  // Ignore Unix domain sockets. See #20691.
+  if (S_ISSOCK(sInfo.st_mode)) {
+    return 0;
+  }
+#endif
+
   if (!S_ISDIR(sInfo.st_mode)) {
     return ensure_copy(path, dest);
   }
@@ -909,7 +919,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
   }
 
   struct NS_tstat_t spathInfo;
-  rv = NS_tstat(spath, &spathInfo);
+  rv = NS_tlstat(spath, &spathInfo);  // Get info about file or symlink.
   if (rv) {
     LOG(("rename_file: failed to read file status info: " LOG_S ", "
          "err: %d",
@@ -917,7 +927,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
     return READ_ERROR;
   }
 
-  if (!S_ISREG(spathInfo.st_mode)) {
+#ifdef XP_WIN
+  if (!S_ISREG(spathInfo.st_mode))
+#else
+  if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode))
+#endif
+  {
     if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
       LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
            spath, errno));
@@ -926,7 +941,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
     LOG(("rename_file: proceeding to rename the directory"));
   }
 
-  if (!NS_taccess(dpath, F_OK)) {
+#ifdef XP_WIN
+  if (!NS_taccess(dpath, F_OK))
+#else
+  if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK))
+#endif
+  {
     if (ensure_remove(dpath)) {
       LOG(
           ("rename_file: destination file exists and could not be "
@@ -946,7 +966,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
   return OK;
 }
 
-#ifdef XP_WIN
+#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE)
 // Remove the directory pointed to by path and all of its files and
 // sub-directories. If a file is in use move it to the tobedeleted directory
 // and attempt to schedule removal of the file on reboot
@@ -1045,7 +1065,19 @@ static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) {
   NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
                NS_T("%s") BACKUP_EXT, relPath);
 
-  if (NS_taccess(backup, F_OK)) {
+  bool isLink = false;
+#ifndef XP_WIN
+  struct stat linkInfo;
+  int rv = lstat(backup, &linkInfo);
+  if (rv) {
+    LOG(("backup_restore: cannot get info for backup file: " LOG_S ", err: %d",
+         relBackup, errno));
+    return OK;
+  }
+  isLink = S_ISLNK(linkInfo.st_mode);
+#endif
+
+  if (!isLink && NS_taccess(backup, F_OK)) {
     LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
     return OK;
   }
@@ -1063,8 +1095,18 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
   NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
                NS_T("%s") BACKUP_EXT, relPath);
 
+  bool isLink = false;
+#ifndef XP_WIN
+  struct stat linkInfo;
+  int rv2 = lstat(backup, &linkInfo);
+  if (rv2) {
+    return OK;  // File does not exist; nothing to do.
+  }
+  isLink = S_ISLNK(linkInfo.st_mode);
+#endif
+
   // Nothing to discard
-  if (NS_taccess(backup, F_OK)) {
+  if (!isLink && NS_taccess(backup, F_OK)) {
     return OK;
   }
 
@@ -1079,6 +1121,8 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
            relBackup, relPath));
       return WRITE_ERROR_DELETE_BACKUP;
     }
+
+#  if !defined(TOR_BROWSER_UPDATE)
     // The MoveFileEx call to remove the file on OS reboot will fail if the
     // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
     // but this is ok since the installer / uninstaller will delete the
@@ -1095,6 +1139,7 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
            "file: " LOG_S,
            relPath));
     }
+#  endif
   }
 #else
   if (rv) {
@@ -1149,7 +1194,7 @@ class Action {
 
 class RemoveFile : public Action {
  public:
-  RemoveFile() : mSkip(0) {}
+  RemoveFile() : mSkip(0), mIsLink(0) {}
 
   int Parse(NS_tchar* line) override;
   int Prepare() override;
@@ -1160,6 +1205,7 @@ class RemoveFile : public Action {
   mozilla::UniquePtr<NS_tchar[]> mFile;
   mozilla::UniquePtr<NS_tchar[]> mRelPath;
   int mSkip;
+  int mIsLink;
 };
 
 int RemoveFile::Parse(NS_tchar* line) {
@@ -1182,28 +1228,39 @@ int RemoveFile::Parse(NS_tchar* line) {
 }
 
 int RemoveFile::Prepare() {
-  // Skip the file if it already doesn't exist.
-  int rv = NS_taccess(mFile.get(), F_OK);
-  if (rv) {
-    mSkip = 1;
-    mProgressCost = 0;
-    return OK;
+  int rv;
+#ifndef XP_WIN
+  struct stat linkInfo;
+  rv = lstat(mFile.get(), &linkInfo);
+  mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode));
+#endif
+
+  if (!mIsLink) {
+    // Skip the file if it already doesn't exist.
+    rv = NS_taccess(mFile.get(), F_OK);
+    if (rv) {
+      mSkip = 1;
+      mProgressCost = 0;
+      return OK;
+    }
   }
 
   LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
 
-  // Make sure that we're actually a file...
-  struct NS_tstat_t fileInfo;
-  rv = NS_tstat(mFile.get(), &fileInfo);
-  if (rv) {
-    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
-         errno));
-    return READ_ERROR;
-  }
+  if (!mIsLink) {
+    // Make sure that we're actually a file...
+    struct NS_tstat_t fileInfo;
+    rv = NS_tstat(mFile.get(), &fileInfo);
+    if (rv) {
+      LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
+           errno));
+      return READ_ERROR;
+    }
 
-  if (!S_ISREG(fileInfo.st_mode)) {
-    LOG(("path present, but not a file: " LOG_S, mFile.get()));
-    return DELETE_ERROR_EXPECTED_FILE;
+    if (!S_ISREG(fileInfo.st_mode)) {
+      LOG(("path present, but not a file: " LOG_S, mFile.get()));
+      return DELETE_ERROR_EXPECTED_FILE;
+    }
   }
 
   NS_tchar* slash = (NS_tchar*)NS_tstrrchr(mFile.get(), NS_T('/'));
@@ -1955,6 +2012,92 @@ void PatchIfFile::Finish(int status) {
   PatchFile::Finish(status);
 }
 
+#ifndef XP_WIN
+class AddSymlink : public Action {
+ public:
+  AddSymlink() : mAdded(false) {}
+
+  virtual int Parse(NS_tchar* line);
+  virtual int Prepare();
+  virtual int Execute();
+  virtual void Finish(int status);
+
+ private:
+  mozilla::UniquePtr<NS_tchar[]> mLinkPath;
+  mozilla::UniquePtr<NS_tchar[]> mRelPath;
+  mozilla::UniquePtr<NS_tchar[]> mTarget;
+  bool mAdded;
+};
+
+int AddSymlink::Parse(NS_tchar* line) {
+  // format "<linkname>" "target"
+
+  NS_tchar* validPath = get_valid_path(&line);
+  if (!validPath) return PARSE_ERROR;
+
+  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+  NS_tstrcpy(mRelPath.get(), validPath);
+  mLinkPath.reset(get_full_path(validPath));
+  if (!mLinkPath) {
+    return PARSE_ERROR;
+  }
+
+  // consume whitespace between args
+  NS_tchar* q = mstrtok(kQuote, &line);
+  if (!q) return PARSE_ERROR;
+
+  validPath = get_valid_path(&line, false, true);
+  if (!validPath) return PARSE_ERROR;
+
+  mTarget = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+  NS_tstrcpy(mTarget.get(), validPath);
+
+  return OK;
+}
+
+int AddSymlink::Prepare() {
+  LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(),
+       mTarget.get()));
+
+  return OK;
+}
+
+int AddSymlink::Execute() {
+  LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(),
+       mTarget.get()));
+
+  // First make sure that we can actually get rid of any existing file or link.
+  struct stat linkInfo;
+  int rv = lstat(mLinkPath.get(), &linkInfo);
+  if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) {
+    rv = NS_taccess(mLinkPath.get(), F_OK);
+  }
+  if (rv == 0) {
+    rv = backup_create(mLinkPath.get());
+    if (rv) return rv;
+  } else {
+    rv = ensure_parent_dir(mLinkPath.get());
+    if (rv) return rv;
+  }
+
+  // Create the link.
+  rv = symlink(mTarget.get(), mLinkPath.get());
+  if (!rv) {
+    mAdded = true;
+  }
+
+  return rv;
+}
+
+void AddSymlink::Finish(int status) {
+  LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), mTarget.get()));
+  // When there is an update failure and a link has been added it is removed
+  // here since there might not be a backup to replace it.
+  if (status && mAdded) NS_tremove(mLinkPath.get());
+  backup_finish(mLinkPath.get(), mRelPath.get(), status);
+}
+#endif
+
 //-----------------------------------------------------------------------------
 
 #ifdef XP_WIN
@@ -2410,14 +2553,29 @@ static bool IsServiceSpecificErrorCode(int errorCode) {
  */
 static int CopyInstallDirToDestDir() {
   // These files should not be copied over to the updated app
-#ifdef XP_WIN
-#  define SKIPLIST_COUNT 3
-#elif XP_MACOSX
-#  define SKIPLIST_COUNT 0
+#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
+#  ifdef XP_WIN
+#    define SKIPLIST_COUNT 6
+#  else
+#    define SKIPLIST_COUNT 5
+#  endif
 #else
-#  define SKIPLIST_COUNT 2
+#  ifdef XP_WIN
+#    define SKIPLIST_COUNT 3
+#  elif XP_MACOSX
+#    define SKIPLIST_COUNT 0
+#  else
+#    define SKIPLIST_COUNT 2
+#  endif
 #endif
   copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
+#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
+#  ifdef XP_MACOSX
+  skiplist.append(0, gInstallDirPath, NS_T("Updated.app"));
+  skiplist.append(1, gInstallDirPath, NS_T("TorBrowser/UpdateInfo/updates/0"));
+#  endif
+#endif
+
 #ifndef XP_MACOSX
   skiplist.append(0, gInstallDirPath, NS_T("updated"));
   skiplist.append(1, gInstallDirPath, NS_T("updates/0"));
@@ -2426,6 +2584,19 @@ static int CopyInstallDirToDestDir() {
 #  endif
 #endif
 
+#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
+#  ifdef XP_WIN
+  skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath,
+                  NS_T("TorBrowser/Data/Browser/profile.default/parent.lock"));
+#  else
+  skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath,
+                  NS_T("TorBrowser/Data/Browser/profile.default/.parentlock"));
+#  endif
+
+  skiplist.append(SKIPLIST_COUNT - 1, gInstallDirPath,
+                  NS_T("TorBrowser/Data/Tor/lock"));
+#endif
+
   return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
 }
 
@@ -2563,7 +2734,9 @@ static int ProcessReplaceRequest() {
     if (NS_taccess(deleteDir, F_OK)) {
       NS_tmkdir(deleteDir, 0755);
     }
+#  if !defined(TOR_BROWSER_UPDATE)
     remove_recursive_on_reboot(tmpDir, deleteDir);
+#  endif
 #endif
   }
 
@@ -2571,8 +2744,45 @@ static int ProcessReplaceRequest() {
   // On OS X, we we need to remove the staging directory after its Contents
   // directory has been moved.
   NS_tchar updatedAppDir[MAXPATHLEN];
+#  if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
+  NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]),
+               NS_T("%s/Updated.app"), gInstallDirPath);
+  // For Tor Browser on OS X, we also need to copy everything else that is
+  // inside Updated.app.
+  NS_tDIR* dir = NS_topendir(updatedAppDir);
+  if (dir) {
+    NS_tdirent* entry;
+    while ((entry = NS_treaddir(dir)) != 0) {
+      if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+          NS_tstrcmp(entry->d_name, NS_T(".."))) {
+        NS_tchar childSrcPath[MAXPATHLEN];
+        NS_tsnprintf(childSrcPath,
+                     sizeof(childSrcPath) / sizeof(childSrcPath[0]),
+                     NS_T("%s/%s"), updatedAppDir, entry->d_name);
+        NS_tchar childDstPath[MAXPATHLEN];
+        NS_tsnprintf(childDstPath,
+                     sizeof(childDstPath) / sizeof(childDstPath[0]),
+                     NS_T("%s/%s"), gInstallDirPath, entry->d_name);
+        ensure_remove_recursive(childDstPath);
+        rv = rename_file(childSrcPath, childDstPath, true);
+        if (rv) {
+          LOG(("Moving " LOG_S " to " LOG_S " failed, err: %d", childSrcPath,
+               childDstPath, errno));
+        }
+      }
+    }
+
+    NS_tclosedir(dir);
+  } else {
+    LOG(("Updated.app dir can't be found: " LOG_S ", err: %d", updatedAppDir,
+         errno));
+  }
+#  else
   NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]),
                NS_T("%s/Updated.app"), gPatchDirPath);
+#  endif
+
+  // Remove the Updated.app directory.
   ensure_remove_recursive(updatedAppDir);
 #endif
 
@@ -2746,11 +2956,15 @@ static void UpdateThreadFunc(void* param) {
 
 #ifdef XP_MACOSX
 static void ServeElevatedUpdateThreadFunc(void* param) {
+#  ifdef TOR_BROWSER_UPDATE
+  WriteStatusFile(ELEVATION_CANCELED);
+#  else
   UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
   gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
   if (!gSucceeded) {
     WriteStatusFile(ELEVATION_CANCELED);
   }
+#  endif
   QuitProgressUI();
 }
 
@@ -2780,7 +2994,7 @@ int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
 #endif
 
   if (argc > callbackIndex) {
-#if defined(XP_WIN)
+#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE)
     if (gSucceeded) {
       if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
         fprintf(stderr, "The post update process was not launched");
@@ -2875,8 +3089,12 @@ int NS_main(int argc, NS_tchar** argv) {
   mozilla::UniquePtr<UmaskContext> umaskContext(new UmaskContext(0));
 
   bool isElevated =
+#  ifdef TOR_BROWSER_UPDATE
+      false;
+#  else
       strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") !=
       0;
+#  endif
   if (isElevated) {
     if (!ObtainUpdaterArguments(&argc, &argv)) {
       // Won't actually get here because ObtainUpdaterArguments will terminate
@@ -3616,6 +3834,26 @@ int NS_main(int argc, NS_tchar** argv) {
         if (!useService && !noServiceFallback &&
             (updateLockFileHandle == INVALID_HANDLE_VALUE ||
              forceServiceFallback)) {
+#  ifdef TOR_BROWSER_UPDATE
+#    ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR
+        // Because the TorBrowser-Data directory that contains the user's
+        // profile is a sibling of the Tor Browser installation directory,
+        // the user probably has permission to apply updates. Therefore, to
+        // avoid potential security issues such as CVE-2015-0833, do not
+        // attempt to elevate privileges. Instead, write a "failed" message
+        // to the update status file (this function will return immediately
+        // after the CloseHandle(elevatedFileHandle) call below).
+#    else
+        // Because the user profile is contained within the Tor Browser
+        // installation directory, the user almost certainly has permission to
+        // apply updates. Therefore, to avoid potential security issues such
+        // as CVE-2015-0833, do not attempt to elevate privileges. Instead,
+        // write a "failed" message to the update status file (this function
+        // will return immediately after the CloseHandle(elevatedFileHandle)
+        // call below).
+#    endif
+        WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
+#  else
           // Get the secure ID before trying to update so it is possible to
           // determine if the updater has created a new one.
           char uuidStringBefore[UUID_LEN] = {'\0'};
@@ -3685,6 +3923,7 @@ int NS_main(int argc, NS_tchar** argv) {
             gCopyOutputFiles = false;
             WriteStatusFile(ELEVATION_CANCELED);
           }
+#  endif /* TOR_BROWSER_UPDATE */
         }
 
         // If we started the elevated updater, and it finished, check the secure
@@ -4055,6 +4294,7 @@ int NS_main(int argc, NS_tchar** argv) {
     if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) {
       LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
            DELETE_DIR, errno));
+#  if !defined(TOR_BROWSER_UPDATE)
       // The directory probably couldn't be removed due to it containing files
       // that are in use and will be removed on OS reboot. The call to remove
       // the directory on OS reboot is done after the calls to remove the files
@@ -4074,6 +4314,7 @@ int NS_main(int argc, NS_tchar** argv) {
              "directory: " LOG_S,
              DELETE_DIR));
       }
+#  endif /* TOR_BROWSER_UPDATE */
     }
 #endif /* XP_WIN */
 
@@ -4718,6 +4959,10 @@ int DoUpdate() {
       action = new AddIfNotFile();
     } else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) {  // Patch if exists
       action = new PatchIfFile();
+#ifndef XP_WIN
+    } else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) {
+      action = new AddSymlink();
+#endif
     } else {
       LOG(("DoUpdate: unknown token: " LOG_S, token));
       free(buf);
diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h
index f8dc75ee4d08..ce816acd83e2 100644
--- a/toolkit/xre/MacLaunchHelper.h
+++ b/toolkit/xre/MacLaunchHelper.h
@@ -17,7 +17,9 @@ extern "C" {
  * pid of the terminated process to confirm that it executed successfully.
  */
 void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0);
+#ifndef TOR_BROWSER_UPDATE
 bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0);
+#endif
 }
 
 #endif
diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm
index ec570ffab124..da2917c2a99e 100644
--- a/toolkit/xre/MacLaunchHelper.mm
+++ b/toolkit/xre/MacLaunchHelper.mm
@@ -40,6 +40,7 @@ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid) {
   }
 }
 
+#ifndef TOR_BROWSER_UPDATE
 BOOL InstallPrivilegedHelper() {
   AuthorizationRef authRef = NULL;
   OSStatus status = AuthorizationCreate(
@@ -116,3 +117,4 @@ bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid) {
   }
   return didSucceed;
 }
+#endif
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index 686d8dd985e6..c5e285c7e746 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -3651,6 +3651,11 @@ static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion,
   gLastAppBuildID.Assign(gAppData->buildID);
 
   nsAutoCString buf;
+
+  nsAutoCString tbVersion(TOR_BROWSER_VERSION_QUOTED);
+  rv = parser.GetString("Compatibility", "LastTorBrowserVersion", buf);
+  if (NS_FAILED(rv) || !tbVersion.Equals(buf)) return false;
+
   rv = parser.GetString("Compatibility", "LastOSABI", buf);
   if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false;
 
@@ -3736,6 +3741,12 @@ static void WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion,
   PR_Write(fd, kHeader, sizeof(kHeader) - 1);
   PR_Write(fd, aVersion.get(), aVersion.Length());
 
+  nsAutoCString tbVersion(TOR_BROWSER_VERSION_QUOTED);
+  static const char kTorBrowserVersionHeader[] =
+      NS_LINEBREAK "LastTorBrowserVersion=";
+  PR_Write(fd, kTorBrowserVersionHeader, sizeof(kTorBrowserVersionHeader) - 1);
+  PR_Write(fd, tbVersion.get(), tbVersion.Length());
+
   static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI=";
   PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1);
   PR_Write(fd, aOSABI.get(), aOSABI.Length());
@@ -5211,8 +5222,17 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
     if (CheckArg("test-process-updates")) {
       SaveToEnv("MOZ_TEST_PROCESS_UPDATES=1");
     }
+#  ifdef TOR_BROWSER_UPDATE
+    nsAutoCString compatVersion(TOR_BROWSER_VERSION_QUOTED);
+#  endif
     ProcessUpdates(mDirProvider.GetGREDir(), exeDir, updRoot, gRestartArgc,
-                   gRestartArgv, mAppData->version);
+                   gRestartArgv,
+#  ifdef TOR_BROWSER_UPDATE
+                   compatVersion.get()
+#  else
+                   mAppData->version
+#  endif
+    );
     if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) {
       SaveToEnv("MOZ_TEST_PROCESS_UPDATES=");
       *aExitFlag = true;
diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp
index 70c8fd45c061..6808b5a5e9cf 100644
--- a/toolkit/xre/nsUpdateDriver.cpp
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -48,7 +48,6 @@
 #  include "commonupdatedir.h"
 #  include "nsWindowsHelpers.h"
 #  include "pathhash.h"
-#  include "WinUtils.h"
 #  define getcwd(path, size) _getcwd(path, size)
 #  define getpid() GetCurrentProcessId()
 #elif defined(XP_UNIX)
@@ -65,6 +64,16 @@ static LazyLogModule sUpdateLog("updatedriver");
 #endif
 #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
 
+#ifdef XP_WIN
+#  define UPDATER_BIN "updater.exe"
+#  define MAINTENANCE_SVC_NAME L"MozillaMaintenance"
+#elif XP_MACOSX
+#  define UPDATER_APP "updater.app"
+#  define UPDATER_BIN "org.mozilla.updater"
+#else
+#  define UPDATER_BIN "updater"
+#endif
+
 #ifdef XP_MACOSX
 static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv,
                                             bool restart) {
@@ -78,7 +87,7 @@ static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv,
   // result from it, so we can't just dispatch and return, we have to wait
   // until the dispatched operation actually completes. So we also set up a
   // monitor to signal us when that happens, and block until then.
-  Monitor monitor MOZ_UNANNOTATED("nsUpdateDriver SetupMacCommandLine");
+  Monitor monitor("nsUpdateDriver SetupMacCommandLine");
 
   nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
       "UpdateDriverSetupMacCommandLine",
@@ -157,6 +166,13 @@ static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) {
   return NS_OK;
 }
 
+#ifdef DEBUG
+static void dump_argv(const char* aPrefix, char** argv, int argc) {
+  printf("%s - %d args\n", aPrefix, argc);
+  for (int i = 0; i < argc; ++i) printf("  %d: %s\n", i, argv[i]);
+}
+#endif
+
 static bool GetFile(nsIFile* dir, const nsACString& name,
                     nsCOMPtr<nsIFile>& result) {
   nsresult rv;
@@ -218,6 +234,34 @@ typedef enum {
   eAppliedService,
 } UpdateStatus;
 
+#ifdef DEBUG
+static const char* UpdateStatusToString(UpdateStatus aStatus) {
+  const char* rv = "unknown";
+  switch (aStatus) {
+    case eNoUpdateAction:
+      rv = "NoUpdateAction";
+      break;
+    case ePendingUpdate:
+      rv = "PendingUpdate";
+      break;
+    case ePendingService:
+      rv = "PendingService";
+      break;
+    case ePendingElevate:
+      rv = "PendingElevate";
+      break;
+    case eAppliedUpdate:
+      rv = "AppliedUpdate";
+      break;
+    case eAppliedService:
+      rv = "AppliedService";
+      break;
+  }
+
+  return rv;
+}
+#endif
+
 /**
  * Returns a value indicating what needs to be done in order to handle an
  * update.
@@ -290,9 +334,39 @@ static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) {
     return false;
   }
 
+#ifdef DEBUG
+  printf("IsOlderVersion checking appVersion %s against updateVersion %s\n",
+         appVersion, buf);
+#endif
+
   return mozilla::Version(appVersion) > buf;
 }
 
+#ifndef TOR_BROWSER_DATA_OUTSIDE_APP_DIR
+#  if defined(TOR_BROWSER_UPDATE) && defined(XP_MACOSX)
+static nsresult GetUpdateDirFromAppDir(nsIFile* aAppDir, nsIFile** aResult) {
+  // On Mac OSX, we stage the update to an Updated.app directory that is
+  // directly below the main Tor Browser.app directory (two levels up from
+  // the appDir).
+  NS_ENSURE_ARG_POINTER(aAppDir);
+  NS_ENSURE_ARG_POINTER(aResult);
+  nsCOMPtr<nsIFile> parentDir1, parentDir2;
+  nsresult rv = aAppDir->GetParent(getter_AddRefs(parentDir1));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIFile> updatedDir;
+  if (!GetFile(parentDir2, "Updated.app"_ns, updatedDir)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  updatedDir.forget(aResult);
+  return NS_OK;
+}
+#  endif
+#endif
+
 /**
  * Applies, switches, or stages an update.
  *
@@ -439,6 +513,7 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
     gfxPlatformMac::WaitForFontRegistration();
   }
 
+#  ifndef TOR_BROWSER_UPDATE
   // We need to detect whether elevation is required for this update. This can
   // occur when an admin user installs the application, but another admin
   // user attempts to update (see bug 394984).
@@ -465,6 +540,7 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
       }
     }
   }
+#  endif
 #endif
 
   nsAutoCString applyToDirPath;
@@ -475,7 +551,12 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
   } else {
     // Get the directory where the update is staged or will be staged.
 #if defined(XP_MACOSX)
+#  if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
+    rv = GetUpdateDirFromAppDir(appDir, getter_AddRefs(updatedDir));
+    if (NS_FAILED(rv)) {
+#  else
     if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) {
+#  endif
 #else
     if (!GetFile(appDir, "updated"_ns, updatedDir)) {
 #endif
@@ -570,6 +651,9 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
   }
 
   LOG(("spawning updater process [%s]\n", updaterPath.get()));
+#ifdef DEBUG
+  dump_argv("ApplyUpdate updater", argv, argc);
+#endif
 
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
   // We use execv to spawn the updater process on all UNIX systems except Mac
@@ -608,7 +692,14 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
   }
 #elif defined(XP_MACOSX)
 UpdateDriverSetupMacCommandLine(argc, argv, restart);
-if (restart && needElevation) {
+#  ifdef DEBUG
+dump_argv("ApplyUpdate after SetupMacCommandLine", argv, argc);
+#  endif
+#  ifndef TOR_BROWSER_UPDATE
+// We need to detect whether elevation is required for this update. This can
+// occur when an admin user installs the application, but another admin
+// user attempts to update (see bug 394984).
+if (restart && !IsRecursivelyWritable(installDirPath.get())) {
   bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid);
   free(argv);
   if (!hasLaunched) {
@@ -617,6 +708,7 @@ if (restart && needElevation) {
   }
   exit(0);
 }
+#  endif
 
 if (isStaged) {
   // Launch the updater to replace the installation with the staged updated.
@@ -689,14 +781,25 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
                         bool restart, ProcessType* pid) {
   nsresult rv;
 
-#ifdef XP_WIN
-  // If we're in a package, we know any updates that we find are not for us.
-  if (mozilla::widget::WinUtils::HasPackageIdentity()) {
-    return NS_OK;
+#if defined(XP_WIN) && defined(TOR_BROWSER_UPDATE)
+  // Try to remove the "tobedeleted" directory which, if present, contains
+  // files that could not be removed during a previous update (e.g., DLLs
+  // that were in use and therefore locked by Windows).
+  nsCOMPtr<nsIFile> deleteDir;
+  nsresult winrv = appDir->Clone(getter_AddRefs(deleteDir));
+  if (NS_SUCCEEDED(winrv)) {
+    winrv = deleteDir->AppendNative("tobedeleted"_ns);
+    if (NS_SUCCEEDED(winrv)) {
+      winrv = deleteDir->Remove(true);
+    }
   }
 #endif
 
   nsCOMPtr<nsIFile> updatesDir;
+#ifdef DEBUG
+  printf("ProcessUpdates updateRootDir: %s appVersion: %s\n",
+         updRootDir->HumanReadablePath().get(), appVersion);
+#endif
   rv = updRootDir->Clone(getter_AddRefs(updatesDir));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = updatesDir->AppendNative("updates"_ns);
@@ -716,6 +819,12 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
 
   nsCOMPtr<nsIFile> statusFile;
   UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
+#ifdef DEBUG
+  printf("ProcessUpdates status: %s (%d)\n", UpdateStatusToString(status),
+         status);
+  printf("ProcessUpdates updatesDir: %s\n",
+         updatesDir->HumanReadablePath().get());
+#endif
   switch (status) {
     case ePendingUpdate:
     case ePendingService: {
@@ -779,13 +888,16 @@ nsUpdateProcessor::ProcessUpdate() {
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  nsAutoCString appVersion;
+#ifdef TOR_BROWSER_UPDATE
+  appVersion = TOR_BROWSER_VERSION_QUOTED;
+#else
   nsCOMPtr<nsIXULAppInfo> appInfo =
       do_GetService("@mozilla.org/xre/app-info;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoCString appVersion;
   rv = appInfo->GetVersion(appVersion);
   NS_ENSURE_SUCCESS(rv, rv);
+#endif
 
   // Copy the parameters to the StagedUpdateInfo structure shared with the
   // watcher thread.
diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
index c39c1fabd57c..13a5878df8ed 100644
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1197,6 +1197,37 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
   }
 #endif
   nsCOMPtr<nsIFile> updRoot;
+#if defined(TOR_BROWSER_UPDATE)
+  // For Tor Browser, we store update history, etc. within the UpdateInfo
+  // directory under the user data directory.
+  nsresult rv = GetTorBrowserUserDataDir(getter_AddRefs(updRoot));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = updRoot->AppendNative("UpdateInfo"_ns);
+  NS_ENSURE_SUCCESS(rv, rv);
+#  if defined(XP_MACOSX) && defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
+  // Since the TorBrowser-Data directory may be shared among different
+  // installations of the application, embed the app path in the update dir
+  // so that the update history is partitioned. This is much less likely to
+  // be an issue on Linux or Windows because the Tor Browser packages for
+  // those platforms include a "container" folder that provides partitioning
+  // by default, and we do not support use of a shared, OS-recommended area
+  // for user data on those platforms.
+  nsAutoString appDirPath;
+  nsCOMPtr<nsIFile> appRootDir;
+  rv = GetAppRootDir(getter_AddRefs(appRootDir));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = appRootDir->GetPath(appDirPath);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  int32_t dotIndex = appDirPath.RFind(".app");
+  if (dotIndex == kNotFound) {
+    dotIndex = appDirPath.Length();
+  }
+  appDirPath = Substring(appDirPath, 1, dotIndex - 1);
+  rv = updRoot->AppendRelativePath(appDirPath);
+  NS_ENSURE_SUCCESS(rv, rv);
+#  endif
+#else  // ! TOR_BROWSER_UPDATE
   nsCOMPtr<nsIFile> appFile;
   bool per = false;
   nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile));
@@ -1204,7 +1235,7 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
   rv = appFile->GetParent(getter_AddRefs(updRoot));
   NS_ENSURE_SUCCESS(rv, rv);
 
-#ifdef XP_MACOSX
+#  ifdef XP_MACOSX
   nsCOMPtr<nsIFile> appRootDirFile;
   nsCOMPtr<nsIFile> localDir;
   nsAutoString appDirPath;
@@ -1238,7 +1269,7 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
   localDir.forget(aResult);
   return NS_OK;
 
-#elif XP_WIN
+#  elif XP_WIN
   nsAutoString installPath;
   rv = updRoot->GetPath(installPath);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1258,7 +1289,8 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
   nsAutoString updatePathStr;
   updatePathStr.Assign(updatePath.get());
   updRoot->InitWithPath(updatePathStr);
-#endif  // XP_WIN
+#  endif  // XP_WIN
+#endif    // ! TOR_BROWSER_UPDATE
   updRoot.forget(aResult);
   return NS_OK;
 }
diff --git a/tools/update-packaging/common.sh b/tools/update-packaging/common.sh
index 4b994f30169c..26eabbf31379 100755
--- a/tools/update-packaging/common.sh
+++ b/tools/update-packaging/common.sh
@@ -8,6 +8,10 @@
 # Author: Darin Fisher
 #
 
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
+# we should remove all lines in this file that contain:
+#      TorBrowser/Data
+
 # -----------------------------------------------------------------------------
 QUIET=0
 
@@ -76,17 +80,8 @@ make_add_instruction() {
     forced=
   fi
 
-  is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/')
-  if [ $is_extension = "1" ]; then
-    # Use the subdirectory of the extensions folder as the file to test
-    # before performing this add instruction.
-    testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/')
-    verbose_notice "     add-if \"$testdir\" \"$f\""
-    echo "add-if \"$testdir\" \"$f\"" >> "$filev3"
-  else
-    verbose_notice "        add \"$f\"$forced"
-    echo "add \"$f\"" >> "$filev3"
-  fi
+  verbose_notice "        add \"$f\"$forced"
+  echo "add \"$f\"" >> "$filev3"
 }
 
 check_for_add_if_not_update() {
@@ -109,21 +104,21 @@ make_add_if_not_instruction() {
   echo "add-if-not \"$f\" \"$f\"" >> "$filev3"
 }
 
+make_addsymlink_instruction() {
+  link="$1"
+  target="$2"
+  filev3="$3"
+
+  verbose_notice "        addsymlink: $link -> $target"
+  echo "addsymlink \"$link\" \"$target\"" >> "$filev3"
+}
+
 make_patch_instruction() {
   f="$1"
   filev3="$2"
 
-  is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/')
-  if [ $is_extension = "1" ]; then
-    # Use the subdirectory of the extensions folder as the file to test
-    # before performing this add instruction.
-    testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/')
-    verbose_notice "   patch-if \"$testdir\" \"$f.patch\" \"$f\""
-    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> "$filev3"
-  else
-    verbose_notice "      patch \"$f.patch\" \"$f\""
-    echo "patch \"$f.patch\" \"$f\"" >> "$filev3"
-  fi
+  verbose_notice "      patch \"$f.patch\" \"$f\""
+  echo "patch \"$f.patch\" \"$f\"" >> "$filev3"
 }
 
 append_remove_instructions() {
@@ -168,6 +163,10 @@ append_remove_instructions() {
 
 # List all files in the current directory, stripping leading "./"
 # Pass a variable name and it will be filled as an array.
+# To support Tor Browser updates, skip the following files:
+#    TorBrowser/Data/Browser/profiles.ini
+#    TorBrowser/Data/Browser/profile.default/bookmarks.html
+#    TorBrowser/Data/Tor/torrc
 list_files() {
   count=0
   temp_filelist=$(mktemp)
@@ -178,6 +177,11 @@ list_files() {
     | sed 's/\.\/\(.*\)/\1/' \
     | sort -r > "${temp_filelist}"
   while read file; do
+    if [ "$file" = "TorBrowser/Data/Browser/profiles.ini" -o                   \
+         "$file" = "TorBrowser/Data/Browser/profile.default/bookmarks.html" -o \
+         "$file" = "TorBrowser/Data/Tor/torrc" ]; then
+      continue;
+    fi
     eval "${1}[$count]=\"$file\""
     (( count++ ))
   done < "${temp_filelist}"
@@ -199,3 +203,19 @@ list_dirs() {
   done < "${temp_dirlist}"
   rm "${temp_dirlist}"
 }
+
+# List all symbolic links in the current directory, stripping leading "./"
+list_symlinks() {
+  count=0
+
+  find . -type l \
+    | sed 's/\.\/\(.*\)/\1/' \
+    | sort -r > "temp-symlinklist"
+  while read symlink; do
+    target=$(readlink "$symlink")
+    eval "${1}[$count]=\"$symlink\""
+    eval "${2}[$count]=\"$target\""
+    (( count++ ))
+  done < "temp-symlinklist"
+  rm "temp-symlinklist"
+}
diff --git a/tools/update-packaging/make_full_update.sh b/tools/update-packaging/make_full_update.sh
index db2c5898efdc..603988997405 100755
--- a/tools/update-packaging/make_full_update.sh
+++ b/tools/update-packaging/make_full_update.sh
@@ -71,6 +71,7 @@ if [ ! -f "precomplete" ]; then
 fi
 
 list_files files
+list_symlinks symlinks symlink_targets
 
 popd
 
@@ -81,6 +82,21 @@ notice "Adding type instruction to update manifests"
 notice "       type complete"
 echo "type \"complete\"" >> "$updatemanifestv3"
 
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
+# we should remove the following lines:
+# If removal of any old, existing directories is desired, emit the appropriate
+# rmrfdir commands.
+notice ""
+notice "Adding directory removal instructions to update manifests"
+for dir_to_remove in $directories_to_remove; do
+  # rmrfdir requires a trailing slash; if slash is missing, add one.
+  if ! [[ "$dir_to_remove" =~ /$ ]]; then
+   dir_to_remove="${dir_to_remove}/"
+  fi
+  echo "rmrfdir \"$dir_to_remove\"" >> "$updatemanifestv3"
+done
+# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+
 notice ""
 notice "Adding file add instructions to update manifests"
 num_files=${#files[*]}
@@ -102,6 +118,15 @@ for ((i=0; $i<$num_files; i=$i+1)); do
   targetfiles="$targetfiles \"$f\""
 done
 
+notice ""
+notice "Adding symlink add instructions to update manifests"
+num_symlinks=${#symlinks[*]}
+for ((i=0; $i<$num_symlinks; i=$i+1)); do
+  link="${symlinks[$i]}"
+  target="${symlink_targets[$i]}"
+  make_addsymlink_instruction "$link" "$target" "$updatemanifestv3"
+done
+
 # Append remove instructions for any dead files.
 notice ""
 notice "Adding file and directory remove instructions from file 'removed-files'"
diff --git a/tools/update-packaging/make_incremental_update.sh b/tools/update-packaging/make_incremental_update.sh
index 24d68616731a..1adfef8fd96e 100755
--- a/tools/update-packaging/make_incremental_update.sh
+++ b/tools/update-packaging/make_incremental_update.sh
@@ -78,7 +78,11 @@ if [ $# = 0 ]; then
   exit 1
 fi
 
-requested_forced_updates='Contents/MacOS/firefox'
+# Firefox uses requested_forced_updates='Contents/MacOS/firefox' due to
+# 770996 but in Tor Browser we do not need that fix.
+requested_forced_updates=""
+directories_to_remove=""
+extra_files_to_remove=""
 
 while getopts "hqf:" flag
 do
@@ -114,6 +118,28 @@ workdir="$(mktemp -d)"
 updatemanifestv3="$workdir/updatev3.manifest"
 archivefiles="updatev3.manifest"
 
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
+# we should remove the following lines:
+# If the NoScript extension has changed between
+# releases, add it to the "force updates" list.
+ext_path='TorBrowser/Data/Browser/profile.default/extensions'
+if [ -d "$newdir/$ext_path" ]; then
+  noscript='{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi'
+
+  # NoScript is a packed extension, so we simply compare the old and the new
+  # .xpi files.
+  noscript_path="$ext_path/$noscript"
+  diff -a "$olddir/$noscript_path" "$newdir/$noscript_path" > /dev/null
+  rc=$?
+  if [ $rc -gt 1 ]; then
+    notice "Unexpected exit $rc from $noscript_path diff command"
+    exit 2
+  elif [ $rc -eq 1 ]; then
+    requested_forced_updates="$requested_forced_updates $noscript_path"
+  fi
+fi
+# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+
 mkdir -p "$workdir"
 
 # Generate a list of all files in the target directory.
@@ -124,6 +150,7 @@ fi
 
 list_files oldfiles
 list_dirs olddirs
+list_symlinks oldsymlinks oldsymlink_targets
 
 popd
 
@@ -141,6 +168,7 @@ fi
 
 list_dirs newdirs
 list_files newfiles
+list_symlinks newsymlinks newsymlink_targets
 
 popd
 
@@ -151,6 +179,22 @@ notice "Adding type instruction to update manifests"
 notice "       type partial"
 echo "type \"partial\"" >> $updatemanifestv3
 
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
+# we should remove the following lines:
+# If removal of any old, existing directories is desired, emit the appropriate
+# rmrfdir commands.
+notice ""
+notice "Adding directory removal instructions to update manifests"
+for dir_to_remove in $directories_to_remove; do
+  # rmrfdir requires a trailing slash, so add one if missing.
+  if ! [[ "$dir_to_remove" =~ /$ ]]; then
+    dir_to_remove="${dir_to_remove}/"
+  fi
+  echo "rmrfdir \"$dir_to_remove\"" >> "$updatemanifestv3"
+done
+# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+
+
 notice ""
 notice "Adding file patch and add instructions to update manifests"
 
@@ -242,6 +286,23 @@ for ((i=0; $i<$num_oldfiles; i=$i+1)); do
   fi
 done
 
+# Remove and re-add symlinks
+notice ""
+notice "Adding symlink remove/add instructions to update manifests"
+num_oldsymlinks=${#oldsymlinks[*]}
+for ((i=0; $i<$num_oldsymlinks; i=$i+1)); do
+  link="${oldsymlinks[$i]}"
+  verbose_notice "        remove: $link"
+  echo "remove \"$link\"" >> "$updatemanifestv3"
+done
+
+num_newsymlinks=${#newsymlinks[*]}
+for ((i=0; $i<$num_newsymlinks; i=$i+1)); do
+  link="${newsymlinks[$i]}"
+  target="${newsymlink_targets[$i]}"
+  make_addsymlink_instruction "$link" "$target" "$updatemanifestv3"
+done
+
 # Newly added files
 notice ""
 notice "Adding file add instructions to update manifests"
@@ -286,6 +347,14 @@ notice ""
 notice "Adding file and directory remove instructions from file 'removed-files'"
 append_remove_instructions "$newdir" "$updatemanifestv3"
 
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
+# we should remove the following lines:
+for f in $extra_files_to_remove; do
+  notice "     remove \"$f\""
+  echo "remove \"$f\"" >> "$updatemanifestv3"
+done
+# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+
 notice ""
 notice "Adding directory remove instructions for directories that no longer exist"
 num_olddirs=${#olddirs[*]}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the tor-commits mailing list