[tor-commits] [tor-messenger-build/master] Update patch for #9173 and add patch for #4234 (Firefox update)

arlo at torproject.org arlo at torproject.org
Thu Jul 28 23:52:35 UTC 2016


commit 770a2829ba7a84bc0ed910b6ce503d152221f684
Author: Sukhbir Singh <sukhbir at torproject.org>
Date:   Mon Jul 18 12:36:28 2016 -0400

    Update patch for #9173 and add patch for #4234 (Firefox update)
---
 ...ault-Firefox-profile-director-bug-9173.mozpatch |  451 ++++
 .../Firefox-update-process-bug-4234.mozpatch       | 2156 ++++++++++++++++++++
 2 files changed, 2607 insertions(+)

diff --git a/projects/instantbird/Change-the-default-Firefox-profile-director-bug-9173.mozpatch b/projects/instantbird/Change-the-default-Firefox-profile-director-bug-9173.mozpatch
new file mode 100644
index 0000000..4447c70
--- /dev/null
+++ b/projects/instantbird/Change-the-default-Firefox-profile-director-bug-9173.mozpatch
@@ -0,0 +1,451 @@
+From 48068e88b66ba37c725850331d099f10f9d34c90 Mon Sep 17 00:00:00 2001
+From: Kathy Brade <brade at pearlcrescent.com>
+Date: Fri, 18 Oct 2013 15:20:06 -0400
+Subject: Bug #9173: Change the default Firefox profile directory to be
+ TBB-relative.
+
+This should eliminate our need to rely on a wrapper script that
+sets $HOME and launches Firefox with -profile.
+
+diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
+index a828d33..831a373 100644
+--- a/toolkit/xre/nsXREDirProvider.cpp
++++ b/toolkit/xre/nsXREDirProvider.cpp
+@@ -32,6 +32,7 @@
+ #include "nsArrayEnumerator.h"
+ #include "nsEnumeratorUtils.h"
+ #include "nsReadableUtils.h"
++#include "nsXPCOMPrivate.h"  // for XPCOM_FILE_PATH_SEPARATOR
+ #include "mozilla/Services.h"
+ #include "mozilla/Omnijar.h"
+ #include "mozilla/Preferences.h"
+@@ -200,9 +201,6 @@ nsXREDirProvider::GetUserProfilesRootDir(nsIFile** aResult,
+                                      aProfileName, aAppName, aVendorName);
+ 
+   if (NS_SUCCEEDED(rv)) {
+-#if !defined(XP_UNIX) || defined(XP_MACOSX)
+-    rv = file->AppendNative(NS_LITERAL_CSTRING("Profiles"));
+-#endif
+     // We must create the profile directory here if it does not exist.
+     nsresult tmp = EnsureDirectoryExists(file);
+     if (NS_FAILED(tmp)) {
+@@ -225,9 +223,6 @@ nsXREDirProvider::GetUserProfilesLocalDir(nsIFile** aResult,
+                                      aProfileName, aAppName, aVendorName);
+ 
+   if (NS_SUCCEEDED(rv)) {
+-#if !defined(XP_UNIX) || defined(XP_MACOSX)
+-    rv = file->AppendNative(NS_LITERAL_CSTRING("Profiles"));
+-#endif
+     // We must create the profile directory here if it does not exist.
+     nsresult tmp = EnsureDirectoryExists(file);
+     if (NS_FAILED(tmp)) {
+@@ -1245,90 +1240,45 @@ nsresult
+ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
+ {
+   // Copied from nsAppFileLocationProvider (more or less)
+-  nsresult rv;
++  NS_ENSURE_ARG_POINTER(aFile);
+   nsCOMPtr<nsIFile> localDir;
+ 
+-#if defined(XP_MACOSX)
+-  FSRef fsRef;
+-  OSType folderType;
+-  if (aLocal) {
+-    folderType = kCachedDataFolderType;
+-  } else {
+-#ifdef MOZ_THUNDERBIRD
+-    folderType = kDomainLibraryFolderType;
+-#else
+-    folderType = kApplicationSupportFolderType;
+-#endif
+-  }
+-  OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef);
+-  NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
+-
+-  rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir));
++  nsresult rv = GetAppDir()->Clone(getter_AddRefs(localDir));
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+-  nsCOMPtr<nsILocalFileMac> dirFileMac = do_QueryInterface(localDir);
+-  NS_ENSURE_TRUE(dirFileMac, NS_ERROR_UNEXPECTED);
++  int levelsToRemove = 0; // In FF21+, appDir points to browser subdirectory.
++#if defined(XP_MACOSX)
++  levelsToRemove += 1;
++#endif
++  while (localDir && (levelsToRemove > 0)) {
++    // When crawling up the hierarchy, components named "." do not count.
++    nsAutoCString removedName;
++    rv = localDir->GetNativeLeafName(removedName);
++    NS_ENSURE_SUCCESS(rv, rv);
++    bool didRemove = !removedName.Equals(".");
+ 
+-  rv = dirFileMac->InitWithFSRef(&fsRef);
+-  NS_ENSURE_SUCCESS(rv, rv);
++    // Remove a directory component.
++    nsCOMPtr<nsIFile> parentDir;
++    rv = localDir->GetParent(getter_AddRefs(parentDir));
++    NS_ENSURE_SUCCESS(rv, rv);
++    localDir = parentDir;
+ 
+-  localDir = do_QueryInterface(dirFileMac, &rv);
+-#elif defined(XP_IOS)
+-  nsAutoCString userDir;
+-  if (GetUIKitDirectory(aLocal, userDir)) {
+-    rv = NS_NewNativeLocalFile(userDir, true, getter_AddRefs(localDir));
+-  } else {
+-    rv = NS_ERROR_FAILURE;
+-  }
+-  NS_ENSURE_SUCCESS(rv, rv);
+-#elif defined(XP_WIN)
+-  nsString path;
+-  if (aLocal) {
+-    rv = GetShellFolderPath(CSIDL_LOCAL_APPDATA, path);
+-    if (NS_FAILED(rv))
+-      rv = GetRegWindowsAppDataFolder(aLocal, path);
+-  }
+-  if (!aLocal || NS_FAILED(rv)) {
+-    rv = GetShellFolderPath(CSIDL_APPDATA, path);
+-    if (NS_FAILED(rv)) {
+-      if (!aLocal)
+-        rv = GetRegWindowsAppDataFolder(aLocal, path);
+-    }
++    if (didRemove)
++      --levelsToRemove;
+   }
+-  NS_ENSURE_SUCCESS(rv, rv);
+ 
+-  rv = NS_NewLocalFile(path, true, getter_AddRefs(localDir));
+-#elif defined(MOZ_WIDGET_GONK)
+-  rv = NS_NewNativeLocalFile(NS_LITERAL_CSTRING("/data/b2g"), true,
+-                             getter_AddRefs(localDir));
+-#elif defined(XP_UNIX)
+-  const char* homeDir = getenv("HOME");
+-  if (!homeDir || !*homeDir)
++  if (!localDir)
+     return NS_ERROR_FAILURE;
+ 
+-#ifdef ANDROID /* We want (ProfD == ProfLD) on Android. */
+-  aLocal = false;
+-#endif
++  rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("TorBrowser"
++                                     XPCOM_FILE_PATH_SEPARATOR "Data"
++                                     XPCOM_FILE_PATH_SEPARATOR "Browser"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ 
+   if (aLocal) {
+-    // If $XDG_CACHE_HOME is defined use it, otherwise use $HOME/.cache.
+-    const char* cacheHome = getenv("XDG_CACHE_HOME");
+-    if (cacheHome && *cacheHome) {
+-      rv = NS_NewNativeLocalFile(nsDependentCString(cacheHome), true,
+-                                 getter_AddRefs(localDir));
+-    } else {
+-      rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+-                                 getter_AddRefs(localDir));
+-      if (NS_SUCCEEDED(rv))
+-        rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache"));
+-    }
+-  } else {
+-    rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+-                               getter_AddRefs(localDir));
++    rv = localDir->AppendNative(NS_LITERAL_CSTRING("Caches"));
++    NS_ENSURE_SUCCESS(rv, rv);
+   }
+-#else
+-#error "Don't know how to get product dir on your platform"
+-#endif
+ 
+   NS_IF_ADDREF(*aFile = localDir);
+   return rv;
+@@ -1541,48 +1491,25 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
+   }
+ 
+   nsAutoCString profile;
+-  nsAutoCString appName;
+-  nsAutoCString vendor;
+   if (aProfileName && !aProfileName->IsEmpty()) {
+     profile = *aProfileName;
+-  } else if (aAppName) {
+-    appName = *aAppName;
+-    if (aVendorName) {
+-      vendor = *aVendorName;
+-    }
+   } else if (gAppData->profile) {
+     profile = gAppData->profile;
+-  } else {
+-    appName = gAppData->name;
+-    vendor = gAppData->vendor;
+   }
+ 
+-  nsresult rv;
++  nsresult rv = NS_ERROR_FAILURE;
+ 
+ #if defined (XP_MACOSX)
+   if (!profile.IsEmpty()) {
+     rv = AppendProfileString(aFile, profile.get());
++    NS_ENSURE_SUCCESS(rv, rv);
+   }
+-  else {
+-    // Note that MacOS ignores the vendor when creating the profile hierarchy -
+-    // all application preferences directories live alongside one another in
+-    // ~/Library/Application Support/
+-    rv = aFile->AppendNative(appName);
+-  }
+-  NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #elif defined(XP_WIN)
+   if (!profile.IsEmpty()) {
+     rv = AppendProfileString(aFile, profile.get());
++    NS_ENSURE_SUCCESS(rv, rv);
+   }
+-  else {
+-    if (!vendor.IsEmpty()) {
+-      rv = aFile->AppendNative(vendor);
+-      NS_ENSURE_SUCCESS(rv, rv);
+-    }
+-    rv = aFile->AppendNative(appName);
+-  }
+-  NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #elif defined(ANDROID)
+   // The directory used for storing profiles
+@@ -1594,12 +1521,6 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
+   rv = aFile->AppendNative(nsDependentCString("mozilla"));
+   NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+-  nsAutoCString folder;
+-  // Make it hidden (by starting with "."), except when local (the
+-  // profile is already under ~/.cache or XDG_CACHE_HOME).
+-  if (!aLocal)
+-    folder.Assign('.');
+-
+   if (!profile.IsEmpty()) {
+     // Skip any leading path characters
+     const char* profileStart = profile.get();
+@@ -1608,31 +1529,17 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
+ 
+     // On the off chance that someone wanted their folder to be hidden don't
+     // let it become ".."
+-    if (*profileStart == '.' && !aLocal)
++    if (*profileStart == '.')
+       profileStart++;
+ 
++    // Make it hidden (by starting with ".").
++    nsAutoCString folder(".");
+     folder.Append(profileStart);
+     ToLowerCase(folder);
+ 
+     rv = AppendProfileString(aFile, folder.BeginReading());
++    NS_ENSURE_SUCCESS(rv, rv);
+   }
+-  else {
+-    if (!vendor.IsEmpty()) {
+-      folder.Append(vendor);
+-      ToLowerCase(folder);
+-
+-      rv = aFile->AppendNative(folder);
+-      NS_ENSURE_SUCCESS(rv, rv);
+-
+-      folder.Truncate();
+-    }
+-
+-    folder.Append(appName);
+-    ToLowerCase(folder);
+-
+-    rv = aFile->AppendNative(folder);
+-  }
+-  NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #else
+ #error "Don't know how to get profile path on your platform"
+diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h
+index eb27ed2..1985f66 100644
+--- a/toolkit/xre/nsXREDirProvider.h
++++ b/toolkit/xre/nsXREDirProvider.h
+@@ -56,16 +56,16 @@ public:
+ 
+   nsresult GetProfileDefaultsDir(nsIFile* *aResult);
+ 
+-  static nsresult GetUserAppDataDirectory(nsIFile* *aFile) {
++  nsresult GetUserAppDataDirectory(nsIFile* *aFile) {
+     return GetUserDataDirectory(aFile, false, nullptr, nullptr, nullptr);
+   }
+-  static nsresult GetUserLocalDataDirectory(nsIFile* *aFile) {
++  nsresult GetUserLocalDataDirectory(nsIFile* *aFile) {
+     return GetUserDataDirectory(aFile, true, nullptr, nullptr, nullptr);
+   }
+ 
+   // By default GetUserDataDirectory gets profile path from gAppData,
+   // but that can be overridden by using aProfileName/aAppName/aVendorName.
+-  static nsresult GetUserDataDirectory(nsIFile** aFile, bool aLocal,
++  nsresult GetUserDataDirectory(nsIFile** aFile, bool aLocal,
+                                        const nsACString* aProfileName,
+                                        const nsACString* aAppName,
+                                        const nsACString* aVendorName);
+@@ -102,8 +102,8 @@ public:
+ 
+ protected:
+   nsresult GetFilesInternal(const char* aProperty, nsISimpleEnumerator** aResult);
+-  static nsresult GetUserDataDirectoryHome(nsIFile* *aFile, bool aLocal);
+-  static nsresult GetSysUserExtensionsDirectory(nsIFile* *aFile);
++  nsresult GetUserDataDirectoryHome(nsIFile* *aFile, bool aLocal);
++  nsresult GetSysUserExtensionsDirectory(nsIFile* *aFile);
+ #if defined(XP_UNIX) || defined(XP_MACOSX)
+   static nsresult GetSystemExtensionsDirectory(nsIFile** aFile);
+ #endif
+diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build
+index 45732e4..bc62f71 100644
+--- a/xpcom/io/moz.build
++++ b/xpcom/io/moz.build
+@@ -134,4 +134,7 @@ FINAL_LIBRARY = 'xul'
+ if CONFIG['OS_ARCH'] == 'Linux' and 'lib64' in CONFIG['libdir']:
+     DEFINES['HAVE_USR_LIB64_DIR'] = True
+ 
+-LOCAL_INCLUDES += ['!..']
++LOCAL_INCLUDES += [
++    '!..',
++    '../build',
++]
+diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp
+index 1211c39..039a89e 100644
+--- a/xpcom/io/nsAppFileLocationProvider.cpp
++++ b/xpcom/io/nsAppFileLocationProvider.cpp
+@@ -15,6 +15,7 @@
+ #include "nsISimpleEnumerator.h"
+ #include "prenv.h"
+ #include "nsCRT.h"
++#include "nsXPCOMPrivate.h"  // for XPCOM_FILE_PATH_SEPARATOR
+ 
+ #if defined(MOZ_WIDGET_COCOA)
+ #include <Carbon/Carbon.h>
+@@ -281,9 +282,8 @@ nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile)
+ //----------------------------------------------------------------------------------------
+ // GetProductDirectory - Gets the directory which contains the application data folder
+ //
+-// UNIX   : ~/.mozilla/
+-// WIN    : <Application Data folder on user's machine>\Mozilla
+-// Mac    : :Documents:Mozilla:
++// UNIX and WIN   : <App Folder>/TorBrowser/Data/Browser
++// Mac            : <App Folder>/../../TorBrowser/Data/Browser
+ //----------------------------------------------------------------------------------------
+ NS_METHOD
+ nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+@@ -297,48 +297,45 @@ nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+   bool exists;
+   nsCOMPtr<nsIFile> localDir;
+ 
+-#if defined(MOZ_WIDGET_COCOA)
+-  FSRef fsRef;
+-  OSType folderType = aLocal ? (OSType)kCachedDataFolderType :
+-                               (OSType)kDomainLibraryFolderType;
+-  OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef);
+-  if (err) {
+-    return NS_ERROR_FAILURE;
++  rv = CloneMozBinDirectory(getter_AddRefs(localDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  int levelsToRemove = 0; // In FF21+, bin dir points to browser subdirectory.
++#if defined(XP_MACOSX)
++  levelsToRemove += 1;
++#endif
++  while (localDir && (levelsToRemove > 0)) {
++    // When crawling up the hierarchy, components named "." do not count.
++    nsAutoCString removedName;
++    rv = localDir->GetNativeLeafName(removedName);
++    NS_ENSURE_SUCCESS(rv, rv);
++    bool didRemove = !removedName.Equals(".");
++
++    // Remove a directory component.
++    nsCOMPtr<nsIFile> parentDir;
++    rv = localDir->GetParent(getter_AddRefs(parentDir));
++    NS_ENSURE_SUCCESS(rv, rv);
++    localDir = parentDir;
++
++    if (didRemove) {
++      --levelsToRemove;
++    }
+   }
+-  NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localDir));
++
+   if (!localDir) {
+     return NS_ERROR_FAILURE;
+   }
+-  nsCOMPtr<nsILocalFileMac> localDirMac(do_QueryInterface(localDir));
+-  rv = localDirMac->InitWithFSRef(&fsRef);
+-  if (NS_FAILED(rv)) {
+-    return rv;
+-  }
+-#elif defined(XP_WIN)
+-  nsCOMPtr<nsIProperties> directoryService =
+-    do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+-  if (NS_FAILED(rv)) {
+-    return rv;
+-  }
+-  const char* prop = aLocal ? NS_WIN_LOCAL_APPDATA_DIR : NS_WIN_APPDATA_DIR;
+-  rv = directoryService->Get(prop, NS_GET_IID(nsIFile), getter_AddRefs(localDir));
+-  if (NS_FAILED(rv)) {
+-    return rv;
+-  }
+-#elif defined(XP_UNIX)
+-  rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
+-                             getter_AddRefs(localDir));
+-  if (NS_FAILED(rv)) {
+-    return rv;
+-  }
+-#else
+-#error dont_know_how_to_get_product_dir_on_your_platform
+-#endif
+ 
+-  rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR);
+-  if (NS_FAILED(rv)) {
+-    return rv;
++  rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("TorBrowser"
++                                        XPCOM_FILE_PATH_SEPARATOR "Data"
++                                        XPCOM_FILE_PATH_SEPARATOR "Browser"));
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  if (aLocal) {
++    rv = localDir->AppendNative(NS_LITERAL_CSTRING("Caches"));
++    NS_ENSURE_SUCCESS(rv, rv);
+   }
++
+   rv = localDir->Exists(&exists);
+ 
+   if (NS_SUCCEEDED(rv) && !exists) {
+@@ -357,10 +354,6 @@ nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+ 
+ //----------------------------------------------------------------------------------------
+ // GetDefaultUserProfileRoot - Gets the directory which contains each user profile dir
+-//
+-// UNIX   : ~/.mozilla/
+-// WIN    : <Application Data folder on user's machine>\Mozilla\Profiles
+-// Mac    : :Documents:Mozilla:Profiles:
+ //----------------------------------------------------------------------------------------
+ NS_METHOD
+ nsAppFileLocationProvider::GetDefaultUserProfileRoot(nsIFile** aLocalFile,
+@@ -378,23 +371,6 @@ nsAppFileLocationProvider::GetDefaultUserProfileRoot(nsIFile** aLocalFile,
+     return rv;
+   }
+ 
+-#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN)
+-  // These 3 platforms share this part of the path - do them as one
+-  rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("Profiles"));
+-  if (NS_FAILED(rv)) {
+-    return rv;
+-  }
+-
+-  bool exists;
+-  rv = localDir->Exists(&exists);
+-  if (NS_SUCCEEDED(rv) && !exists) {
+-    rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+-  }
+-  if (NS_FAILED(rv)) {
+-    return rv;
+-  }
+-#endif
+-
+   localDir.forget(aLocalFile);
+ 
+   return rv;
+-- 
+cgit v0.10.2
+
diff --git a/projects/instantbird/Firefox-update-process-bug-4234.mozpatch b/projects/instantbird/Firefox-update-process-bug-4234.mozpatch
new file mode 100644
index 0000000..a02d80b
--- /dev/null
+++ b/projects/instantbird/Firefox-update-process-bug-4234.mozpatch
@@ -0,0 +1,2156 @@
+From 3f8110f69e1ddfc762a7c7dba4d06b537a785a9c Mon Sep 17 00:00:00 2001
+From: Kathy Brade <brade at pearlcrescent.com>
+Date: Thu, 4 Sep 2014 15:45:23 +0000
+Subject: Bug #4234: Use the Firefox Update Process for Tor Browser.
+
+New configure options:
+  --with-tor-browser-version=VERSION # Pass TB version throughout build.
+  --enable-tor-browser-update        # Enable bundle update behavior.
+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.
+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, and 16909.
+
+diff --git a/browser/components/moz.build b/browser/components/moz.build
+index 431d505..bf7a40c 100644
+--- a/browser/components/moz.build
++++ b/browser/components/moz.build
+@@ -38,11 +38,8 @@ XPIDL_MODULE = 'browsercompsbase'
+ 
+ EXTRA_PP_COMPONENTS += [
+     'BrowserComponents.manifest',
+-    'nsBrowserGlue.js',
+-]
+-
+-EXTRA_COMPONENTS += [
+     'nsBrowserContentHandler.js',
++    'nsBrowserGlue.js',
+ ]
+ 
+ EXTRA_JS_MODULES += [
+diff --git a/browser/components/nsBrowserContentHandler.js b/browser/components/nsBrowserContentHandler.js
+index e89004d..b892eaf 100644
+--- a/browser/components/nsBrowserContentHandler.js
++++ b/browser/components/nsBrowserContentHandler.js
+@@ -45,6 +45,10 @@ const NS_BINDING_ABORTED = Components.results.NS_BINDING_ABORTED;
+ const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
+ const NS_ERROR_ABORT = Components.results.NS_ERROR_ABORT;
+ 
++#ifdef TOR_BROWSER_VERSION
++const kTBSavedVersionPref = "browser.startup.homepage_override.torbrowser.version";
++#endif
++
+ function shouldLoadURI(aURI) {
+   if (aURI && !aURI.schemeIs("chrome"))
+     return true;
+@@ -96,7 +100,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.
+@@ -112,6 +117,15 @@ function needHomepageOverride(prefb) {
+ 
+   var mstone = Services.appinfo.platformVersion;
+ 
++#ifdef TOR_BROWSER_VERSION
++#expand const TOR_BROWSER_VERSION = __TOR_BROWSER_VERSION__;
++
++  var savedTBVersion = null;
++  try {
++    savedTBVersion = prefb.getCharPref(kTBSavedVersionPref);
++  } catch (e) {}
++#endif
++
+   var savedBuildID = null;
+   try {
+     savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID");
+@@ -129,9 +143,30 @@ function needHomepageOverride(prefb) {
+     
+     prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
+     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
++#ifdef TOR_BROWSER_VERSION
++    prefb.setCharPref(kTBSavedVersionPref, 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.
++    var updated = prefb.prefHasUserValue("app.update.postupdate");
++    return (savedmstone || updated) ? OVERRIDE_NEW_MSTONE
++                                    : OVERRIDE_NEW_PROFILE;
++#else
+     return (savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE);
++#endif
+   }
+ 
++#ifdef TOR_BROWSER_VERSION
++  if (TOR_BROWSER_VERSION != savedTBVersion) {
++    prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
++    prefb.setCharPref(kTBSavedVersionPref, TOR_BROWSER_VERSION);
++    return OVERRIDE_NEW_MSTONE;
++  }
++#endif
++
+   if (buildID != savedBuildID) {
+     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+     return OVERRIDE_NEW_BUILD_ID;
+@@ -515,6 +550,15 @@ nsBrowserContentHandler.prototype = {
+       try {
+         old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
+       } catch (ex) {}
++
++#ifdef TOR_BROWSER_VERSION
++      // We do the same for the Tor Browser version.
++      var old_tbversion = null;
++      try {
++        old_tbversion = prefb.getCharPref(kTBSavedVersionPref);
++      } catch (e) {}
++#endif
++
+       override = needHomepageOverride(prefb);
+       if (override != OVERRIDE_NONE) {
+         switch (override) {
+@@ -538,6 +582,10 @@ nsBrowserContentHandler.prototype = {
+               overridePage = getPostUpdateOverridePage(overridePage);
+ 
+             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
++#ifdef TOR_BROWSER_VERSION
++            overridePage = overridePage.replace("%OLD_TOR_BROWSER_VERSION%",
++                                                old_tbversion);
++#endif
+             break;
+         }
+       }
+diff --git a/browser/confvars.sh b/browser/confvars.sh
+index 6935e05..ed93deb 100755
+--- a/browser/confvars.sh
++++ b/browser/confvars.sh
+@@ -8,22 +8,6 @@ MOZ_APP_VENDOR=Mozilla
+ MOZ_UPDATER=1
+ MOZ_PHOENIX=1
+ 
+-if test "$OS_ARCH" = "WINNT"; then
+-  MOZ_MAINTENANCE_SERVICE=1
+-  if ! test "$HAVE_64BIT_BUILD"; then
+-    if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \
+-            "$MOZ_UPDATE_CHANNEL" = "aurora" -o \
+-            "$MOZ_UPDATE_CHANNEL" = "beta" -o \
+-            "$MOZ_UPDATE_CHANNEL" = "beta-dev" -o \
+-            "$MOZ_UPDATE_CHANNEL" = "release" -o \
+-            "$MOZ_UPDATE_CHANNEL" = "release-dev"; then
+-      if ! test "$MOZ_DEBUG"; then
+-        MOZ_STUB_INSTALLER=1
+-      fi
+-    fi
+-  fi
+-fi
+-
+ # Enable building ./signmar and running libmar signature tests
+ MOZ_ENABLE_SIGNMAR=1
+ 
+@@ -51,9 +35,9 @@ MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+ # This 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.
+-ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-esr
++ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-release
+ # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
+-MAR_CHANNEL_ID=firefox-mozilla-esr
++MAR_CHANNEL_ID=torbrowser-torproject-release
+ MOZ_PROFILE_MIGRATOR=1
+ MOZ_APP_STATIC_INI=1
+ MOZ_WEBAPP_RUNTIME=1
+diff --git a/browser/installer/Makefile.in b/browser/installer/Makefile.in
+index bb91b4a..ecab9ad 100644
+--- a/browser/installer/Makefile.in
++++ b/browser/installer/Makefile.in
+@@ -72,6 +72,10 @@ endif
+ endif
+ endif
+ 
++ifdef TOR_BROWSER_UPDATE
++DEFINES += -DTOR_BROWSER_UPDATE
++endif
++
+ ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
+ DEFINES += -DMOZ_SHARED_MOZGLUE=1
+ endif
+diff --git a/config/createprecomplete.py b/config/createprecomplete.py
+index 3241d52..be571be 100644
+--- a/config/createprecomplete.py
++++ b/config/createprecomplete.py
+@@ -5,13 +5,22 @@
+ # 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.
+ 
+ import sys
+ import os
+ 
++# 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()
+@@ -22,6 +31,9 @@ def get_build_entries(root_path):
+             rel_path_file = rel_path_file.replace("\\", "/")
+             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)
+ 
+@@ -30,7 +42,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/configure.in b/configure.in
+index 38df30e..e9fb038 100644
+--- a/configure.in
++++ b/configure.in
+@@ -3369,15 +3369,12 @@ AC_SUBST(GRE_MILESTONE)
+ # set RELEASE_BUILD 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_BUILD)
+ case "$GRE_MILESTONE" in
+   *a1*)
+       NIGHTLY_BUILD=1
+       AC_DEFINE(NIGHTLY_BUILD)
+       ;;
+-  *a*)
+-      ;;
+   *)
+       RELEASE_BUILD=1
+       AC_DEFINE(RELEASE_BUILD)
+@@ -6521,6 +6518,31 @@ MOZ_ARG_ENABLE_BOOL(update-packaging,
+ AC_SUBST(MOZ_UPDATE_PACKAGING)
+ 
+ dnl ========================================================
++dnl Tor Additions
++dnl ========================================================
++MOZ_ARG_WITH_STRING(eor-browser-version,
++[  --with-tor-browser-version=VERSION
++                          Set Tor Browser version, e.g., 4.0b1],
++    TOR_BROWSER_VERSION="$withval")
++
++MOZ_ARG_ENABLE_BOOL(tor-browser-update,
++[  --enable-tor-browser-update
++                          Enable Tor Browser update],
++    TOR_BROWSER_UPDATE=1,
++    TOR_BROWSER_UPDATE= )
++
++if test -n "$TOR_BROWSER_UPDATE"; then
++    if test -z "$TOR_BROWSER_VERSION"; then
++        AC_MSG_ERROR([--enable-tor-browser-update requires --with-tor-browser-version.])
++    fi
++    AC_DEFINE(TOR_BROWSER_UPDATE)
++fi
++
++AC_DEFINE_UNQUOTED(TOR_BROWSER_VERSION,"$TOR_BROWSER_VERSION")
++AC_SUBST(TOR_BROWSER_VERSION)
++AC_SUBST(TOR_BROWSER_UPDATE)
++
++dnl ========================================================
+ dnl build the tests by default
+ dnl ========================================================
+ MOZ_ARG_DISABLE_BOOL(tests,
+@@ -9068,7 +9090,7 @@ if test "$MOZ_DEBUG"; then
+     A11Y_LOG=1
+ fi
+ case "$MOZ_UPDATE_CHANNEL" in
+-aurora|beta|release|esr)
++aurora|alpha|beta|hardened|release|esr)
+     ;;
+ *)
+     A11Y_LOG=1
+diff --git a/js/src/configure.in b/js/src/configure.in
+index 25b4ae8..7fae8a4 100644
+--- a/js/src/configure.in
++++ b/js/src/configure.in
+@@ -2691,15 +2691,12 @@ AC_SUBST(GRE_MILESTONE)
+ dnl set RELEASE_BUILD and NIGHTLY_BUILD variables depending on the cycle we're in
+ dnl The logic works like this:
+ dnl - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
+-dnl - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
+ dnl - otherwise, we're building Release/Beta (define RELEASE_BUILD)
+ case "$GRE_MILESTONE" in
+   *a1*)
+       NIGHTLY_BUILD=1
+       AC_DEFINE(NIGHTLY_BUILD)
+       ;;
+-  *a*)
+-      ;;
+   *)
+       RELEASE_BUILD=1
+       AC_DEFINE(RELEASE_BUILD)
+diff --git a/toolkit/modules/UpdateUtils.jsm b/toolkit/modules/UpdateUtils.jsm
+index b943467..4638c8e 100644
+--- a/toolkit/modules/UpdateUtils.jsm
++++ b/toolkit/modules/UpdateUtils.jsm
+@@ -20,6 +20,9 @@ const PREF_APP_B2G_VERSION                = "b2g.version";
+ const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";
+ const PREF_APP_UPDATE_IMEI_HASH           = "app.update.imei_hash";
+ 
++#ifdef TOR_BROWSER_VERSION
++#expand const TOR_BROWSER_VERSION = __TOR_BROWSER_VERSION__;
++#endif
+ 
+ this.UpdateUtils = {
+   /**
+@@ -70,7 +73,11 @@ this.UpdateUtils = {
+    */
+   formatUpdateURL(url) {
+     url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
++#ifdef TOR_BROWSER_UPDATE
++    url = url.replace(/%VERSION%/g, TOR_BROWSER_VERSION);
++#else
+     url = url.replace(/%VERSION%/g, Services.appinfo.version);
++#endif
+     url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
+     url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + this.ABI);
+     url = url.replace(/%OS_VERSION%/g, this.OSVersion);
+diff --git a/toolkit/modules/debug.js b/toolkit/modules/debug.js
+index ba59c65..486105e 100644
+--- a/toolkit/modules/debug.js
++++ b/toolkit/modules/debug.js
+@@ -41,6 +41,8 @@ this.NS_ASSERT = function NS_ASSERT(condition, message) {
+     switch (defB.getCharPref("app.update.channel")) {
+       case "nightly":
+       case "aurora":
++      case "alpha":
++      case "hardened":
+       case "beta":
+       case "default":
+         releaseBuild = false;
+diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
+index 0600b82..81125d1 100644
+--- a/toolkit/modules/moz.build
++++ b/toolkit/modules/moz.build
+@@ -72,7 +72,6 @@ EXTRA_JS_MODULES += [
+     'Task.jsm',
+     'Timer.jsm',
+     'Troubleshoot.jsm',
+-    'UpdateUtils.jsm',
+     'WebChannel.jsm',
+     'WindowDraggingUtils.jsm',
+     'ZipUtils.jsm',
+@@ -87,6 +86,7 @@ if not CONFIG['TOR_BROWSER_VERSION']:
+ EXTRA_PP_JS_MODULES += [
+     'AppConstants.jsm',
+     'SessionRecorder.jsm',
++    'UpdateUtils.jsm',
+ ]
+ 
+ if 'Android' != CONFIG['OS_TARGET']:
+diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm
+index ab63c3d..a453062 100644
+--- a/toolkit/mozapps/extensions/AddonManager.jsm
++++ b/toolkit/mozapps/extensions/AddonManager.jsm
+@@ -23,7 +23,7 @@ if ("@mozilla.org/xre/app-info;1" in Cc) {
+ 
+ Cu.import("resource://gre/modules/AppConstants.jsm");
+ 
+-const MOZ_COMPATIBILITY_NIGHTLY = !['aurora', 'beta', 'release', 'esr'].includes(AppConstants.MOZ_UPDATE_CHANNEL);
++const MOZ_COMPATIBILITY_NIGHTLY = !['aurora', 'alpha', 'beta', 'hardened', 'release', 'esr'].includes(AppConstants.MOZ_UPDATE_CHANNEL);
+ 
+ const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
+ const PREF_DEFAULT_PROVIDERS_ENABLED  = "extensions.defaultProviders.enabled";
+diff --git a/toolkit/mozapps/update/content/updates.js b/toolkit/mozapps/update/content/updates.js
+index 48f8bad..40e76c8 100644
+--- a/toolkit/mozapps/update/content/updates.js
++++ b/toolkit/mozapps/update/content/updates.js
+@@ -3,6 +3,8 @@
+  * 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/. */
+ 
++#filter substitution
++
+ 'use strict';
+ 
+ // Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
+@@ -51,6 +53,11 @@ const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
+ const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
+ const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
+ 
++#ifdef TOR_BROWSER_VERSION
++# Add double-quotes back on (stripped by JarMaker.py).
++#expand const TOR_BROWSER_VERSION = "__TOR_BROWSER_VERSION__";
++#endif
++
+ var gLogEnabled = false;
+ var gUpdatesFoundPageId;
+ 
+@@ -510,8 +517,13 @@ var gUpdates = {
+       return;
+     }
+ 
++#ifdef TOR_BROWSER_UPDATE
++    var appVersion = TOR_BROWSER_VERSION;
++#else
++    var appVersion = Services.appinfo.version;
++#endif
+     if (!this.update.appVersion ||
+-        Services.vc.compare(this.update.appVersion, Services.appinfo.version) == 0) {
++        Services.vc.compare(this.update.appVersion, appVersion) == 0) {
+       aCallback(false);
+       return;
+     }
+@@ -523,6 +535,11 @@ var gUpdates = {
+ 
+     var self = this;
+     AddonManager.getAllAddons(function(addons) {
++#ifdef TOR_BROWSER_UPDATE
++      let compatVersion = self.update.platformVersion;
++#else
++      let compatVersion = self.update.appVersion;
++#endif
+       self.addons = [];
+       addons.forEach(function(addon) {
+         // Protect against code that overrides the add-ons manager and doesn't
+@@ -551,7 +568,7 @@ var gUpdates = {
+               !addon.appDisabled && !addon.userDisabled &&
+               addon.scope != AddonManager.SCOPE_APPLICATION &&
+               addon.isCompatible &&
+-              !addon.isCompatibleWith(self.update.appVersion,
++              !addon.isCompatibleWith(compatVersion,
+                                       self.update.platformVersion))
+             self.addons.push(addon);
+         }
+@@ -821,9 +838,14 @@ var gIncompatibleCheckPage = {
+     this._totalCount = gUpdates.addons.length;
+ 
+     this._pBar.mode = "normal";
++#ifdef TOR_BROWSER_UPDATE
++    let compatVersion = gUpdates.update.platformVersion;
++#else
++    let compatVersion = gUpdates.update.appVersion;
++#endif
+     gUpdates.addons.forEach(function(addon) {
+       addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
+-                        gUpdates.update.appVersion,
++                        compatVersion,
+                         gUpdates.update.platformVersion);
+     }, this);
+   },
+@@ -848,8 +870,13 @@ var gIncompatibleCheckPage = {
+     // the add-on will become incompatible.
+     let bs = CoC["@mozilla.org/extensions/blocklist;1"].
+              getService(CoI.nsIBlocklistService);
++#ifdef TOR_BROWSER_UPDATE
++    let compatVersion = gUpdates.update.platformVersion;
++#else
++    let compatVersion = gUpdates.update.appVersion;
++#endif
+     if (bs.isAddonBlocklisted(addon,
+-                              gUpdates.update.appVersion,
++                              compatVersion,
+                               gUpdates.update.platformVersion))
+       return;
+ 
+diff --git a/toolkit/mozapps/update/jar.mn b/toolkit/mozapps/update/jar.mn
+index 9bb5d2d..5347123 100644
+--- a/toolkit/mozapps/update/jar.mn
++++ b/toolkit/mozapps/update/jar.mn
+@@ -7,6 +7,6 @@ toolkit.jar:
+   content/mozapps/update/history.xul                            (content/history.xul)
+   content/mozapps/update/history.js                             (content/history.js)
+   content/mozapps/update/updates.css                            (content/updates.css)
+-  content/mozapps/update/updates.js                             (content/updates.js)
++* content/mozapps/update/updates.js                             (content/updates.js)
+   content/mozapps/update/updates.xml                            (content/updates.xml)
+   content/mozapps/update/updates.xul                            (content/updates.xul)
+diff --git a/toolkit/mozapps/update/moz.build b/toolkit/mozapps/update/moz.build
+index 3824b88..c93100a 100644
+--- a/toolkit/mozapps/update/moz.build
++++ b/toolkit/mozapps/update/moz.build
+@@ -18,11 +18,14 @@ XPIDL_SOURCES += [
+ TEST_DIRS += ['tests']
+ 
+ EXTRA_COMPONENTS += [
+-    'nsUpdateService.js',
+     'nsUpdateService.manifest',
+     'nsUpdateServiceStub.js',
+ ]
+ 
++EXTRA_PP_COMPONENTS += [
++    'nsUpdateService.js',
++]
++
+ EXTRA_JS_MODULES += [
+     'UpdateTelemetry.jsm',
+ ]
+diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js
+index 489d085..e12636d 100644
+--- a/toolkit/mozapps/update/nsUpdateService.js
++++ b/toolkit/mozapps/update/nsUpdateService.js
+@@ -12,7 +12,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+ Cu.import("resource://gre/modules/FileUtils.jsm", this);
+ Cu.import("resource://gre/modules/AddonManager.jsm", this);
+ Cu.import("resource://gre/modules/Services.jsm", this);
++#ifdef XP_WIN
+ Cu.import("resource://gre/modules/ctypes.jsm", this);
++#endif
+ Cu.import("resource://gre/modules/UpdateTelemetry.jsm", this);
+ Cu.import("resource://gre/modules/AppConstants.jsm", this);
+ 
+@@ -66,6 +68,10 @@ const KEY_EXECUTABLE      = "XREExeF";
+ // Gonk only
+ const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD";
+ 
++#ifdef TOR_BROWSER_VERSION
++#expand const TOR_BROWSER_VERSION = __TOR_BROWSER_VERSION__;
++#endif
++
+ const DIR_UPDATED         = "updated";
+ const DIR_UPDATED_APP     = "Updated.app";
+ const DIR_UPDATES         = "updates";
+@@ -701,8 +707,13 @@ function getUpdatesDirInApplyToDir() {
+   if (AppConstants.platform == "macosx") {
+     dir = dir.parent.parent; // the bundle directory
+     dir.append(DIR_UPDATED_APP);
++#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR)
++    dir.append("TorBrowser");
++    dir.append("UpdateInfo");
++#else
+     dir.append("Contents");
+     dir.append("MacOS");
++#endif
+   } else {
+     dir.append(DIR_UPDATED);
+   }
+@@ -1521,7 +1532,17 @@ function Update(update) {
+     this._patches.push(patch);
+   }
+ 
+-  if (this._patches.length == 0 && !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._patches.length == 0 && !this.unsupported) {
+     throw Cr.NS_ERROR_ILLEGAL_VALUE;
+   }
+ 
+@@ -1574,15 +1595,13 @@ function Update(update) {
+       if(!isNaN(attr.value)) {
+         this.promptWaitTime = parseInt(attr.value);
+       }
+-    } else if (attr.name == "unsupported") {
+-      this.unsupported = attr.value == "true";
+     } else if (attr.name == "version") {
+       // Prevent version from replacing displayVersion if displayVersion is
+       // present in the update xml.
+       if (!this.displayVersion) {
+         this.displayVersion = attr.value;
+       }
+-    } else {
++    } else if (attr.name != "unsupported") {
+       this[attr.name] = attr.value;
+ 
+       switch (attr.name) {
+@@ -2408,9 +2427,14 @@ UpdateService.prototype = {
+     updates.forEach(function(aUpdate) {
+       // Ignore updates for older versions of the application and updates for
+       // the same version of the application with the same build ID.
+-      if (vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 ||
+-          vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 &&
+-          aUpdate.buildID == Services.appinfo.appBuildID) {
++#ifdef TOR_BROWSER_UPDATE
++      var compatVersion = TOR_BROWSER_VERSION;
++#else
++      var compatVersion = Services.appinfo.version;
++#endif
++      var rc = vc.compare(aUpdate.appVersion, compatVersion);
++      if (rc < 0 || ((rc == 0) &&
++                     (aUpdate.buildID == Services.appinfo.appBuildID))) {
+         LOG("UpdateService:selectUpdate - skipping update because the " +
+             "update's application version is less than the current " +
+             "application version");
+@@ -2574,8 +2598,13 @@ UpdateService.prototype = {
+     }
+ 
+     // Only check add-on compatibility when the version changes.
++#ifdef TOR_BROWSER_UPDATE
++    var compatVersion = TOR_BROWSER_VERSION;
++#else
++    var compatVersion = Services.appinfo.version;
++#endif
+     if (update.appVersion &&
+-        Services.vc.compare(update.appVersion, Services.appinfo.version) != 0) {
++        Services.vc.compare(update.appVersion, compatVersion) != 0) {
+       this._update = update;
+       this._checkAddonCompatibility();
+     }
+@@ -2606,6 +2635,11 @@ UpdateService.prototype = {
+     // Get all the installed add-ons
+     var self = this;
+     AddonManager.getAllAddons(function(addons) {
++#ifdef TOR_BROWSER_UPDATE
++      let compatVersion = self._update.platformVersion;
++#else
++      let compatVersion = self._update.appVersion;
++#endif
+       self._incompatibleAddons = [];
+       addons.forEach(function(addon) {
+         // Protect against code that overrides the add-ons manager and doesn't
+@@ -2635,7 +2669,7 @@ UpdateService.prototype = {
+               !addon.appDisabled && !addon.userDisabled &&
+               addon.scope != AddonManager.SCOPE_APPLICATION &&
+               addon.isCompatible &&
+-              !addon.isCompatibleWith(self._update.appVersion,
++              !addon.isCompatibleWith(compatVersion,
+                                       self._update.platformVersion)) {
+             self._incompatibleAddons.push(addon);
+           }
+@@ -2671,7 +2705,7 @@ UpdateService.prototype = {
+ 
+         self._incompatibleAddons.forEach(function(addon) {
+           addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
+-                            this._update.appVersion, this._update.platformVersion);
++                            compatVersion, this._update.platformVersion);
+         }, self);
+       }
+       else {
+@@ -2707,7 +2741,12 @@ UpdateService.prototype = {
+     // If the new version of this add-on is blocklisted for the new application
+     // then it isn't a valid update and the user should still be warned that
+     // the add-on will become incompatible.
+-    if (Services.blocklist.isAddonBlocklisted(addon, this._update.appVersion,
++#ifdef TOR_BROWSER_UPDATE
++    let compatVersion = this._update.platformVersion;
++#else
++    let compatVersion = this._update.appVersion;
++#endif
++    if (Services.blocklist.isAddonBlocklisted(addon, compatVersion,
+                                               this._update.platformVersion)) {
+       return;
+     }
+@@ -2819,14 +2858,24 @@ UpdateService.prototype = {
+     // current application's version or the update's version is the same as the
+     // application's version and the build ID is the same as the application's
+     // build ID.
++#ifdef TOR_BROWSER_UPDATE
++    var compatVersion = TOR_BROWSER_VERSION;
++#else
++    var compatVersion = Services.appinfo.version;
++#endif
+     if (update.appVersion &&
+-        (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
++        (Services.vc.compare(update.appVersion, compatVersion) < 0 ||
+          update.buildID && update.buildID == Services.appinfo.appBuildID &&
+-         update.appVersion == Services.appinfo.version)) {
++         update.appVersion == compatVersion)) {
+       LOG("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 + "\n" +
++#ifdef TOR_BROWSER_UPDATE
++          "current Tor Browser version: " + compatVersion + "\n" +
++          "update Tor Browser version : " + update.appVersion + "\n" +
++#else
++          "current application version: " + compatVersion + "\n" +
+           "update application version : " + update.appVersion + "\n" +
++#endif
+           "current build ID: " + Services.appinfo.appBuildID + "\n" +
+           "update build ID : " + update.buildID);
+       cleanupActiveUpdate();
+@@ -2859,7 +2908,7 @@ UpdateService.prototype = {
+       }
+     }
+     // Set the previous application version prior to downloading the update.
+-    update.previousAppVersion = Services.appinfo.version;
++    update.previousAppVersion = compatVersion;
+     this._downloader = new Downloader(background, this);
+     return this._downloader.downloadUpdate(update);
+   },
+diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
+index 6ef82a1..f362292 100644
+--- a/toolkit/mozapps/update/updater/updater.cpp
++++ b/toolkit/mozapps/update/updater/updater.cpp
+@@ -25,7 +25,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.
+  *
+@@ -403,10 +403,12 @@ get_full_path(const NS_tchar *relpath)
+  *         The line from the manifest that contains the path.
+  * @param  isdir
+  *         Whether the path is a directory path. Defaults to false.
++ * @param  islinktarget
++ *         Whether the path is a symbolic link target. 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)
++get_valid_path(NS_tchar **line, bool isdir = false, bool islinktarget = false)
+ {
+   NS_tchar *path = mstrtok(kQuote, line);
+   if (!path) {
+@@ -441,10 +443,12 @@ 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;
+@@ -479,7 +483,8 @@ 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
+@@ -680,11 +685,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
+ 
+ #if MAYBE_USE_HARD_LINKS
+   if (sUseHardLinks) {
+@@ -780,7 +783,7 @@ 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);
+   }
+@@ -839,14 +842,19 @@ static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
+     return rv;
+ 
+   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", spath, errno));
+     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));
+@@ -856,7 +864,12 @@ static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
+     }
+   }
+ 
+-  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 " \
+            "removed: " LOG_S, dpath));
+@@ -873,7 +886,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
+@@ -960,7 +973,18 @@ static int backup_restore(const NS_tchar *path)
+   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+                NS_T("%s") BACKUP_EXT, path);
+ 
+-  if (NS_taccess(backup, F_OK)) {
++  bool isLink = false;
++#ifndef XP_WIN
++  struct stat linkInfo;
++  int rv = lstat(path, &linkInfo);
++  if (!rv) {
++    LOG(("backup_restore: cannot get info for backup file: " LOG_S, backup));
++    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, backup));
+     return OK;
+   }
+@@ -975,8 +999,18 @@ static int backup_discard(const NS_tchar *path)
+   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+                NS_T("%s") BACKUP_EXT, path);
+ 
++  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;
+   }
+ 
+@@ -991,6 +1025,8 @@ static int backup_discard(const NS_tchar *path)
+            backup, path));
+       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
+@@ -1003,6 +1039,7 @@ static int backup_discard(const NS_tchar *path)
+       LOG(("backup_discard: failed to schedule OS reboot removal of " \
+            "file: " LOG_S, path));
+     }
++#endif
+   }
+ #else
+   if (rv)
+@@ -1056,7 +1093,7 @@ private:
+ class RemoveFile : public Action
+ {
+ public:
+-  RemoveFile() : mFile(nullptr), mSkip(0) { }
++  RemoveFile() : mFile(nullptr), mSkip(0), mIsLink(0) { }
+ 
+   int Parse(NS_tchar *line);
+   int Prepare();
+@@ -1066,6 +1103,7 @@ public:
+ private:
+   const NS_tchar *mFile;
+   int mSkip;
++  int mIsLink;
+ };
+ 
+ int
+@@ -1083,28 +1121,39 @@ RemoveFile::Parse(NS_tchar *line)
+ int
+ RemoveFile::Prepare()
+ {
+-  // Skip the file if it already doesn't exist.
+-  int rv = NS_taccess(mFile, F_OK);
+-  if (rv) {
+-    mSkip = 1;
+-    mProgressCost = 0;
+-    return OK;
++  int rv;
++#ifndef XP_WIN
++  struct stat linkInfo;
++  rv = lstat(mFile, &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, F_OK);
++    if (rv) {
++      mSkip = 1;
++      mProgressCost = 0;
++      return OK;
++    }
+   }
+ 
+   LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
+ 
+-  // Make sure that we're actually a file...
+-  struct NS_tstat_t fileInfo;
+-  rv = NS_tstat(mFile, &fileInfo);
+-  if (rv) {
+-    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
+-         errno));
+-    return READ_ERROR;
+-  }
++  if (!mIsLink) {
++    // Make sure that we're actually a file...
++    struct NS_tstat_t fileInfo;
++    rv = NS_tstat(mFile, &fileInfo);
++    if (rv) {
++      LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
++           errno));
++      return READ_ERROR;
++    }
+ 
+-  if (!S_ISREG(fileInfo.st_mode)) {
+-    LOG(("path present, but not a file: " LOG_S, mFile));
+-    return DELETE_ERROR_EXPECTED_FILE;
++    if (!S_ISREG(fileInfo.st_mode)) {
++      LOG(("path present, but not a file: " LOG_S, mFile));
++      return DELETE_ERROR_EXPECTED_FILE;
++    }
+   }
+ 
+   NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/'));
+@@ -1134,7 +1183,13 @@ RemoveFile::Execute()
+ 
+   // The file is checked for existence here and in Prepare since it might have
+   // been removed by a separate instruction: bug 311099.
+-  int rv = NS_taccess(mFile, F_OK);
++  int rv = 0;
++  if (mIsLink) {
++    struct NS_tstat_t linkInfo;
++    rv = NS_tlstat(mFile, &linkInfo);
++  } else {
++    rv = NS_taccess(mFile, F_OK);
++  }
+   if (rv) {
+     LOG(("file cannot be removed because it does not exist; skipping"));
+     mSkip = 1;
+@@ -1814,6 +1869,97 @@ PatchIfFile::Finish(int status)
+   PatchFile::Finish(status);
+ }
+ 
++#ifndef XP_WIN
++class AddSymlink : public Action
++{
++public:
++  AddSymlink() : mLinkName(NULL)
++               , mTarget(NULL)
++               , mAdded(false)
++            { }
++
++  virtual int Parse(NS_tchar *line);
++  virtual int Prepare();
++  virtual int Execute();
++  virtual void Finish(int status);
++
++private:
++  const NS_tchar *mLinkName;
++  const NS_tchar *mTarget;
++  bool mAdded;
++};
++
++int
++AddSymlink::Parse(NS_tchar *line)
++{
++  // format "<linkname>" "target"
++
++  mLinkName = get_valid_path(&line);
++  if (!mLinkName)
++    return PARSE_ERROR;
++
++  // consume whitespace between args
++  NS_tchar *q = mstrtok(kQuote, &line);
++  if (!q)
++    return PARSE_ERROR;
++
++  mTarget = get_valid_path(&line, false, true);
++  if (!mTarget)
++    return PARSE_ERROR;
++
++  return OK;
++}
++
++int
++AddSymlink::Prepare()
++{
++  LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
++
++  return OK;
++}
++
++int
++AddSymlink::Execute()
++{
++  LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
++
++  // First make sure that we can actually get rid of any existing file or link.
++  struct stat linkInfo;
++  int rv = lstat(mLinkName, &linkInfo);
++  if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) {
++    rv = NS_taccess(mLinkName, F_OK);
++  }
++  if (rv == 0) {
++    rv = backup_create(mLinkName);
++    if (rv)
++      return rv;
++  } else {
++    rv = ensure_parent_dir(mLinkName);
++    if (rv)
++      return rv;
++  }
++
++  // Create the link.
++  rv = symlink(mTarget, mLinkName);
++  if (!rv) {
++    mAdded = true;
++  }
++
++  return rv;
++}
++
++void
++AddSymlink::Finish(int status)
++{
++  LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
++  // 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(mLinkName);
++  backup_finish(mLinkName, status);
++}
++#endif
++
+ //-----------------------------------------------------------------------------
+ 
+ #ifdef XP_WIN
+@@ -2115,14 +2261,29 @@ 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"));
+@@ -2131,6 +2292,23 @@ 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"));
++  skiplist.append(SKIPLIST_COUNT - 2, gInstallDirPath,
++         NS_T("TorBrowser/Data/Browser/profile.meek-http-helper/parent.lock"));
++#else
++  skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath,
++                  NS_T("TorBrowser/Data/Browser/profile.default/.parentlock"));
++  skiplist.append(SKIPLIST_COUNT - 2, gInstallDirPath,
++         NS_T("TorBrowser/Data/Browser/profile.meek-http-helper/.parentlock"));
++#endif
++
++skiplist.append(SKIPLIST_COUNT - 1, gInstallDirPath,
++                NS_T("TorBrowser/Data/Tor/lock"));
++#endif
++
+   return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
+ }
+ 
+@@ -2251,16 +2429,52 @@ ProcessReplaceRequest()
+     if (NS_taccess(deleteDir, F_OK)) {
+       NS_tmkdir(deleteDir, 0755);
+     }
++#if !defined(TOR_BROWSER_UPDATE)
+     remove_recursive_on_reboot(tmpDir, deleteDir);
+ #endif
++#endif
+   }
+ 
+ #ifdef XP_MACOSX
+   // 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
+ 
+@@ -2998,6 +3212,26 @@ int NS_main(int argc, NS_tchar **argv)
+       // using the service is because we are testing.
+       if (!useService && !noServiceFallback &&
+           updateLockFileHandle == INVALID_HANDLE_VALUE) {
++#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
+         SHELLEXECUTEINFO sinfo;
+         memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
+         sinfo.cbSize       = sizeof(SHELLEXECUTEINFO);
+@@ -3019,6 +3253,7 @@ int NS_main(int argc, NS_tchar **argv)
+         } else {
+           WriteStatusFile(ELEVATION_CANCELED);
+         }
++#endif
+       }
+ 
+       if (argc > callbackIndex) {
+@@ -3310,6 +3545,7 @@ int NS_main(int argc, NS_tchar **argv)
+   if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) {
+     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 so the
+@@ -3326,6 +3562,7 @@ int NS_main(int argc, NS_tchar **argv)
+       LOG(("NS_main: failed to schedule OS reboot removal of " \
+            "directory: " LOG_S, DELETE_DIR));
+     }
++#endif
+   }
+ #endif /* XP_WIN */
+ 
+@@ -3376,7 +3613,7 @@ int NS_main(int argc, NS_tchar **argv)
+   LogFinish();
+ 
+   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");
+@@ -4005,6 +4242,11 @@ int DoUpdate()
+     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));
+       return PARSE_ERROR;
+diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
+index 6d04d46..a3a2857 100644
+--- a/toolkit/xre/nsAppRunner.cpp
++++ b/toolkit/xre/nsAppRunner.cpp
+@@ -4051,12 +4051,20 @@ XREMain::XRE_mainStartup(bool* aExitFlag)
+   NS_ENSURE_SUCCESS(rv, 1);
+   rv = exeFile->GetParent(getter_AddRefs(exeDir));
+   NS_ENSURE_SUCCESS(rv, 1);
++#ifdef TOR_BROWSER_UPDATE
++  nsAutoCString compatVersion(TOR_BROWSER_VERSION);
++#endif
+   ProcessUpdates(mDirProvider.GetGREDir(),
+                  exeDir,
+                  updRoot,
+                  gRestartArgc,
+                  gRestartArgv,
+-                 mAppData->version);
++#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 83002ca..ee60aee 100644
+--- a/toolkit/xre/nsUpdateDriver.cpp
++++ b/toolkit/xre/nsUpdateDriver.cpp
+@@ -39,6 +39,7 @@
+ # include <windows.h>
+ # include <shlwapi.h>
+ # include "nsWindowsHelpers.h"
++# include "prprf.h"
+ # define getcwd(path, size) _getcwd(path, size)
+ # define getpid() GetCurrentProcessId()
+ #elif defined(XP_UNIX)
+@@ -168,6 +169,46 @@ GetInstallDirPath(nsIFile *appDir, nsACString& installDirPath)
+   return NS_OK;
+ }
+ 
++#if defined(TOR_BROWSER_UPDATE) && defined(XP_WIN)
++#define PATH_SEPARATOR ";"
++
++// In Tor Browser, updater.exe depends on some DLLs that are located in the
++// app directory.  To allow the updater to run when it has been copied into
++// the update directory, we append the app directory to the PATH.
++static nsresult
++AdjustPathForUpdater(nsIFile *appDir)
++{
++  nsAutoCString appPath;
++  nsresult rv = appDir->GetNativePath(appPath);
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  char *s = nullptr;
++  char *pathValue = PR_GetEnv("PATH");
++  if ((nullptr == pathValue) || ('\0' == *pathValue)) {
++    s = PR_smprintf("PATH=%s", appPath.get());
++  } else {
++    s = PR_smprintf("PATH=%s" PATH_SEPARATOR "%s", pathValue, appPath.get());
++  }
++
++  // We intentionally leak the value that is passed into PR_SetEnv() because
++  // the environment will hold a pointer to it.
++  if ((nullptr == s) || (PR_SUCCESS != PR_SetEnv(s)))
++    return NS_ERROR_FAILURE;
++
++  return NS_OK;
++}
++#endif
++
++#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
++
+ #if defined(XP_MACOSX)
+ // This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the
+ // gBinaryPath check removed so that the updater can reload the stub executable
+@@ -255,6 +296,33 @@ 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 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.
+  *
+@@ -323,12 +391,44 @@ IsOlderVersion(nsIFile *versionFile, const char *appVersion)
+   if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0)
+     return false;
+ 
++#ifdef DEBUG
++  printf("IsOlderVersion checking appVersion %s against updateVersion %s\n",
++         appVersion, buf);
++#endif
++
+   if (mozilla::Version(appVersion) > buf)
+     return true;
+ 
+   return false;
+ }
+ 
++#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, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
++    return NS_ERROR_FAILURE;
++  }
++
++  updatedDir.forget(aResult);
++  return NS_OK;
++}
++#endif
++#endif
++
+ static bool
+ CopyFileIntoUpdateDir(nsIFile *parentDir, const nsACString& leaf, nsIFile *updateDir)
+ {
+@@ -541,7 +641,12 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
+   nsAutoCString applyToDir;
+   nsCOMPtr<nsIFile> updatedDir;
+ #ifdef 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, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
++#endif
+ #else
+   if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
+ #endif
+@@ -632,6 +737,13 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
+   AppendToLibPath(installDirPath.get());
+ #endif
+ 
++#if defined(TOR_BROWSER_UPDATE) && defined(XP_WIN)
++  nsresult rv2 = AdjustPathForUpdater(appDir);
++  if (NS_FAILED(rv2)) {
++    LOG(("SwitchToUpdatedApp -- AdjustPathForUpdater failed (0x%x)\n", rv2));
++  }
++#endif
++
+   LOG(("spawning updater process for replacing [%s]\n", updaterPath.get()));
+ 
+ #if defined(USE_EXECV)
+@@ -807,7 +919,12 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
+     applyToDir.Assign(installDirPath);
+   } else {
+ #ifdef 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, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
++#endif
+ #else
+     if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
+ #endif
+@@ -911,6 +1028,14 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
+   if (isOSUpdate) {
+     PR_SetEnv("MOZ_OS_UPDATE=1");
+   }
++
++#if defined(TOR_BROWSER_UPDATE) && defined(XP_WIN)
++  nsresult rv2 = AdjustPathForUpdater(appDir);
++  if (NS_FAILED(rv2)) {
++    LOG(("ApplyUpdate -- AdjustPathForUpdater failed (0x%x)\n", rv2));
++  }
++#endif
++
+ #if defined(MOZ_WIDGET_GONK)
+   // We want the updater to be CPU friendly and not subject to being killed by
+   // the low memory killer, so we pass in some preferences to allow it to
+@@ -933,6 +1058,9 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
+ #endif
+ 
+   LOG(("spawning updater process [%s]\n", updaterPath.get()));
++#ifdef DEBUG
++  dump_argv("ApplyUpdate updater", argv, argc);
++#endif
+ 
+ #if defined(USE_EXECV)
+   // Don't use execv when staging updates.
+@@ -956,6 +1084,9 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
+   // LaunchChildMac uses posix_spawnp and prefers the current
+   // architecture when launching. It doesn't require a
+   // null-terminated string but it doesn't matter if we pass one.
++#ifdef DEBUG
++  dump_argv("ApplyUpdate after SetupMacCommandLine", argv, argc);
++#endif
+   LaunchChildMac(argc, argv, 0, outpid);
+   if (restart) {
+     exit(0);
+@@ -994,9 +1125,29 @@ ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
+                bool restart, bool isOSUpdate, nsIFile *osApplyToDir,
+                ProcessType *pid)
+ {
++#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(NS_LITERAL_CSTRING("tobedeleted"));
++    if (NS_SUCCEEDED(winrv)) {
++      winrv = deleteDir->Remove(true);
++    }
++  }
++#endif
++
+   nsresult rv;
+ 
+   nsCOMPtr<nsIFile> updatesDir;
++#ifdef DEBUG
++  nsAutoCString path;
++  updRootDir->GetNativePath(path);
++  printf("ProcessUpdates updateRootDir: %s appVersion: %s\n",
++         path.get(), appVersion);
++#endif
+   rv = updRootDir->Clone(getter_AddRefs(updatesDir));
+   if (NS_FAILED(rv))
+     return rv;
+@@ -1022,6 +1173,12 @@ 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);
++  updatesDir->GetNativePath(path);
++  printf("ProcessUpdates updatesDir: %s\n", path.get());
++#endif
+   switch (status) {
+   case ePendingUpdate:
+   case ePendingService: {
+@@ -1104,7 +1261,11 @@ nsUpdateProcessor::ProcessUpdate(nsIUpdate* aUpdate)
+     if (NS_FAILED(rv))
+       appDir = dirProvider->GetAppDir();
+ 
++#ifdef TOR_BROWSER_UPDATE
++    appVersion = TOR_BROWSER_VERSION;
++#else
+     appVersion = gAppData->version;
++#endif
+     argc = gRestartArgc;
+     argv = gRestartArgv;
+   } else {
+@@ -1134,6 +1295,8 @@ nsUpdateProcessor::ProcessUpdate(nsIUpdate* aUpdate)
+                  getter_AddRefs(updRoot));
+     NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
+ 
++    // To support Tor Browser updates from xpcshell, modify the following
++    // code to use TOR_BROWSER_VERSION from the configure process.
+     nsCOMPtr<nsIXULAppInfo> appInfo =
+       do_GetService("@mozilla.org/xre/app-info;1");
+     if (appInfo) {
+diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
+index 831a373..403e820 100644
+--- a/toolkit/xre/nsXREDirProvider.cpp
++++ b/toolkit/xre/nsXREDirProvider.cpp
+@@ -1066,6 +1066,31 @@ nsXREDirProvider::GetUpdateRootDir(nsIFile* *aResult)
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #ifdef XP_MACOSX
++#ifdef TOR_BROWSER_UPDATE
++#ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR
++  // For Tor Browser, we cannot store update history, etc. under the user's
++  // home directory. Instead, we place it under
++  // Tor Browser.app/../TorBrowser-Data/UpdateInfo/
++  nsCOMPtr<nsIFile> appRootDir;
++  rv = GetAppRootDir(getter_AddRefs(appRootDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++  nsCOMPtr<nsIFile> localDir;
++  rv = appRootDir->GetParent(getter_AddRefs(localDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++  rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("TorBrowser-Data"
++                                     XPCOM_FILE_PATH_SEPARATOR "UpdateInfo"));
++#else
++  // For Tor Browser, we cannot store update history, etc. under the user's home directory.
++  // Instead, we place it under Tor Browser.app/TorBrowser/UpdateInfo/
++  nsCOMPtr<nsIFile> localDir;
++  rv = GetAppRootDir(getter_AddRefs(localDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++  rv = localDir->AppendNative(NS_LITERAL_CSTRING("TorBrowser"));
++  NS_ENSURE_SUCCESS(rv, rv);
++  rv = localDir->AppendNative(NS_LITERAL_CSTRING("UpdateInfo"));
++#endif
++  NS_ENSURE_SUCCESS(rv, rv);
++#else
+   nsCOMPtr<nsIFile> appRootDirFile;
+   nsCOMPtr<nsIFile> localDir;
+   nsAutoString appDirPath;
+@@ -1096,6 +1121,7 @@ nsXREDirProvider::GetUpdateRootDir(nsIFile* *aResult)
+       NS_FAILED(localDir->AppendRelativePath(appDirPath))) {
+     return NS_ERROR_FAILURE;
+   }
++#endif
+ 
+   localDir.forget(aResult);
+   return NS_OK;
+@@ -1243,33 +1269,8 @@ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
+   NS_ENSURE_ARG_POINTER(aFile);
+   nsCOMPtr<nsIFile> localDir;
+ 
+-  nsresult rv = GetAppDir()->Clone(getter_AddRefs(localDir));
++  nsresult rv = GetAppRootDir(getter_AddRefs(localDir));
+   NS_ENSURE_SUCCESS(rv, rv);
+-
+-  int levelsToRemove = 0; // In FF21+, appDir points to browser subdirectory.
+-#if defined(XP_MACOSX)
+-  levelsToRemove += 1;
+-#endif
+-  while (localDir && (levelsToRemove > 0)) {
+-    // When crawling up the hierarchy, components named "." do not count.
+-    nsAutoCString removedName;
+-    rv = localDir->GetNativeLeafName(removedName);
+-    NS_ENSURE_SUCCESS(rv, rv);
+-    bool didRemove = !removedName.Equals(".");
+-
+-    // Remove a directory component.
+-    nsCOMPtr<nsIFile> parentDir;
+-    rv = localDir->GetParent(getter_AddRefs(parentDir));
+-    NS_ENSURE_SUCCESS(rv, rv);
+-    localDir = parentDir;
+-
+-    if (didRemove)
+-      --levelsToRemove;
+-  }
+-
+-  if (!localDir)
+-    return NS_ERROR_FAILURE;
+-
+   rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("TorBrowser"
+                                      XPCOM_FILE_PATH_SEPARATOR "Data"
+                                      XPCOM_FILE_PATH_SEPARATOR "Browser"));
+@@ -1376,6 +1377,44 @@ nsXREDirProvider::GetUserDataDirectory(nsIFile** aFile, bool aLocal,
+ }
+ 
+ nsresult
++nsXREDirProvider::GetAppRootDir(nsIFile* *aFile)
++{
++  NS_ENSURE_ARG_POINTER(aFile);
++  nsCOMPtr<nsIFile> appRootDir;
++
++  nsresult rv = GetAppDir()->Clone(getter_AddRefs(appRootDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  int levelsToRemove = 0; // In FF21+, appDir points to browser subdirectory.
++#if defined(XP_MACOSX)
++  levelsToRemove += 1;
++#endif
++  while (appRootDir && (levelsToRemove > 0)) {
++    // When crawling up the hierarchy, components named "." do not count.
++    nsAutoCString removedName;
++    rv = appRootDir->GetNativeLeafName(removedName);
++    NS_ENSURE_SUCCESS(rv, rv);
++    bool didRemove = !removedName.Equals(".");
++
++    // Remove a directory component.
++    nsCOMPtr<nsIFile> parentDir;
++    rv = appRootDir->GetParent(getter_AddRefs(parentDir));
++    NS_ENSURE_SUCCESS(rv, rv);
++    appRootDir = parentDir;
++
++    if (didRemove)
++      --levelsToRemove;
++  }
++
++  if (!appRootDir)
++    return NS_ERROR_FAILURE;
++
++  appRootDir.forget(aFile);
++  return NS_OK;
++}
++
++
++nsresult
+ nsXREDirProvider::EnsureDirectoryExists(nsIFile* aDirectory)
+ {
+   bool exists;
+diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h
+index 1985f66..b86cc68 100644
+--- a/toolkit/xre/nsXREDirProvider.h
++++ b/toolkit/xre/nsXREDirProvider.h
+@@ -107,6 +107,7 @@ protected:
+ #if defined(XP_UNIX) || defined(XP_MACOSX)
+   static nsresult GetSystemExtensionsDirectory(nsIFile** aFile);
+ #endif
++  nsresult GetAppRootDir(nsIFile* *aFile);
+   static nsresult EnsureDirectoryExists(nsIFile* aDirectory);
+   void EnsureProfileFileExists(nsIFile* aFile);
+ 
+diff --git a/tools/update-packaging/common.sh b/tools/update-packaging/common.sh
+index eb35880..96bfa43 100755
+--- a/tools/update-packaging/common.sh
++++ b/tools/update-packaging/common.sh
+@@ -8,7 +8,13 @@
+ # 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
++
+ # By default just assume that these tools exist on our path
+ MAR=${MAR:-mar}
+ BZIP2=${BZIP2:-bzip2}
+@@ -21,6 +27,12 @@ notice() {
+   echo "$*" 1>&2
+ }
+ 
++verbose_notice() {
++  if [ $QUIET -eq 0 ]; then
++    notice "$*"
++  fi
++}
++
+ get_file_size() {
+   info=($(ls -ln "$1"))
+   echo ${info[4]}
+@@ -54,22 +66,10 @@ 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/')
+-    notice "     add-if \"$testdir\" \"$f\""
+-    echo "add-if \"$testdir\" \"$f\"" >> $filev2
+-    if [ ! $filev3 = "" ]; then
+-      echo "add-if \"$testdir\" \"$f\"" >> $filev3
+-    fi
+-  else
+-    notice "        add \"$f\"$forced"
+-    echo "add \"$f\"" >> $filev2
+-    if [ ! $filev3 = "" ]; then
+-      echo "add \"$f\"" >> $filev3
+-    fi
++  verbose_notice "        add \"$f\"$forced"
++  echo "add \"$f\"" >> "$filev2"
++  if [ ! "$filev3" = "" ]; then
++    echo "add \"$f\"" >> "$filev3"
+   fi
+ }
+ 
+@@ -100,8 +100,19 @@ make_add_if_not_instruction() {
+   f="$1"
+   filev3="$2"
+ 
+-  notice " add-if-not \"$f\" \"$f\""
+-  echo "add-if-not \"$f\" \"$f\"" >> $filev3
++  verbose_notice " add-if-not \"$f\" \"$f\""
++  echo "add-if-not \"$f\" \"$f\"" >> "$filev3"
++}
++
++make_addsymlink_instruction() {
++  link="$1"
++  target="$2"
++  filev2="$3"
++  filev3="$4"
++
++  verbose_notice "        addsymlink: $link -> $target"
++  echo "addsymlink \"$link\" \"$target\"" >> "$filev2"
++  echo "addsymlink \"$link\" \"$target\"" >> "$filev3"
+ }
+ 
+ make_patch_instruction() {
+@@ -109,19 +120,9 @@ make_patch_instruction() {
+   filev2="$2"
+   filev3="$3"
+ 
+-  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/')
+-    notice "   patch-if \"$testdir\" \"$f.patch\" \"$f\""
+-    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> $filev2
+-    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> $filev3
+-  else
+-    notice "      patch \"$f.patch\" \"$f\""
+-    echo "patch \"$f.patch\" \"$f\"" >> $filev2
+-    echo "patch \"$f.patch\" \"$f\"" >> $filev3
+-  fi
++  verbose_notice "      patch \"$f.patch\" \"$f\""
++  echo "patch \"$f.patch\" \"$f\"" >> "$filev2"
++  echo "patch \"$f.patch\" \"$f\"" >> "$filev3"
+ }
+ 
+ append_remove_instructions() {
+@@ -148,19 +149,19 @@ append_remove_instructions() {
+         # Exclude comments
+         if [ ! $(echo "$f" | grep -c '^#') = 1 ]; then
+           if [ $(echo "$f" | grep -c '\/$') = 1 ]; then
+-            notice "      rmdir \"$f\""
+-            echo "rmdir \"$f\"" >> $filev2
+-            echo "rmdir \"$f\"" >> $filev3
++            verbose_notice "      rmdir \"$f\""
++            echo "rmdir \"$f\"" >> "$filev2"
++            echo "rmdir \"$f\"" >> "$filev3"
+           elif [ $(echo "$f" | grep -c '\/\*$') = 1 ]; then
+             # Remove the *
+             f=$(echo "$f" | sed -e 's:\*$::')
+-            notice "    rmrfdir \"$f\""
+-            echo "rmrfdir \"$f\"" >> $filev2
+-            echo "rmrfdir \"$f\"" >> $filev3
++            verbose_notice "    rmrfdir \"$f\""
++            echo "rmrfdir \"$f\"" >> "$filev2"
++            echo "rmrfdir \"$f\"" >> "$filev3"
+           else
+-            notice "     remove \"$f\""
+-            echo "remove \"$f\"" >> $filev2
+-            echo "remove \"$f\"" >> $filev3
++            verbose_notice "     remove \"$f\""
++            echo "remove \"$f\"" >> "$filev2"
++            echo "remove \"$f\"" >> "$filev3"
+           fi
+         fi
+       fi
+@@ -170,6 +171,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
+ 
+@@ -182,6 +187,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"
+@@ -203,3 +213,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 f046614..f0bd7f6 100755
+--- a/tools/update-packaging/make_full_update.sh
++++ b/tools/update-packaging/make_full_update.sh
+@@ -28,10 +28,16 @@ if [ $1 = -h ]; then
+   notice ""
+   notice "Options:"
+   notice "  -h  show this help text"
++  notice "  -q  be less verbose"
+   notice ""
+   exit 1
+ fi
+ 
++if [ $1 = -q ]; then
++  QUIET=1
++  shift
++fi
++
+ # -----------------------------------------------------------------------------
+ 
+ archive="$1"
+@@ -63,17 +69,46 @@ if [ ! -f "precomplete" ]; then
+ fi
+ 
+ list_files files
++list_symlinks symlinks symlink_targets
++
++# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
++# we should remove the following lines:
++# Make sure we delete the pre 5.1.0 HTTPS Everywhere as well in case it
++# exists. The extension ID got changed with the version bump to 5.1.0.
++ext_path='TorBrowser/Data/Browser/profile.default/extensions'
++if [ -d "$ext_dir" ]; then
++  directories_to_remove="$ext_path/https-everywhere at eff.org $ext_path/https-everywhere-eff at eff.org"
++else
++  directories_to_remove=""
++fi
++# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+ 
+ popd
+ 
+ # Add the type of update to the beginning of the update manifests.
+-> $updatemanifestv2
+-> $updatemanifestv3
++> "$updatemanifestv2"
++> "$updatemanifestv3"
+ notice ""
+ notice "Adding type instruction to update manifests"
+ notice "       type complete"
+-echo "type \"complete\"" >> $updatemanifestv2
+-echo "type \"complete\"" >> $updatemanifestv3
++echo "type \"complete\"" >> "$updatemanifestv2"
++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\"" >> "$updatemanifestv2"
++  echo "rmrfdir \"$dir_to_remove\"" >> "$updatemanifestv3"
++done
++# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+ 
+ notice ""
+ notice "Adding file add instructions to update manifests"
+@@ -99,6 +134,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" "$updatemanifestv2" "$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 592b7ab..15af172 100755
+--- a/tools/update-packaging/make_incremental_update.sh
++++ b/tools/update-packaging/make_incremental_update.sh
+@@ -21,6 +21,7 @@ print_usage() {
+   notice "  -h  show this help text"
+   notice "  -f  clobber this file in the installation"
+   notice "      Must be a path to a file to clobber in the partial update."
++  notice "  -q  be less verbose"
+   notice ""
+ }
+ 
+@@ -61,6 +62,21 @@ check_for_forced_update() {
+       ## "true" *giggle*
+       return 0;
+     fi
++
++# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
++# we should remove the following lines:
++    # If the file in the skip list ends with /*, do a prefix match.
++    # This allows TorBrowser/Data/Browser/profile.default/extensions/https-everywhere-eff at eff.org/*
++    # to be used to force all HTTPS Everywhere files to be updated.
++    f_suffix=${f##*/}
++    if [[ $f_suffix = "*" ]]; then
++      f_prefix="${f%\/\*}";
++      if [[ $forced_file_chk == $f_prefix* ]]; then
++        ## 0 means "true"
++        return 0;
++      fi
++    fi
++# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
+   done
+   ## 'false'... because this is bash. Oh yay!
+   return 1;
+@@ -71,13 +87,18 @@ 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=""
+ 
+-while getopts "hf:" flag
++while getopts "hqf:" flag
+ do
+    case "$flag" in
+       h) print_usage; exit 0
+       ;;
++      q) QUIET=1
++      ;;
+       f) requested_forced_updates="$requested_forced_updates $OPTARG"
+       ;;
+       ?) print_usage; exit 1
+@@ -104,6 +125,46 @@ updatemanifestv2="$workdir/updatev2.manifest"
+ updatemanifestv3="$workdir/updatev3.manifest"
+ archivefiles="updatev2.manifest updatev3.manifest"
+ 
++# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms,
++# we should remove the following lines:
++# If the NoScript or HTTPS Everywhere extensions have changed between
++# releases, add them to the "force updates" list.
++ext_path='TorBrowser/Data/Browser/profile.default/extensions'
++if [ -d "$newdir/$ext_path" ]; then
++  https_everywhere='https-everywhere-eff at eff.org'
++  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
++
++  # HTTPS Everywhere is an unpacked extension, so we need to determine if any of
++  # the unpacked files have changed. Since that is messy, we simply compare the
++  # old extension's install.rdf file to the new one.
++  https_everywhere_install_rdf="$ext_path/$https_everywhere/install.rdf"
++  diff "$olddir/$https_everywhere_install_rdf"     \
++        "$newdir/$https_everywhere_install_rdf" > /dev/null
++  rc=$?
++  if [ $rc -gt 1 -a -e "$olddir/$https_everywhere_install_rdf" ]; then
++    notice "Unexpected exit $rc from $https_everywhere_install_rdf diff command"
++    exit 2
++  elif [ $rc -ge 1 ]; then
++    requested_forced_updates="$requested_forced_updates $ext_path/$https_everywhere/*"
++    # Make sure we delete the pre 5.1.0 HTTPS Everywhere as well in case it
++    # exists. The extension ID got changed with the version bump to 5.1.0.
++    directories_to_remove="$directories_to_remove $ext_path/https-everywhere at eff.org $ext_path/$https_everywhere"
++  fi
++fi
++# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal
++
+ mkdir -p "$workdir"
+ 
+ # Generate a list of all files in the target directory.
+@@ -113,6 +174,7 @@ if test $? -ne 0 ; then
+ fi
+ 
+ list_files oldfiles
++list_symlinks oldsymlinks oldsymlink_targets
+ list_dirs olddirs
+ 
+ popd
+@@ -131,17 +193,34 @@ fi
+ 
+ list_dirs newdirs
+ list_files newfiles
++list_symlinks newsymlinks newsymlink_targets
+ 
+ popd
+ 
+ # Add the type of update to the beginning of the update manifests.
+ notice ""
+ notice "Adding type instruction to update manifests"
+-> $updatemanifestv2
+-> $updatemanifestv3
++> "$updatemanifestv2"
++> "$updatemanifestv3"
+ notice "       type partial"
+-echo "type \"partial\"" >> $updatemanifestv2
+-echo "type \"partial\"" >> $updatemanifestv3
++echo "type \"partial\"" >> "$updatemanifestv2"
++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\"" >> "$updatemanifestv2"
++  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"
+@@ -234,6 +313,24 @@ 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\"" >> "$updatemanifestv2"
++  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" "$updatemanifestv2" "$updatemanifestv3"
++done
++
+ # Newly added files
+ notice ""
+ notice "Adding file add instructions to update manifests"
+@@ -270,8 +367,8 @@ notice "Adding file remove instructions to update manifests"
+ for ((i=0; $i<$num_removes; i=$i+1)); do
+   f="${remove_array[$i]}"
+   notice "     remove \"$f\""
+-  echo "remove \"$f\"" >> $updatemanifestv2
+-  echo "remove \"$f\"" >> $updatemanifestv3
++  echo "remove \"$f\"" >> "$updatemanifestv2"
++  echo "remove \"$f\"" >> "$updatemanifestv3"
+ done
+ 
+ # Add remove instructions for any dead files.
+@@ -288,8 +385,8 @@ for ((i=0; $i<$num_olddirs; i=$i+1)); do
+   # If this dir doesn't exist in the new directory remove it.
+   if [ ! -d "$newdir/$f" ]; then
+     notice "      rmdir $f/"
+-    echo "rmdir \"$f/\"" >> $updatemanifestv2
+-    echo "rmdir \"$f/\"" >> $updatemanifestv3
++    echo "rmdir \"$f/\"" >> "$updatemanifestv2"
++    echo "rmdir \"$f/\"" >> "$updatemanifestv3"
+   fi
+ done
+ 
+-- 
+cgit v0.10.2
+





More information about the tor-commits mailing list