toolkit/mozapps/update/nsUpdateService.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 #filter substitution
     3 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     4 /*
     5 # This Source Code Form is subject to the terms of the Mozilla Public
     6 # License, v. 2.0. If a copy of the MPL was not distributed with this
     7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     8 */
    10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    11 Components.utils.import("resource://gre/modules/FileUtils.jsm");
    12 Components.utils.import("resource://gre/modules/AddonManager.jsm");
    13 Components.utils.import("resource://gre/modules/Services.jsm");
    14 #ifdef BUILD_CTYPES
    15 #ifdef XP_WIN
    16 Components.utils.import("resource://gre/modules/ctypes.jsm");
    17 #endif
    18 #endif
    20 const Cc = Components.classes;
    21 const Ci = Components.interfaces;
    22 const Cr = Components.results;
    23 const Cu = Components.utils;
    25 const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
    26 const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
    28 const PREF_APP_UPDATE_ALTWINDOWTYPE       = "app.update.altwindowtype";
    29 const PREF_APP_UPDATE_AUTO                = "app.update.auto";
    30 const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
    31 const PREF_APP_UPDATE_BACKGROUNDERRORS    = "app.update.backgroundErrors";
    32 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
    33 const PREF_APP_UPDATE_CERTS_BRANCH        = "app.update.certs.";
    34 const PREF_APP_UPDATE_CERT_CHECKATTRS     = "app.update.cert.checkAttributes";
    35 const PREF_APP_UPDATE_CERT_ERRORS         = "app.update.cert.errors";
    36 const PREF_APP_UPDATE_CERT_MAXERRORS      = "app.update.cert.maxErrors";
    37 const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
    38 const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";
    39 const PREF_APP_UPDATE_ENABLED             = "app.update.enabled";
    40 const PREF_APP_UPDATE_METRO_ENABLED       = "app.update.metro.enabled";
    41 const PREF_APP_UPDATE_IDLETIME            = "app.update.idletime";
    42 const PREF_APP_UPDATE_INCOMPATIBLE_MODE   = "app.update.incompatible.mode";
    43 const PREF_APP_UPDATE_INTERVAL            = "app.update.interval";
    44 const PREF_APP_UPDATE_LASTUPDATETIME      = "app.update.lastUpdateTime.background-update-timer";
    45 const PREF_APP_UPDATE_LOG                 = "app.update.log";
    46 const PREF_APP_UPDATE_MODE                = "app.update.mode";
    47 const PREF_APP_UPDATE_NEVER_BRANCH        = "app.update.never.";
    48 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
    49 const PREF_APP_UPDATE_POSTUPDATE          = "app.update.postupdate";
    50 const PREF_APP_UPDATE_PROMPTWAITTIME      = "app.update.promptWaitTime";
    51 const PREF_APP_UPDATE_SHOW_INSTALLED_UI   = "app.update.showInstalledUI";
    52 const PREF_APP_UPDATE_SILENT              = "app.update.silent";
    53 const PREF_APP_UPDATE_STAGING_ENABLED     = "app.update.staging.enabled";
    54 const PREF_APP_UPDATE_URL                 = "app.update.url";
    55 const PREF_APP_UPDATE_URL_DETAILS         = "app.update.url.details";
    56 const PREF_APP_UPDATE_URL_OVERRIDE        = "app.update.url.override";
    57 const PREF_APP_UPDATE_SERVICE_ENABLED     = "app.update.service.enabled";
    58 const PREF_APP_UPDATE_SERVICE_ERRORS      = "app.update.service.errors";
    59 const PREF_APP_UPDATE_SERVICE_MAX_ERRORS  = "app.update.service.maxErrors";
    60 const PREF_APP_UPDATE_SOCKET_ERRORS       = "app.update.socket.maxErrors";
    61 const PREF_APP_UPDATE_RETRY_TIMEOUT       = "app.update.socket.retryTimeout";
    63 const PREF_APP_DISTRIBUTION               = "distribution.id";
    64 const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
    66 const PREF_APP_B2G_VERSION                = "b2g.version";
    68 const PREF_EM_HOTFIX_ID                   = "extensions.hotfix.id";
    70 const URI_UPDATE_PROMPT_DIALOG  = "chrome://mozapps/content/update/updates.xul";
    71 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
    72 const URI_BRAND_PROPERTIES      = "chrome://branding/locale/brand.properties";
    73 const URI_UPDATES_PROPERTIES    = "chrome://mozapps/locale/update/updates.properties";
    74 const URI_UPDATE_NS             = "http://www.mozilla.org/2005/app-update";
    76 const CATEGORY_UPDATE_TIMER               = "update-timer";
    78 const KEY_GRED            = "GreD";
    79 const KEY_UPDROOT         = "UpdRootD";
    80 const KEY_EXECUTABLE      = "XREExeF";
    82 #ifdef TOR_BROWSER_VERSION
    83 #expand const TOR_BROWSER_VERSION = __TOR_BROWSER_VERSION__;
    84 #endif
    86 #ifdef MOZ_WIDGET_GONK
    87 #define USE_UPDATE_ARCHIVE_DIR
    88 #endif
    90 #ifdef USE_UPDATE_ARCHIVE_DIR
    91 const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
    92 #endif
    94 #ifdef XP_WIN
    95 #define CHECK_CAN_USE_SERVICE
    96 #elifdef MOZ_WIDGET_GONK
    97 // In Gonk, the updater will remount the /system partition to move staged files
    98 // into place, so we skip the test here to keep things isolated.
    99 #define CHECK_CAN_USE_SERVICE
   100 #endif
   102 const DIR_UPDATES         = "updates";
   103 #ifdef XP_MACOSX
   104 const UPDATED_DIR         = "Updated.app";
   105 #else
   106 const UPDATED_DIR         = "updated";
   107 #endif
   108 const FILE_UPDATE_STATUS  = "update.status";
   109 const FILE_UPDATE_VERSION = "update.version";
   110 #ifdef MOZ_WIDGET_ANDROID
   111 const FILE_UPDATE_ARCHIVE = "update.apk";
   112 #else
   113 const FILE_UPDATE_ARCHIVE = "update.mar";
   114 #endif
   115 const FILE_UPDATE_LINK    = "update.link";
   116 const FILE_UPDATE_LOG     = "update.log";
   117 const FILE_UPDATES_DB     = "updates.xml";
   118 const FILE_UPDATE_ACTIVE  = "active-update.xml";
   119 const FILE_PERMS_TEST     = "update.test";
   120 const FILE_LAST_LOG       = "last-update.log";
   121 const FILE_BACKUP_LOG     = "backup-update.log";
   122 const FILE_UPDATE_LOCALE  = "update.locale";
   124 const STATE_NONE            = "null";
   125 const STATE_DOWNLOADING     = "downloading";
   126 const STATE_PENDING         = "pending";
   127 const STATE_PENDING_SVC     = "pending-service";
   128 const STATE_APPLYING        = "applying";
   129 const STATE_APPLIED         = "applied";
   130 const STATE_APPLIED_OS      = "applied-os";
   131 const STATE_APPLIED_SVC     = "applied-service";
   132 const STATE_SUCCEEDED       = "succeeded";
   133 const STATE_DOWNLOAD_FAILED = "download-failed";
   134 const STATE_FAILED          = "failed";
   136 // From updater/errors.h:
   137 const WRITE_ERROR        = 7;
   138 // const UNEXPECTED_ERROR   = 8; // Replaced with errors 38-42
   139 const ELEVATION_CANCELED = 9;
   141 // Windows service specific errors
   142 const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
   143 const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
   144 const SERVICE_UPDATER_SIGN_ERROR           = 26;
   145 const SERVICE_UPDATER_COMPARE_ERROR        = 27;
   146 const SERVICE_UPDATER_IDENTITY_ERROR       = 28;
   147 const SERVICE_STILL_APPLYING_ON_SUCCESS    = 29;
   148 const SERVICE_STILL_APPLYING_ON_FAILURE    = 30;
   149 const SERVICE_UPDATER_NOT_FIXED_DRIVE      = 31;
   150 const SERVICE_COULD_NOT_LOCK_UPDATER       = 32;
   151 const SERVICE_INSTALLDIR_ERROR             = 33;
   152 const SERVICE_COULD_NOT_COPY_UPDATER       = 49;
   154 const WRITE_ERROR_ACCESS_DENIED                     = 35;
   155 // const WRITE_ERROR_SHARING_VIOLATION                 = 36; // Replaced with errors 46-48
   156 const WRITE_ERROR_CALLBACK_APP                      = 37;
   157 const INVALID_UPDATER_STATUS_CODE                   = 38;
   158 const UNEXPECTED_BZIP_ERROR                         = 39;
   159 const UNEXPECTED_MAR_ERROR                          = 40;
   160 const UNEXPECTED_BSPATCH_ERROR                      = 41;
   161 const UNEXPECTED_FILE_OPERATION_ERROR               = 42;
   162 const FILESYSTEM_MOUNT_READWRITE_ERROR              = 43;
   163 const FOTA_GENERAL_ERROR                            = 44;
   164 const FOTA_UNKNOWN_ERROR                            = 45;
   165 const WRITE_ERROR_SHARING_VIOLATION_SIGNALED        = 46;
   166 const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47;
   167 const WRITE_ERROR_SHARING_VIOLATION_NOPID           = 48;
   168 const FOTA_FILE_OPERATION_ERROR                     = 49;
   169 const FOTA_RECOVERY_ERROR                           = 50;
   171 const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
   172 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
   173 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
   174 const NETWORK_ERROR_OFFLINE             = 111;
   175 const FILE_ERROR_TOO_BIG                = 112;
   177 // Error codes should be < 1000. Errors above 1000 represent http status codes
   178 const HTTP_ERROR_OFFSET                 = 1000;
   180 const DOWNLOAD_CHUNK_SIZE           = 300000; // bytes
   181 const DOWNLOAD_BACKGROUND_INTERVAL  = 600;    // seconds
   182 const DOWNLOAD_FOREGROUND_INTERVAL  = 0;
   184 const UPDATE_WINDOW_NAME      = "Update:Wizard";
   186 // The number of consecutive failures when updating using the service before
   187 // setting the app.update.service.enabled preference to false.
   188 const DEFAULT_SERVICE_MAX_ERRORS = 10;
   190 // The number of consecutive socket errors to allow before falling back to
   191 // downloading a different MAR file or failing if already downloading the full.
   192 const DEFAULT_SOCKET_MAX_ERRORS = 10;
   194 // The number of milliseconds to wait before retrying a connection error.
   195 const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000;
   197 // A background download is in progress (no notification)
   198 const PING_BGUC_IS_DOWNLOADING               = 0;
   199 // An update is staged (no notification)
   200 const PING_BGUC_IS_STAGED                    = 1;
   201 // Invalid url for app.update.url default preference (no notification)
   202 const PING_BGUC_INVALID_DEFAULT_URL          = 2;
   203 // Invalid url for app.update.url user preference (no notification)
   204 const PING_BGUC_INVALID_CUSTOM_URL           = 3;
   205 // Invalid url for app.update.url.override user preference (no notification)
   206 const PING_BGUC_INVALID_OVERRIDE_URL         = 4;
   207 // Unable to check for updates per gCanCheckForUpdates and hasUpdateMutex()
   208 // (no notification)
   209 const PING_BGUC_UNABLE_TO_CHECK              = 5;
   210 // Already has an active update in progress (no notification)
   211 const PING_BGUC_HAS_ACTIVEUPDATE             = 6;
   212 // Background checks disabled by preference (no notification)
   213 const PING_BGUC_PREF_DISABLED                = 7;
   214 // Background checks disabled for the current session (no notification)
   215 const PING_BGUC_DISABLED_FOR_SESSION         = 8;
   216 // Background checks disabled in Metro (no notification)
   217 const PING_BGUC_METRO_DISABLED               = 9;
   218 // Unable to perform a background check while offline (no notification)
   219 const PING_BGUC_OFFLINE                      = 10;
   220 // No update found certificate check failed and threshold reached
   221 // (possible mitm attack notification)
   222 const PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY   = 11;
   223 // No update found certificate check failed and threshold not reached
   224 // (no notification)
   225 const PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT   = 12;
   226 // Update found certificate check failed and threshold reached
   227 // (possible mitm attack notification)
   228 const PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY = 13;
   229 // Update found certificate check failed and threshold not reached
   230 // (no notification)
   231 const PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT = 14;
   232 // General update check failure and threshold reached
   233 // (check failure notification)
   234 const PING_BGUC_GENERAL_ERROR_NOTIFY         = 15;
   235 // General update check failure and threshold not reached
   236 // (no notification)
   237 const PING_BGUC_GENERAL_ERROR_SILENT         = 16;
   238 // No update found (no notification)
   239 const PING_BGUC_NO_UPDATE_FOUND              = 17;
   240 // No compatible update found though there were updates (no notification)
   241 const PING_BGUC_NO_COMPAT_UPDATE_FOUND       = 18;
   242 // Update found for a previous version (no notification)
   243 const PING_BGUC_UPDATE_PREVIOUS_VERSION      = 19;
   244 // Update found for a version with the never preference set (no notification)
   245 const PING_BGUC_UPDATE_NEVER_PREF            = 20;
   246 // Update found without a type attribute (no notification)
   247 const PING_BGUC_UPDATE_INVALID_TYPE          = 21;
   248 // The system is no longer supported (system unsupported notification)
   249 const PING_BGUC_UNSUPPORTED                  = 22;
   250 // Unable to apply updates (manual install to update notification)
   251 const PING_BGUC_UNABLE_TO_APPLY              = 23;
   252 // Showing prompt due to the update.xml specifying showPrompt
   253 // (update notification)
   254 const PING_BGUC_SHOWPROMPT_SNIPPET           = 24;
   255 // Showing prompt due to preference (update notification)
   256 const PING_BGUC_SHOWPROMPT_PREF              = 25;
   257 // Incompatible add-on check disabled by preference (background download)
   258 const PING_BGUC_ADDON_PREF_DISABLED          = 26;
   259 // Incompatible add-on not checked not performed due to same update version and
   260 // app version (background download)
   261 const PING_BGUC_ADDON_SAME_APP_VER           = 27;
   262 // No incompatible add-ons found during incompatible check (background download)
   263 const PING_BGUC_CHECK_NO_INCOMPAT            = 28;
   264 // Incompatible add-ons found and all of them have updates (background download)
   265 const PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT   = 29;
   266 // Incompatible add-ons found (update notification)
   267 const PING_BGUC_ADDON_HAVE_INCOMPAT          = 30;
   269 var gLocale = null;
   270 var gUpdateMutexHandle = null;
   272 #ifdef MOZ_WIDGET_GONK
   273 var gSDCardMountLock = null;
   275 XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
   276     return Services.env.get("EXTERNAL_STORAGE");
   277 });
   278 #endif
   280 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
   281                                   "resource://gre/modules/UpdateChannel.jsm");
   283 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
   284   return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
   285 });
   287 XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
   288   return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
   289 });
   291 // shared code for suppressing bad cert dialogs
   292 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
   293   let temp = { };
   294   Cu.import("resource://gre/modules/CertUtils.jsm", temp);
   295   return temp;
   296 });
   298 XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() {
   299   let abi = null;
   300   try {
   301     abi = Services.appinfo.XPCOMABI;
   302   }
   303   catch (e) {
   304     LOG("gABI - XPCOM ABI unknown: updates are not possible.");
   305   }
   306 #ifdef XP_MACOSX
   307   // Mac universal build should report a different ABI than either macppc
   308   // or mactel.
   309   let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
   310                  getService(Ci.nsIMacUtils);
   312   if (macutils.isUniversalBinary)
   313     abi += "-u-" + macutils.architecturesInBinary;
   314 #ifdef MOZ_SHARK
   315   // Disambiguate optimised and shark nightlies
   316   abi += "-shark"
   317 #endif
   318 #endif
   319   return abi;
   320 });
   322 #ifdef MOZ_WIDGET_GONK
   323 XPCOMUtils.defineLazyGetter(this, "gProductModel", function aus_gProductModel() {
   324   Cu.import("resource://gre/modules/systemlibs.js");
   325   return libcutils.property_get("ro.product.model");
   326 });
   327 #endif
   329 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
   330   let osVersion;
   331   let sysInfo = Cc["@mozilla.org/system-info;1"].
   332                 getService(Ci.nsIPropertyBag2);
   333   try {
   334     osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
   335   }
   336   catch (e) {
   337     LOG("gOSVersion - OS Version unknown: updates are not possible.");
   338   }
   340   if (osVersion) {
   341 #ifdef BUILD_CTYPES
   342 #ifdef XP_WIN
   343     const BYTE = ctypes.uint8_t;
   344     const WORD = ctypes.uint16_t;
   345     const DWORD = ctypes.uint32_t;
   346     const WCHAR = ctypes.jschar;
   347     const BOOL = ctypes.int;
   349     // This structure is described at:
   350     // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
   351     const SZCSDVERSIONLENGTH = 128;
   352     const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
   353         [
   354         {dwOSVersionInfoSize: DWORD},
   355         {dwMajorVersion: DWORD},
   356         {dwMinorVersion: DWORD},
   357         {dwBuildNumber: DWORD},
   358         {dwPlatformId: DWORD},
   359         {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
   360         {wServicePackMajor: WORD},
   361         {wServicePackMinor: WORD},
   362         {wSuiteMask: WORD},
   363         {wProductType: BYTE},
   364         {wReserved: BYTE}
   365         ]);
   367     // This structure is described at:
   368     // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
   369     const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
   370         [
   371         {wProcessorArchitecture: WORD},
   372         {wReserved: WORD},
   373         {dwPageSize: DWORD},
   374         {lpMinimumApplicationAddress: ctypes.voidptr_t},
   375         {lpMaximumApplicationAddress: ctypes.voidptr_t},
   376         {dwActiveProcessorMask: DWORD.ptr},
   377         {dwNumberOfProcessors: DWORD},
   378         {dwProcessorType: DWORD},
   379         {dwAllocationGranularity: DWORD},
   380         {wProcessorLevel: WORD},
   381         {wProcessorRevision: WORD}
   382         ]);
   384     let kernel32 = false;
   385     try {
   386       kernel32 = ctypes.open("Kernel32");
   387     } catch (e) {
   388       LOG("gOSVersion - Unable to open kernel32! " + e);
   389       osVersion += ".unknown (unknown)";
   390     }
   392     if(kernel32) {
   393       try {
   394         // Get Service pack info
   395         try {
   396           let GetVersionEx = kernel32.declare("GetVersionExW",
   397                                               ctypes.default_abi,
   398                                               BOOL,
   399                                               OSVERSIONINFOEXW.ptr);
   400           let winVer = OSVERSIONINFOEXW();
   401           winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
   403           if(0 !== GetVersionEx(winVer.address())) {
   404             osVersion += "." + winVer.wServicePackMajor
   405                       +  "." + winVer.wServicePackMinor;
   406           } else {
   407             LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
   408             osVersion += ".unknown";
   409           }
   410         } catch (e) {
   411           LOG("gOSVersion - error getting service pack information. Exception: " + e);
   412           osVersion += ".unknown";
   413         }
   415         // Get processor architecture
   416         let arch = "unknown";
   417         try {
   418           let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
   419                                                      ctypes.default_abi,
   420                                                      ctypes.void_t,
   421                                                      SYSTEM_INFO.ptr);
   422           let sysInfo = SYSTEM_INFO();
   423           // Default to unknown
   424           sysInfo.wProcessorArchitecture = 0xffff;
   426           GetNativeSystemInfo(sysInfo.address());
   427           switch(sysInfo.wProcessorArchitecture) {
   428             case 9:
   429               arch = "x64";
   430               break;
   431             case 6:
   432               arch = "IA64";
   433               break;
   434             case 0:
   435               arch = "x86";
   436               break;
   437           }
   438         } catch (e) {
   439           LOG("gOSVersion - error getting processor architecture.  Exception: " + e);
   440         } finally {
   441           osVersion += " (" + arch + ")";
   442         }
   443       } finally {
   444         kernel32.close();
   445       }
   446     }
   447 #endif
   448 #endif
   450     try {
   451       osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
   452     }
   453     catch (e) {
   454       // Not all platforms have a secondary widget library, so an error is nothing to worry about.
   455     }
   456     osVersion = encodeURIComponent(osVersion);
   457   }
   458   return osVersion;
   459 });
   461 /**
   462  * Tests to make sure that we can write to a given directory.
   463  *
   464  * @param updateTestFile a test file in the directory that needs to be tested.
   465  * @param createDirectory whether a test directory should be created.
   466  * @throws if we don't have right access to the directory.
   467  */
   468 function testWriteAccess(updateTestFile, createDirectory) {
   469   const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
   470   const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE;
   471   if (updateTestFile.exists())
   472     updateTestFile.remove(false);
   473   updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE,
   474                         createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE);
   475   updateTestFile.remove(false);
   476 }
   478 #ifdef XP_WIN
   480 /**
   481  * Closes a Win32 handle
   482  *
   483  * @param handle The handle to close
   484  */
   485 function closeHandle(handle) {
   486   var lib = ctypes.open("kernel32.dll");
   487   var CloseHandle = lib.declare("CloseHandle",
   488                                 ctypes.winapi_abi,
   489                                 ctypes.int32_t, /* success */
   490                                 ctypes.void_t.ptr); /* handle */
   491   CloseHandle(handle);
   492   lib.close();
   493 }
   495 /**
   496  * Creates a mutex.
   497  *
   498  * @param  aName
   499  *         The name for the mutex.
   500  * @param  aAllowExisting
   501  *         If false the function will close the handle and return null.
   502  * @return The Win32 handle to the mutex.
   503  */
   504 function createMutex(aName, aAllowExisting) {
   505   if (aAllowExisting === undefined) {
   506     aAllowExisting = true;
   507   }
   509   const INITIAL_OWN = 1;
   510   const ERROR_ALREADY_EXISTS = 0xB7;
   511   var lib = ctypes.open("kernel32.dll");
   512   var CreateMutexW = lib.declare("CreateMutexW",
   513                                  ctypes.winapi_abi,
   514                                  ctypes.void_t.ptr, /* return handle */
   515                                  ctypes.void_t.ptr, /* security attributes */
   516                                  ctypes.int32_t, /* initial owner */
   517                                  ctypes.jschar.ptr); /* name */
   519   var handle = CreateMutexW(null, INITIAL_OWN, aName);
   520   var alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
   521   if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
   522     closeHandle(handle);
   523     handle = null;
   524   }
   525   lib.close();
   527   if (handle && handle.isNull())
   528     handle = null;
   530   return handle;
   531 }
   533 /**
   534  * Determines a unique mutex name for the installation
   535  *
   536  * @param aGlobal true if the function should return a global mutex. A global
   537  *                mutex is valid across different sessions
   538  * @return Global mutex path
   539  */
   540 function getPerInstallationMutexName(aGlobal) {
   541   if (aGlobal === undefined) {
   542     aGobal = true;
   543   }
   544   let hasher = Cc["@mozilla.org/security/hash;1"].
   545                createInstance(Ci.nsICryptoHash);
   546   hasher.init(hasher.SHA1);
   548   var exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile);
   550   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
   551                   createInstance(Ci.nsIScriptableUnicodeConverter);
   552   converter.charset = "UTF-8";
   553   var data = converter.convertToByteArray(exeFile.path.toLowerCase());
   555   hasher.update(data, data.length);
   556   return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
   557 }
   558 #endif // XP_WIN
   560 /**
   561  * Whether or not the current instance has the update mutex. The update mutex
   562  * gives protection against 2 applications from the same installation updating:
   563  * 1) Running multiple profiles from the same installation path
   564  * 2) Running a Metro and Desktop application at the same time from the same
   565  *    path
   566  * 3) 2 applications running in 2 different user sessions from the same path
   567  *
   568  * @return true if this instance holds the update mutex
   569  */
   570 function hasUpdateMutex() {
   571 #ifdef XP_WIN
   572   if (!gUpdateMutexHandle) {
   573     gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
   574   }
   576   return !!gUpdateMutexHandle;
   577 #else
   578   return true;
   579 #endif // XP_WIN
   580 }
   582 XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
   583   function submitHasPermissionsTelemetryPing(val) {
   584     try {
   585       let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
   586       h.add(+val);
   587     } catch(e) {
   588       // Don't allow any exception to be propagated.
   589       Components.utils.reportError(e);
   590     }
   591   }
   593   let useService = false;
   594   if (shouldUseService() && isServiceInstalled()) {
   595     // No need to perform directory write checks, the maintenance service will
   596     // be able to write to all directories.
   597     LOG("gCanApplyUpdates - bypass the write checks because we'll use the service");
   598     useService = true;
   599   }
   601   if (!useService) {
   602     try {
   603       var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
   604       LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
   605       testWriteAccess(updateTestFile, false);
   606 #ifdef XP_WIN
   607       var sysInfo = Cc["@mozilla.org/system-info;1"].
   608                     getService(Ci.nsIPropertyBag2);
   610       // Example windowsVersion:  Windows XP == 5.1
   611       var windowsVersion = sysInfo.getProperty("version");
   612       LOG("gCanApplyUpdates - windowsVersion = " + windowsVersion);
   614     /**
   615   #    For Vista, updates can be performed to a location requiring admin
   616   #    privileges by requesting elevation via the UAC prompt when launching
   617   #    updater.exe if the appDir is under the Program Files directory
   618   #    (e.g. C:\Program Files\) and UAC is turned on and  we can elevate
   619   #    (e.g. user has a split token).
   620   #
   621   #    Note: this does note attempt to handle the case where UAC is turned on
   622   #    and the installation directory is in a restricted location that
   623   #    requires admin privileges to update other than Program Files.
   624      */
   625       var userCanElevate = false;
   627       if (parseFloat(windowsVersion) >= 6) {
   628         try {
   629           var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
   630                             getService(Ci.nsIProperties);
   631           // KEY_UPDROOT will fail and throw an exception if
   632           // appDir is not under the Program Files, so we rely on that
   633           var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
   634           // appDir is under Program Files, so check if the user can elevate
   635           userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).
   636                            userCanElevate;
   637           LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
   638         }
   639         catch (ex) {
   640           // When the installation directory is not under Program Files,
   641           // fall through to checking if write access to the
   642           // installation directory is available.
   643           LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files");
   644         }
   645       }
   647       /**
   648 #      On Windows, we no longer store the update under the app dir.
   649 #
   650 #      If we are on Windows (including Vista, if we can't elevate) we need to
   651 #      to check that we can create and remove files from the actual app
   652 #      directory (like C:\Program Files\Mozilla Firefox).  If we can't
   653 #      (because this user is not an adminstrator, for example) canUpdate()
   654 #      should return false.
   655 #
   656 #      For Vista, we perform this check to enable updating the  application
   657 #      when the user has write access to the installation directory under the
   658 #      following scenarios:
   659 #      1) the installation directory is not under Program Files
   660 #         (e.g. C:\Program Files)
   661 #      2) UAC is turned off
   662 #      3) UAC is turned on and the user is not an admin
   663 #         (e.g. the user does not have a split token)
   664 #      4) UAC is turned on and the user is already elevated, so they can't be
   665 #         elevated again
   666        */
   667       if (!userCanElevate) {
   668         // if we're unable to create the test file this will throw an exception.
   669         var appDirTestFile = getAppBaseDir();
   670         appDirTestFile.append(FILE_PERMS_TEST);
   671         LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path);
   672         if (appDirTestFile.exists())
   673           appDirTestFile.remove(false)
   674         appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
   675         appDirTestFile.remove(false);
   676       }
   677 #endif //XP_WIN
   678     }
   679     catch (e) {
   680        LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
   681       // No write privileges to install directory
   682       submitHasPermissionsTelemetryPing(false);
   683       return false;
   684     }
   685   } // if (!useService)
   687   LOG("gCanApplyUpdates - able to apply updates");
   688   submitHasPermissionsTelemetryPing(true);
   689   return true;
   690 });
   692 /**
   693  * Whether or not the application can stage an update.
   694  *
   695  * @return true if updates can be staged.
   696  */
   697 function getCanStageUpdates() {
   698   // If background updates are disabled, then just bail out!
   699   if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
   700     LOG("getCanStageUpdates - staging updates is disabled by preference " +
   701         PREF_APP_UPDATE_STAGING_ENABLED);
   702     return false;
   703   }
   705 #ifdef CHECK_CAN_USE_SERVICE
   706   if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
   707     // No need to perform directory write checks, the maintenance service will
   708     // be able to write to all directories.
   709     LOG("getCanStageUpdates - able to stage updates because we'll use the service");
   710     return true;
   711   }
   712 #endif
   714   if (!hasUpdateMutex()) {
   715     LOG("getCanStageUpdates - unable to apply updates because another " +
   716         "instance of the application is already handling updates for this " +
   717         "installation.");
   718     return false;
   719   }
   721   /**
   722    * Whether or not the application can stage an update for the current session.
   723    * These checks are only performed once per session due to using a lazy getter.
   724    *
   725    * @return true if updates can be staged for this session.
   726    */
   727   XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() {
   728     try {
   729       var updateTestFile = getInstallDirRoot();
   730       updateTestFile.append(FILE_PERMS_TEST);
   731       LOG("canStageUpdatesSession - testing write access " +
   732           updateTestFile.path);
   733       testWriteAccess(updateTestFile, true);
   734 #ifndef XP_MACOSX
   735       // On all platforms except Mac, we need to test the parent directory as
   736       // well, as we need to be able to move files in that directory during the
   737       // replacing step.
   738       updateTestFile = getInstallDirRoot().parent;
   739       updateTestFile.append(FILE_PERMS_TEST);
   740       LOG("canStageUpdatesSession - testing write access " +
   741           updateTestFile.path);
   742       updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
   743                                   FileUtils.PERMS_DIRECTORY);
   744       updateTestFile.remove(false);
   745 #endif
   746     }
   747     catch (e) {
   748        LOG("canStageUpdatesSession - unable to stage updates. Exception: " +
   749            e);
   750       // No write privileges
   751       return false;
   752     }
   754     LOG("canStageUpdatesSession - able to stage updates");
   755     return true;
   756   });
   758   return canStageUpdatesSession;
   759 }
   761 XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() {
   762 #ifdef XP_WIN
   763 #ifdef MOZ_METRO
   764   if (Services.metro && Services.metro.immersive) {
   765     let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
   766     if (!metroUpdate) {
   767       LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
   768           "updates, disabled by pref");
   769       return false;
   770     }
   771   }
   772 #endif
   773 #endif
   775   return true;
   776 });
   778 XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
   779   // If the administrator has disabled app update and locked the preference so
   780   // users can't check for updates. This preference check is ok in this lazy
   781   // getter since locked prefs don't change until the application is restarted.
   782   var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
   783   if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
   784     LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
   785         "the preference is disabled and admistratively locked.");
   786     return false;
   787   }
   789   if (!gMetroUpdatesEnabled) {
   790     return false;
   791   }
   793   // If we don't know the binary platform we're updating, we can't update.
   794   if (!gABI) {
   795     LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
   796     return false;
   797   }
   799   // If we don't know the OS version we're updating, we can't update.
   800   if (!gOSVersion) {
   801     LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
   802         "version");
   803     return false;
   804   }
   806   LOG("gCanCheckForUpdates - able to check for updates");
   807   return true;
   808 });
   810 /**
   811  * Logs a string to the error console.
   812  * @param   string
   813  *          The string to write to the error console.
   814  */
   815 function LOG(string) {
   816   if (gLogEnabled) {
   817     dump("*** AUS:SVC " + string + "\n");
   818     Services.console.logStringMessage("AUS:SVC " + string);
   819   }
   820 }
   822 /**
   823 #  Gets a preference value, handling the case where there is no default.
   824 #  @param   func
   825 #           The name of the preference function to call, on nsIPrefBranch
   826 #  @param   preference
   827 #           The name of the preference
   828 #  @param   defaultValue
   829 #           The default value to return in the event the preference has
   830 #           no setting
   831 #  @return  The value of the preference, or undefined if there was no
   832 #           user or default value.
   833  */
   834 function getPref(func, preference, defaultValue) {
   835   try {
   836     return Services.prefs[func](preference);
   837   }
   838   catch (e) {
   839   }
   840   return defaultValue;
   841 }
   843 /**
   844  * Convert a string containing binary values to hex.
   845  */
   846 function binaryToHex(input) {
   847   var result = "";
   848   for (var i = 0; i < input.length; ++i) {
   849     var hex = input.charCodeAt(i).toString(16);
   850     if (hex.length == 1)
   851       hex = "0" + hex;
   852     result += hex;
   853   }
   854   return result;
   855 }
   857 /**
   858 #  Gets the specified directory at the specified hierarchy under the
   859 #  update root directory and creates it if it doesn't exist.
   860 #  @param   pathArray
   861 #           An array of path components to locate beneath the directory
   862 #           specified by |key|
   863 #  @return  nsIFile object for the location specified.
   864  */
   865 function getUpdateDirCreate(pathArray) {
   866   return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
   867 }
   869 /**
   870 #  Gets the specified directory at the specified hierarchy under the
   871 #  update root directory and without creating it if it doesn't exist.
   872 #  @param   pathArray
   873 #           An array of path components to locate beneath the directory
   874 #           specified by |key|
   875 #  @return  nsIFile object for the location specified.
   876  */
   877 function getUpdateDirNoCreate(pathArray) {
   878   return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
   879 }
   881 /**
   882  * Gets the application base directory.
   883  *
   884  * @return  nsIFile object for the application base directory.
   885  */
   886 function getAppBaseDir() {
   887   return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
   888 }
   890 /**
   891  * Gets the root of the installation directory which is the application
   892  * bundle directory on Mac OS X and the location of the application binary
   893  * on all other platforms.
   894  *
   895  * @return nsIFile object for the directory
   896  */
   897 function getInstallDirRoot() {
   898   var dir = getAppBaseDir();
   899 #ifdef XP_MACOSX
   900   // On Mac, we store the Updated.app directory inside the bundle directory.
   901   dir = dir.parent.parent;
   902 #endif
   903   return dir;
   904 }
   906 /**
   907  * Gets the file at the specified hierarchy under the update root directory.
   908  * @param   pathArray
   909  *          An array of path components to locate beneath the directory
   910  *          specified by |key|. The last item in this array must be the
   911  *          leaf name of a file.
   912  * @return  nsIFile object for the file specified. The file is NOT created
   913  *          if it does not exist, however all required directories along
   914  *          the way are.
   915  */
   916 function getUpdateFile(pathArray) {
   917   var file = getUpdateDirCreate(pathArray.slice(0, -1));
   918   file.append(pathArray[pathArray.length - 1]);
   919   return file;
   920 }
   922 /**
   923  * Returns human readable status text from the updates.properties bundle
   924  * based on an error code
   925  * @param   code
   926  *          The error code to look up human readable status text for
   927  * @param   defaultCode
   928  *          The default code to look up should human readable status text
   929  *          not exist for |code|
   930  * @return  A human readable status text string
   931  */
   932 function getStatusTextFromCode(code, defaultCode) {
   933   var reason;
   934   try {
   935     reason = gUpdateBundle.GetStringFromName("check_error-" + code);
   936     LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
   937         code);
   938   }
   939   catch (e) {
   940     // Use the default reason
   941     reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
   942     LOG("getStatusTextFromCode - transfer error: " + reason +
   943         ", default code: " + defaultCode);
   944   }
   945   return reason;
   946 }
   948 /**
   949  * Get the Active Updates directory
   950  * @return The active updates directory, as a nsIFile object
   951  */
   952 function getUpdatesDir() {
   953   // Right now, we only support downloading one patch at a time, so we always
   954   // use the same target directory.
   955   return getUpdateDirCreate([DIR_UPDATES, "0"]);
   956 }
   958 /**
   959  * Get the Active Updates directory inside the directory where we apply the
   960  * background updates.
   961  * @return The active updates directory inside the updated directory, as a
   962  *         nsIFile object.
   963  */
   964 function getUpdatesDirInApplyToDir() {
   965   var dir = getAppBaseDir();
   966 #ifdef XP_MACOSX
   967   dir = dir.parent.parent; // the bundle directory
   968 #endif
   969   dir.append(UPDATED_DIR);
   970 #ifdef XP_MACOSX
   971   dir.append("Contents");
   972   dir.append("MacOS");
   973 #endif
   974   dir.append(DIR_UPDATES);
   975   if (!dir.exists()) {
   976     dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   977   }
   978   return dir;
   979 }
   981 /**
   982  * Reads the update state from the update.status file in the specified
   983  * directory.
   984  * @param   dir
   985  *          The dir to look for an update.status file in
   986  * @return  The status value of the update.
   987  */
   988 function readStatusFile(dir) {
   989   var statusFile = dir.clone();
   990   statusFile.append(FILE_UPDATE_STATUS);
   991   var status = readStringFromFile(statusFile) || STATE_NONE;
   992   LOG("readStatusFile - status: " + status + ", path: " + statusFile.path);
   993   return status;
   994 }
   996 /**
   997  * Writes the current update operation/state to a file in the patch
   998  * directory, indicating to the patching system that operations need
   999  * to be performed.
  1000  * @param   dir
  1001  *          The patch directory where the update.status file should be
  1002  *          written.
  1003  * @param   state
  1004  *          The state value to write.
  1005  */
  1006 function writeStatusFile(dir, state) {
  1007   var statusFile = dir.clone();
  1008   statusFile.append(FILE_UPDATE_STATUS);
  1009   writeStringToFile(statusFile, state);
  1012 #ifdef MOZ_WIDGET_GONK
  1013 /**
  1014  * Reads the link file specified in the update.link file in the
  1015  * specified directory and returns the nsIFile for the
  1016  * corresponding file.
  1017  * @param   dir
  1018  *          The dir to look for an update.link file in
  1019  * @return  A nsIFile for the file path specified in the
  1020  *          update.link file or null if the update.link file
  1021  *          doesn't exist.
  1022  */
  1023 function getFileFromUpdateLink(dir) {
  1024   var linkFile = dir.clone();
  1025   linkFile.append(FILE_UPDATE_LINK);
  1026   var link = readStringFromFile(linkFile);
  1027   LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link);
  1028   if (!link) {
  1029     return null;
  1031   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  1032   file.initWithPath(link);
  1033   return file;
  1036 /**
  1037  * Creates a link file, which allows the actual patch to live in
  1038  * a directory different from the update directory.
  1039  * @param   dir
  1040  *          The patch directory where the update.link file
  1041  *          should be written.
  1042  * @param   patchFile
  1043  *          The fully qualified filename of the patchfile.
  1044  */
  1045 function writeLinkFile(dir, patchFile) {
  1046   var linkFile = dir.clone();
  1047   linkFile.append(FILE_UPDATE_LINK);
  1048   writeStringToFile(linkFile, patchFile.path);
  1049   if (patchFile.path.indexOf(gExtStorage) == 0) {
  1050     // The patchfile is being stored on external storage. Try to lock it
  1051     // so that it doesn't get shared with the PC while we're downloading
  1052     // to it.
  1053     acquireSDCardMountLock();
  1057 /**
  1058  * Acquires a VolumeMountLock for the sdcard volume.
  1060  * This prevents the SDCard from being shared with the PC while
  1061  * we're downloading the update.
  1062  */
  1063 function acquireSDCardMountLock() {
  1064   let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
  1065                     getService(Ci.nsIVolumeService);
  1066   if (volsvc) {
  1067     gSDCardMountLock = volsvc.createMountLock("sdcard");
  1071 /**
  1072  * Determines if the state corresponds to an interrupted update.
  1073  * This could either be because the download was interrupted, or
  1074  * because staging the update was interrupted.
  1076  * @return true if the state corresponds to an interrupted
  1077  *         update.
  1078  */
  1079 function isInterruptedUpdate(status) {
  1080   return (status == STATE_DOWNLOADING) ||
  1081          (status == STATE_PENDING) ||
  1082          (status == STATE_APPLYING);
  1084 #endif // MOZ_WIDGET_GONK
  1086 /**
  1087  * Releases any SDCard mount lock that we might have.
  1089  * This once again allows the SDCard to be shared with the PC.
  1091  * This function was placed outside the #ifdef so that we didn't
  1092  * need to put #ifdefs around the callers.
  1093  */
  1094 function releaseSDCardMountLock() {
  1095 #ifdef MOZ_WIDGET_GONK
  1096   if (gSDCardMountLock) {
  1097     gSDCardMountLock.unlock();
  1098     gSDCardMountLock = null;
  1100 #endif
  1103 /**
  1104  * Determines if the service should be used to attempt an update
  1105  * or not.  For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED
  1106  * is true and we have Firefox.
  1108  * @return  true if the service should be used for updates.
  1109  */
  1110 function shouldUseService() {
  1111 #ifdef MOZ_MAINTENANCE_SERVICE
  1112   return getPref("getBoolPref",
  1113                  PREF_APP_UPDATE_SERVICE_ENABLED, false);
  1114 #else
  1115   return false;
  1116 #endif
  1119 /**
  1120  * Determines if the service is is installed and enabled or not.
  1122  * @return  true if the service should be used for updates,
  1123  *          is installed and enabled.
  1124  */
  1125 function isServiceInstalled() {
  1126 #ifdef XP_WIN
  1127   let installed = 0;
  1128   try {
  1129     let wrk = Cc["@mozilla.org/windows-registry-key;1"].
  1130               createInstance(Ci.nsIWindowsRegKey);
  1131     wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
  1132              "SOFTWARE\\Mozilla\\MaintenanceService",
  1133              wrk.ACCESS_READ | wrk.WOW64_64);
  1134     installed = wrk.readIntValue("Installed");
  1135     wrk.close();
  1136   } catch(e) {
  1138   installed = installed == 1;  // convert to bool
  1139   LOG("isServiceInstalled = " + installed);
  1140   return installed;
  1141 #else
  1142   return false;
  1143 #endif
  1146 /**
  1147 #  Writes the update's application version to a file in the patch directory. If
  1148 #  the update doesn't provide application version information via the
  1149 #  appVersion attribute the string "null" will be written to the file.
  1150 #  This value is compared during startup (in nsUpdateDriver.cpp) to determine if
  1151 #  the update should be applied. Note that this won't provide protection from
  1152 #  downgrade of the application for the nightly user case where the application
  1153 #  version doesn't change.
  1154 #  @param   dir
  1155 #           The patch directory where the update.version file should be
  1156 #           written.
  1157 #  @param   version
  1158 #           The version value to write. Will be the string "null" when the
  1159 #           update doesn't provide the appVersion attribute in the update xml.
  1160  */
  1161 function writeVersionFile(dir, version) {
  1162   var versionFile = dir.clone();
  1163   versionFile.append(FILE_UPDATE_VERSION);
  1164   writeStringToFile(versionFile, version);
  1167 /**
  1168  * Removes the MozUpdater folders that bgupdates/staged updates creates.
  1169  */
  1170 function cleanUpMozUpdaterDirs() {
  1171   try {
  1172     var tmpDir = Cc["@mozilla.org/file/directory_service;1"].
  1173                  getService(Ci.nsIProperties).
  1174                  get("TmpD", Ci.nsIFile);
  1176     // We used to store MozUpdater-i folders directly inside the temp directory.
  1177     // We need to cleanup these directories if we detect that they still exist.
  1178     // To check if they still exist, we simply check for MozUpdater-1.
  1179     var mozUpdaterDir1 = tmpDir.clone();
  1180     mozUpdaterDir1.append("MozUpdater-1");
  1181     // Only try to delete the left over folders in "$Temp/MozUpdater-i/*" if
  1182     // MozUpdater-1 exists.
  1183     if (mozUpdaterDir1.exists()) {
  1184       LOG("cleanUpMozUpdaterDirs - Cleaning top level MozUpdater-i folders");
  1185       let i = 0;
  1186       let dirEntries = tmpDir.directoryEntries;
  1187       while (dirEntries.hasMoreElements() && i < 10) {
  1188         let file = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
  1189         if (file.leafName.startsWith("MozUpdater-") && file.leafName != "MozUpdater-1") {
  1190           file.remove(true);
  1191           i++;
  1194       // If you enumerate the whole temp directory and the count of deleted
  1195       // items is less than 10, then delete MozUpdate-1.
  1196       if (i < 10) {
  1197         mozUpdaterDir1.remove(true);
  1201     // If we reach here, we simply need to clean the MozUpdater folder.  In our
  1202     // new way of storing these files, the unique subfolders are inside MozUpdater
  1203     var mozUpdaterDir = tmpDir.clone();
  1204     mozUpdaterDir.append("MozUpdater");
  1205     if (mozUpdaterDir.exists()) {
  1206       LOG("cleanUpMozUpdaterDirs - Cleaning MozUpdater folder");
  1207       mozUpdaterDir.remove(true);
  1209   } catch (e) {
  1210     LOG("cleanUpMozUpdaterDirs - Exception: " + e);
  1214 /**
  1215  * Removes the contents of the Updates Directory
  1217  * @param aBackgroundUpdate Whether the update has been performed in the
  1218  *        background.  If this is true, we move the update log file to the
  1219  *        updated directory, so that it survives replacing the directories
  1220  *        later on.
  1221  */
  1222 function cleanUpUpdatesDir(aBackgroundUpdate) {
  1223   // Bail out if we don't have appropriate permissions
  1224   try {
  1225     var updateDir = getUpdatesDir();
  1226   } catch (e) {
  1227     return;
  1230   // Preserve the last update log file for debugging purposes.
  1231   let file = updateDir.clone();
  1232   file.append(FILE_UPDATE_LOG);
  1233   if (file.exists()) {
  1234     let dir;
  1235     if (aBackgroundUpdate && getUpdateDirNoCreate([]).equals(getAppBaseDir())) {
  1236       dir = getUpdatesDirInApplyToDir();
  1237     } else {
  1238       dir = updateDir.parent;
  1240     let logFile = dir.clone();
  1241     logFile.append(FILE_LAST_LOG);
  1242     if (logFile.exists()) {
  1243       try {
  1244         logFile.moveTo(dir, FILE_BACKUP_LOG);
  1245       } catch (e) {
  1246         LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
  1247             " to " + FILE_BACKUP_LOG);
  1251     try {
  1252       file.moveTo(dir, FILE_LAST_LOG);
  1253     } catch (e) {
  1254       LOG("cleanUpUpdatesDir - failed to rename file " + file.path +
  1255           " to " + FILE_LAST_LOG);
  1259   if (!aBackgroundUpdate) {
  1260     let e = updateDir.directoryEntries;
  1261     while (e.hasMoreElements()) {
  1262       let f = e.getNext().QueryInterface(Ci.nsIFile);
  1263 #ifdef MOZ_WIDGET_GONK
  1264       if (f.leafName == FILE_UPDATE_LINK) {
  1265         let linkedFile = getFileFromUpdateLink(updateDir);
  1266         if (linkedFile && linkedFile.exists()) {
  1267           linkedFile.remove(false);
  1270 #endif
  1272       // Now, recursively remove this file.  The recursive removal is needed for
  1273       // Mac OSX because this directory will contain a copy of updater.app,
  1274       // which is itself a directory.
  1275       try {
  1276         f.remove(true);
  1277       } catch (e) {
  1278         LOG("cleanUpUpdatesDir - failed to remove file " + f.path);
  1282   releaseSDCardMountLock();
  1285 /**
  1286  * Clean up updates list and the updates directory.
  1287  */
  1288 function cleanupActiveUpdate() {
  1289   // Move the update from the Active Update list into the Past Updates list.
  1290   var um = Cc["@mozilla.org/updates/update-manager;1"].
  1291            getService(Ci.nsIUpdateManager);
  1292   um.activeUpdate = null;
  1293   um.saveUpdates();
  1295   // Now trash the updates directory, since we're done with it
  1296   cleanUpUpdatesDir();
  1299 /**
  1300  * Gets the locale from the update.locale file for replacing %LOCALE% in the
  1301  * update url. The update.locale file can be located in the application
  1302  * directory or the GRE directory with preference given to it being located in
  1303  * the application directory.
  1304  */
  1305 function getLocale() {
  1306   if (gLocale)
  1307     return gLocale;
  1309   for (let res of ['app', 'gre']) {
  1310     var channel = Services.io.newChannel("resource://" + res + "/" + FILE_UPDATE_LOCALE, null, null);
  1311     try {
  1312       var inputStream = channel.open();
  1313       gLocale = readStringFromInputStream(inputStream);
  1314     } catch(e) {}
  1315     if (gLocale)
  1316       break;
  1319   if (!gLocale)
  1320     throw Components.Exception(FILE_UPDATE_LOCALE + " file doesn't exist in " +
  1321                                "either the application or GRE directories",
  1322                                Cr.NS_ERROR_FILE_NOT_FOUND);
  1324   LOG("getLocale - getting locale from file: " + channel.originalURI.spec +
  1325       ", locale: " + gLocale);
  1326   return gLocale;
  1329 /* Get the distribution pref values, from defaults only */
  1330 function getDistributionPrefValue(aPrefName) {
  1331   var prefValue = "default";
  1333   try {
  1334     prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
  1335   } catch (e) {
  1336     // use default when pref not found
  1339   return prefValue;
  1342 /**
  1343  * An enumeration of items in a JS array.
  1344  * @constructor
  1345  */
  1346 function ArrayEnumerator(aItems) {
  1347   this._index = 0;
  1348   if (aItems) {
  1349     for (var i = 0; i < aItems.length; ++i) {
  1350       if (!aItems[i])
  1351         aItems.splice(i, 1);
  1354   this._contents = aItems;
  1357 ArrayEnumerator.prototype = {
  1358   _index: 0,
  1359   _contents: [],
  1361   hasMoreElements: function ArrayEnumerator_hasMoreElements() {
  1362     return this._index < this._contents.length;
  1363   },
  1365   getNext: function ArrayEnumerator_getNext() {
  1366     return this._contents[this._index++];
  1368 };
  1370 /**
  1371  * Writes a string of text to a file.  A newline will be appended to the data
  1372  * written to the file.  This function only works with ASCII text.
  1373  */
  1374 function writeStringToFile(file, text) {
  1375   var fos = FileUtils.openSafeFileOutputStream(file)
  1376   text += "\n";
  1377   fos.write(text, text.length);
  1378   FileUtils.closeSafeFileOutputStream(fos);
  1381 function readStringFromInputStream(inputStream) {
  1382   var sis = Cc["@mozilla.org/scriptableinputstream;1"].
  1383             createInstance(Ci.nsIScriptableInputStream);
  1384   sis.init(inputStream);
  1385   var text = sis.read(sis.available());
  1386   sis.close();
  1387   if (text[text.length - 1] == "\n")
  1388     text = text.slice(0, -1);
  1389   return text;
  1392 /**
  1393  * Reads a string of text from a file.  A trailing newline will be removed
  1394  * before the result is returned.  This function only works with ASCII text.
  1395  */
  1396 function readStringFromFile(file) {
  1397   if (!file.exists()) {
  1398     LOG("readStringFromFile - file doesn't exist: " + file.path);
  1399     return null;
  1401   var fis = Cc["@mozilla.org/network/file-input-stream;1"].
  1402             createInstance(Ci.nsIFileInputStream);
  1403   fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
  1404   return readStringFromInputStream(fis);
  1407 function handleUpdateFailure(update, errorCode) {
  1408   update.errorCode = parseInt(errorCode);
  1409   if (update.errorCode == FOTA_GENERAL_ERROR ||
  1410       update.errorCode == FOTA_FILE_OPERATION_ERROR ||
  1411       update.errorCode == FOTA_RECOVERY_ERROR ||
  1412       update.errorCode == FOTA_UNKNOWN_ERROR) {
  1413     // In the case of FOTA update errors, don't reset the state to pending. This
  1414     // causes the FOTA update path to try again, which is not necessarily what
  1415     // we want.
  1416     update.statusText = gUpdateBundle.GetStringFromName("statusFailed");
  1418     Cc["@mozilla.org/updates/update-prompt;1"].
  1419       createInstance(Ci.nsIUpdatePrompt).
  1420       showUpdateError(update);
  1421     writeStatusFile(getUpdatesDir(), STATE_FAILED + ": " + errorCode);
  1422     cleanupActiveUpdate();
  1423     return true;
  1426   if (update.errorCode == WRITE_ERROR ||
  1427       update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
  1428       update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
  1429       update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
  1430       update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
  1431       update.errorCode == WRITE_ERROR_CALLBACK_APP ||
  1432       update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) {
  1433     Cc["@mozilla.org/updates/update-prompt;1"].
  1434       createInstance(Ci.nsIUpdatePrompt).
  1435       showUpdateError(update);
  1436     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
  1437     return true;
  1440   if (update.errorCode == ELEVATION_CANCELED) {
  1441     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
  1442     return true;
  1445   if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
  1446       update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
  1447       update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
  1448       update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
  1449       update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR ||
  1450       update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS ||
  1451       update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE ||
  1452       update.errorCode == SERVICE_UPDATER_NOT_FIXED_DRIVE ||
  1453       update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER ||
  1454       update.errorCode == SERVICE_COULD_NOT_COPY_UPDATER ||
  1455       update.errorCode == SERVICE_INSTALLDIR_ERROR) {
  1457     var failCount = getPref("getIntPref",
  1458                             PREF_APP_UPDATE_SERVICE_ERRORS, 0);
  1459     var maxFail = getPref("getIntPref",
  1460                           PREF_APP_UPDATE_SERVICE_MAX_ERRORS,
  1461                           DEFAULT_SERVICE_MAX_ERRORS);
  1463     // As a safety, when the service reaches maximum failures, it will
  1464     // disable itself and fallback to using the normal update mechanism
  1465     // without the service.
  1466     if (failCount >= maxFail) {
  1467       Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
  1468       Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
  1469     } else {
  1470       failCount++;
  1471       Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
  1472                                 failCount);
  1475     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
  1476     try {
  1477       Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").
  1478         add(update.errorCode);
  1480     catch (e) {
  1481       Cu.reportError(e);
  1483     return true;
  1486   try {
  1487     Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").add(0);
  1489   catch (e) {
  1490     Cu.reportError(e);
  1492   return false;
  1495 /**
  1496  * Fall back to downloading a complete update in case an update has failed.
  1498  * @param update the update object that has failed to apply.
  1499  * @param postStaging true if we have just attempted to stage an update.
  1500  */
  1501 function handleFallbackToCompleteUpdate(update, postStaging) {
  1502   cleanupActiveUpdate();
  1504   update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
  1505   var oldType = update.selectedPatch ? update.selectedPatch.type
  1506                                      : "complete";
  1507   if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) {
  1508     // Partial patch application failed, try downloading the complete
  1509     // update in the background instead.
  1510     LOG("handleFallbackToCompleteUpdate - install of partial patch " +
  1511         "failed, downloading complete patch");
  1512     var status = Cc["@mozilla.org/updates/update-service;1"].
  1513                  getService(Ci.nsIApplicationUpdateService).
  1514                  downloadUpdate(update, !postStaging);
  1515     if (status == STATE_NONE)
  1516       cleanupActiveUpdate();
  1518   else {
  1519     LOG("handleFallbackToCompleteUpdate - install of complete or " +
  1520         "only one patch offered failed.");
  1522   update.QueryInterface(Ci.nsIWritablePropertyBag);
  1523   update.setProperty("patchingFailed", oldType);
  1526 /**
  1527  * Update Patch
  1528  * @param   patch
  1529  *          A <patch> element to initialize this object with
  1530  * @throws if patch has a size of 0
  1531  * @constructor
  1532  */
  1533 function UpdatePatch(patch) {
  1534   this._properties = {};
  1535   for (var i = 0; i < patch.attributes.length; ++i) {
  1536     var attr = patch.attributes.item(i);
  1537     attr.QueryInterface(Ci.nsIDOMAttr);
  1538     switch (attr.name) {
  1539     case "selected":
  1540       this.selected = attr.value == "true";
  1541       break;
  1542     case "size":
  1543       if (0 == parseInt(attr.value)) {
  1544         LOG("UpdatePatch:init - 0-sized patch!");
  1545         throw Cr.NS_ERROR_ILLEGAL_VALUE;
  1547       // fall through
  1548     default:
  1549       this[attr.name] = attr.value;
  1550       break;
  1551     };
  1554 UpdatePatch.prototype = {
  1555   /**
  1556    * See nsIUpdateService.idl
  1557    */
  1558   serialize: function UpdatePatch_serialize(updates) {
  1559     var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
  1560     patch.setAttribute("type", this.type);
  1561     patch.setAttribute("URL", this.URL);
  1562     // finalURL is not available until after the download has started
  1563     if (this.finalURL)
  1564       patch.setAttribute("finalURL", this.finalURL);
  1565     patch.setAttribute("hashFunction", this.hashFunction);
  1566     patch.setAttribute("hashValue", this.hashValue);
  1567     patch.setAttribute("size", this.size);
  1568     patch.setAttribute("selected", this.selected);
  1569     patch.setAttribute("state", this.state);
  1571     for (var p in this._properties) {
  1572       if (this._properties[p].present)
  1573         patch.setAttribute(p, this._properties[p].data);
  1576     return patch;
  1577   },
  1579   /**
  1580    * A hash of custom properties
  1581    */
  1582   _properties: null,
  1584   /**
  1585    * See nsIWritablePropertyBag.idl
  1586    */
  1587   setProperty: function UpdatePatch_setProperty(name, value) {
  1588     this._properties[name] = { data: value, present: true };
  1589   },
  1591   /**
  1592    * See nsIWritablePropertyBag.idl
  1593    */
  1594   deleteProperty: function UpdatePatch_deleteProperty(name) {
  1595     if (name in this._properties)
  1596       this._properties[name].present = false;
  1597     else
  1598       throw Cr.NS_ERROR_FAILURE;
  1599   },
  1601   /**
  1602    * See nsIPropertyBag.idl
  1603    */
  1604   get enumerator() {
  1605     var properties = [];
  1606     for (var p in this._properties)
  1607       properties.push(this._properties[p].data);
  1608     return new ArrayEnumerator(properties);
  1609   },
  1611   /**
  1612    * See nsIPropertyBag.idl
  1613    */
  1614   getProperty: function UpdatePatch_getProperty(name) {
  1615     if (name in this._properties &&
  1616         this._properties[name].present)
  1617       return this._properties[name].data;
  1618     throw Cr.NS_ERROR_FAILURE;
  1619   },
  1621   /**
  1622    * Returns whether or not the update.status file for this patch exists at the
  1623    * appropriate location.
  1624    */
  1625   get statusFileExists() {
  1626     var statusFile = getUpdatesDir();
  1627     statusFile.append(FILE_UPDATE_STATUS);
  1628     return statusFile.exists();
  1629   },
  1631   /**
  1632    * See nsIUpdateService.idl
  1633    */
  1634   get state() {
  1635     if (this._properties.state)
  1636       return this._properties.state;
  1637     return STATE_NONE;
  1638   },
  1639   set state(val) {
  1640     this._properties.state = val;
  1641   },
  1643   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
  1644                                          Ci.nsIPropertyBag,
  1645                                          Ci.nsIWritablePropertyBag])
  1646 };
  1648 /**
  1649  * Update
  1650  * Implements nsIUpdate
  1651  * @param   update
  1652  *          An <update> element to initialize this object with
  1653  * @throws if the update contains no patches
  1654  * @constructor
  1655  */
  1656 function Update(update) {
  1657   this._properties = {};
  1658   this._patches = [];
  1659   this.isCompleteUpdate = false;
  1660   this.isOSUpdate = false;
  1661   this.showPrompt = false;
  1662   this.showNeverForVersion = false;
  1663   this.unsupported = false;
  1664   this.channel = "default";
  1665   this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
  1667   // Null <update>, assume this is a message container and do no
  1668   // further initialization
  1669   if (!update)
  1670     return;
  1672   const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
  1673   for (var i = 0; i < update.childNodes.length; ++i) {
  1674     var patchElement = update.childNodes.item(i);
  1675     if (patchElement.nodeType != ELEMENT_NODE ||
  1676         patchElement.localName != "patch")
  1677       continue;
  1679     patchElement.QueryInterface(Ci.nsIDOMElement);
  1680     try {
  1681       var patch = new UpdatePatch(patchElement);
  1682     } catch (e) {
  1683       continue;
  1685     this._patches.push(patch);
  1688   if (update.hasAttribute("unsupported"))
  1689     this.unsupported = ("true" == update.getAttribute("unsupported"));
  1690   else if (update.hasAttribute("minSupportedOSVersion")) {
  1691     let minOSVersion = update.getAttribute("minSupportedOSVersion");
  1692     try {
  1693       let osVersion = Services.sysinfo.getProperty("version");
  1694       this.unsupported = (Services.vc.compare(osVersion, minOSVersion) < 0);
  1695     } catch (e) {}
  1698   if (this._patches.length == 0 && !this.unsupported)
  1699     throw Cr.NS_ERROR_ILLEGAL_VALUE;
  1701   // Fallback to the behavior prior to bug 530872 if the update does not have an
  1702   // appVersion attribute.
  1703   if (!update.hasAttribute("appVersion")) {
  1704     if (update.getAttribute("type") == "major") {
  1705       if (update.hasAttribute("detailsURL")) {
  1706         this.billboardURL = update.getAttribute("detailsURL");
  1707         this.showPrompt = true;
  1708         this.showNeverForVersion = true;
  1713   for (var i = 0; i < update.attributes.length; ++i) {
  1714     var attr = update.attributes.item(i);
  1715     attr.QueryInterface(Ci.nsIDOMAttr);
  1716     if (attr.value == "undefined")
  1717       continue;
  1718     else if (attr.name == "detailsURL")
  1719       this._detailsURL = attr.value;
  1720     else if (attr.name == "extensionVersion") {
  1721       // Prevent extensionVersion from replacing appVersion if appVersion is
  1722       // present in the update xml.
  1723       if (!this.appVersion)
  1724         this.appVersion = attr.value;
  1726     else if (attr.name == "installDate" && attr.value)
  1727       this.installDate = parseInt(attr.value);
  1728     else if (attr.name == "isCompleteUpdate")
  1729       this.isCompleteUpdate = attr.value == "true";
  1730     else if (attr.name == "isSecurityUpdate")
  1731       this.isSecurityUpdate = attr.value == "true";
  1732     else if (attr.name == "isOSUpdate")
  1733       this.isOSUpdate = attr.value == "true";
  1734     else if (attr.name == "showNeverForVersion")
  1735       this.showNeverForVersion = attr.value == "true";
  1736     else if (attr.name == "showPrompt")
  1737       this.showPrompt = attr.value == "true";
  1738     else if (attr.name == "promptWaitTime")
  1740       if(!isNaN(attr.value))
  1741         this.promptWaitTime = parseInt(attr.value);
  1743     else if (attr.name == "version") {
  1744       // Prevent version from replacing displayVersion if displayVersion is
  1745       // present in the update xml.
  1746       if (!this.displayVersion)
  1747         this.displayVersion = attr.value;
  1749     else if (attr.name != "unsupported") {
  1750       this[attr.name] = attr.value;
  1752       switch (attr.name) {
  1753       case "appVersion":
  1754       case "billboardURL":
  1755       case "buildID":
  1756       case "channel":
  1757       case "displayVersion":
  1758       case "licenseURL":
  1759       case "name":
  1760       case "platformVersion":
  1761       case "previousAppVersion":
  1762       case "serviceURL":
  1763       case "statusText":
  1764       case "type":
  1765         break;
  1766       default:
  1767         // Save custom attributes when serializing to the local xml file but
  1768         // don't use this method for the expected attributes which are already
  1769         // handled in serialize.
  1770         this.setProperty(attr.name, attr.value);
  1771         break;
  1772       };
  1776   // Set the initial value with the current time when it doesn't already have a
  1777   // value or the value is already set to 0 (bug 316328).
  1778   if (!this.installDate && this.installDate != 0)
  1779     this.installDate = (new Date()).getTime();
  1781   // The Update Name is either the string provided by the <update> element, or
  1782   // the string: "<App Name> <Update App Version>"
  1783   var name = "";
  1784   if (update.hasAttribute("name"))
  1785     name = update.getAttribute("name");
  1786   else {
  1787     var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
  1788     var appName = brandBundle.GetStringFromName("brandShortName");
  1789     name = gUpdateBundle.formatStringFromName("updateName",
  1790                                               [appName, this.displayVersion], 2);
  1792   this.name = name;
  1794 Update.prototype = {
  1795   /**
  1796    * See nsIUpdateService.idl
  1797    */
  1798   get patchCount() {
  1799     return this._patches.length;
  1800   },
  1802   /**
  1803    * See nsIUpdateService.idl
  1804    */
  1805   getPatchAt: function Update_getPatchAt(index) {
  1806     return this._patches[index];
  1807   },
  1809   /**
  1810    * See nsIUpdateService.idl
  1812    * We use a copy of the state cached on this object in |_state| only when
  1813    * there is no selected patch, i.e. in the case when we could not load
  1814    * |.activeUpdate| from the update manager for some reason but still have
  1815    * the update.status file to work with.
  1816    */
  1817   _state: "",
  1818   set state(state) {
  1819     if (this.selectedPatch)
  1820       this.selectedPatch.state = state;
  1821     this._state = state;
  1822     return state;
  1823   },
  1824   get state() {
  1825     if (this.selectedPatch)
  1826       return this.selectedPatch.state;
  1827     return this._state;
  1828   },
  1830   /**
  1831    * See nsIUpdateService.idl
  1832    */
  1833   errorCode: 0,
  1835   /**
  1836    * See nsIUpdateService.idl
  1837    */
  1838   get selectedPatch() {
  1839     for (var i = 0; i < this.patchCount; ++i) {
  1840       if (this._patches[i].selected)
  1841         return this._patches[i];
  1843     return null;
  1844   },
  1846   /**
  1847    * See nsIUpdateService.idl
  1848    */
  1849   get detailsURL() {
  1850     if (!this._detailsURL) {
  1851       try {
  1852         // Try using a default details URL supplied by the distribution
  1853         // if the update XML does not supply one.
  1854         return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
  1856       catch (e) {
  1859     return this._detailsURL || "";
  1860   },
  1862   /**
  1863    * See nsIUpdateService.idl
  1864    */
  1865   serialize: function Update_serialize(updates) {
  1866     var update = updates.createElementNS(URI_UPDATE_NS, "update");
  1867     update.setAttribute("appVersion", this.appVersion);
  1868     update.setAttribute("buildID", this.buildID);
  1869     update.setAttribute("channel", this.channel);
  1870     update.setAttribute("displayVersion", this.displayVersion);
  1871     // for backwards compatibility in case the user downgrades
  1872     update.setAttribute("extensionVersion", this.appVersion);
  1873     update.setAttribute("installDate", this.installDate);
  1874     update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
  1875     update.setAttribute("isOSUpdate", this.isOSUpdate);
  1876     update.setAttribute("name", this.name);
  1877     update.setAttribute("serviceURL", this.serviceURL);
  1878     update.setAttribute("showNeverForVersion", this.showNeverForVersion);
  1879     update.setAttribute("showPrompt", this.showPrompt);
  1880     update.setAttribute("promptWaitTime", this.promptWaitTime);
  1881     update.setAttribute("type", this.type);
  1882     // for backwards compatibility in case the user downgrades
  1883     update.setAttribute("version", this.displayVersion);
  1885     // Optional attributes
  1886     if (this.billboardURL)
  1887       update.setAttribute("billboardURL", this.billboardURL);
  1888     if (this.detailsURL)
  1889       update.setAttribute("detailsURL", this.detailsURL);
  1890     if (this.licenseURL)
  1891       update.setAttribute("licenseURL", this.licenseURL);
  1892     if (this.platformVersion)
  1893       update.setAttribute("platformVersion", this.platformVersion);
  1894     if (this.previousAppVersion)
  1895       update.setAttribute("previousAppVersion", this.previousAppVersion);
  1896     if (this.statusText)
  1897       update.setAttribute("statusText", this.statusText);
  1898     if (this.unsupported)
  1899       update.setAttribute("unsupported", this.unsupported);
  1900     updates.documentElement.appendChild(update);
  1902     for (var p in this._properties) {
  1903       if (this._properties[p].present)
  1904         update.setAttribute(p, this._properties[p].data);
  1907     for (var i = 0; i < this.patchCount; ++i)
  1908       update.appendChild(this.getPatchAt(i).serialize(updates));
  1910     return update;
  1911   },
  1913   /**
  1914    * A hash of custom properties
  1915    */
  1916   _properties: null,
  1918   /**
  1919    * See nsIWritablePropertyBag.idl
  1920    */
  1921   setProperty: function Update_setProperty(name, value) {
  1922     this._properties[name] = { data: value, present: true };
  1923   },
  1925   /**
  1926    * See nsIWritablePropertyBag.idl
  1927    */
  1928   deleteProperty: function Update_deleteProperty(name) {
  1929     if (name in this._properties)
  1930       this._properties[name].present = false;
  1931     else
  1932       throw Cr.NS_ERROR_FAILURE;
  1933   },
  1935   /**
  1936    * See nsIPropertyBag.idl
  1937    */
  1938   get enumerator() {
  1939     var properties = [];
  1940     for (var p in this._properties)
  1941       properties.push(this._properties[p].data);
  1942     return new ArrayEnumerator(properties);
  1943   },
  1945   /**
  1946    * See nsIPropertyBag.idl
  1947    */
  1948   getProperty: function Update_getProperty(name) {
  1949     if (name in this._properties && this._properties[name].present)
  1950       return this._properties[name].data;
  1951     throw Cr.NS_ERROR_FAILURE;
  1952   },
  1954   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
  1955                                          Ci.nsIPropertyBag,
  1956                                          Ci.nsIWritablePropertyBag])
  1957 };
  1959 const UpdateServiceFactory = {
  1960   _instance: null,
  1961   createInstance: function (outer, iid) {
  1962     if (outer != null)
  1963       throw Cr.NS_ERROR_NO_AGGREGATION;
  1964     return this._instance == null ? this._instance = new UpdateService() :
  1965                                     this._instance;
  1967 };
  1969 /**
  1970  * UpdateService
  1971  * A Service for managing the discovery and installation of software updates.
  1972  * @constructor
  1973  */
  1974 function UpdateService() {
  1975   LOG("Creating UpdateService");
  1976   Services.obs.addObserver(this, "xpcom-shutdown", false);
  1977   Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this, false);
  1978 #ifdef MOZ_WIDGET_GONK
  1979   // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
  1980   // and Restart) sends the profile-change-net-teardown event. We can then
  1981   // pause the download in a similar manner to xpcom-shutdown.
  1982   Services.obs.addObserver(this, "profile-change-net-teardown", false);
  1983 #endif
  1986 UpdateService.prototype = {
  1987   /**
  1988    * The downloader we are using to download updates. There is only ever one of
  1989    * these.
  1990    */
  1991   _downloader: null,
  1993   /**
  1994    * Incompatible add-on count.
  1995    */
  1996   _incompatAddonsCount: 0,
  1998   /**
  1999    * Whether or not the service registered the "online" observer.
  2000    */
  2001   _registeredOnlineObserver: false,
  2003   /**
  2004    * The current number of consecutive socket errors
  2005    */
  2006   _consecutiveSocketErrors: 0,
  2008   /**
  2009    * A timer used to retry socket errors
  2010    */
  2011   _retryTimer: null,
  2013   /**
  2014    * Whether or not a background update check was initiated by the
  2015    * application update timer notification.
  2016    */
  2017   _isNotify: true,
  2019   /**
  2020    * Handle Observer Service notifications
  2021    * @param   subject
  2022    *          The subject of the notification
  2023    * @param   topic
  2024    *          The notification name
  2025    * @param   data
  2026    *          Additional data
  2027    */
  2028   observe: function AUS_observe(subject, topic, data) {
  2029     switch (topic) {
  2030     case "post-update-processing":
  2031       // Clean up any extant updates
  2032       this._postUpdateProcessing();
  2033       break;
  2034     case "network:offline-status-changed":
  2035       this._offlineStatusChanged(data);
  2036       break;
  2037     case "nsPref:changed":
  2038       if (data == PREF_APP_UPDATE_LOG) {
  2039         gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
  2041       break;
  2042 #ifdef MOZ_WIDGET_GONK
  2043     case "profile-change-net-teardown": // fall thru
  2044 #endif
  2045     case "xpcom-shutdown":
  2046       Services.obs.removeObserver(this, topic);
  2047       Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
  2049       if (this._retryTimer) {
  2050         this._retryTimer.cancel();
  2053       this.pauseDownload();
  2054       // Prevent leaking the downloader (bug 454964)
  2055       this._downloader = null;
  2056       break;
  2058   },
  2060   /**
  2061    * The following needs to happen during the post-update-processing
  2062    * notification from nsUpdateServiceStub.js:
  2063    * 1. post update processing
  2064    * 2. resume of a download that was in progress during a previous session
  2065    * 3. start of a complete update download after the failure to apply a partial
  2066    *    update
  2067    */
  2069   /**
  2070    * Perform post-processing on updates lingering in the updates directory
  2071    * from a previous application session - either report install failures (and
  2072    * optionally attempt to fetch a different version if appropriate) or
  2073    * notify the user of install success.
  2074    */
  2075   _postUpdateProcessing: function AUS__postUpdateProcessing() {
  2076     // canCheckForUpdates will return false when metro-only updates are disabled
  2077     // from within metro.  In that case we still want _postUpdateProcessing to
  2078     // run.  gMetroUpdatesEnabled returns true on non Windows 8 platforms.
  2079     // We want _postUpdateProcessing to run so that it will update the history
  2080     // XML. Without updating the history XML, the about flyout will continue to
  2081     // have the "Restart to Apply Update" button (history xml indicates update
  2082     // is applied).
  2083     // TODO: I think this whole if-block should be removed since updates can
  2084     // always be applied via the about dialog, we should be running post update
  2085     // in those cases.
  2086     if (!this.canCheckForUpdates && gMetroUpdatesEnabled) {
  2087       LOG("UpdateService:_postUpdateProcessing - unable to check for " +
  2088           "updates... returning early");
  2089       return;
  2092     if (!this.canApplyUpdates) {
  2093       LOG("UpdateService:_postUpdateProcessing - unable to apply " +
  2094           "updates... returning early");
  2095       // If the update is present in the update directly somehow,
  2096       // it would prevent us from notifying the user of futher updates.
  2097       cleanupActiveUpdate();
  2098       return;
  2101     var status = readStatusFile(getUpdatesDir());
  2102     // STATE_NONE status means that the update.status file is present but a
  2103     // background download error occurred.
  2104     if (status == STATE_NONE) {
  2105       LOG("UpdateService:_postUpdateProcessing - no status, no update");
  2106       cleanupActiveUpdate();
  2107       return;
  2110     var um = Cc["@mozilla.org/updates/update-manager;1"].
  2111              getService(Ci.nsIUpdateManager);
  2113 #ifdef MOZ_WIDGET_GONK
  2114     // This code is called very early in the boot process, before we've even
  2115     // had a chance to setup the UI so we can give feedback to the user.
  2116     //
  2117     // Since the download may be occuring over a link which has associated
  2118     // cost, we want to require user-consent before resuming the download.
  2119     // Also, applying an already downloaded update now is undesireable,
  2120     // since the phone will look dead while the update is being applied.
  2121     // Applying the update can take several minutes. Instead we wait until
  2122     // the UI is initialized so it is possible to give feedback to and get
  2123     // consent to update from the user.
  2124     if (isInterruptedUpdate(status)) {
  2125       LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent");
  2126       return;
  2128 #endif
  2130     var update = um.activeUpdate;
  2132     if (status == STATE_DOWNLOADING) {
  2133       LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
  2134           "state");
  2135       if (update && update.state != STATE_SUCCEEDED) {
  2136         // Resume download
  2137         var status = this.downloadUpdate(update, true);
  2138         if (status == STATE_NONE)
  2139           cleanupActiveUpdate();
  2141       return;
  2144     if (status == STATE_APPLYING) {
  2145       // This indicates that the background updater service is in either of the
  2146       // following two states:
  2147       // 1. It is in the process of applying an update in the background, and
  2148       //    we just happen to be racing against that.
  2149       // 2. It has failed to apply an update for some reason, and we hit this
  2150       //    case because the updater process has set the update status to
  2151       //    applying, but has never finished.
  2152       // In order to differentiate between these two states, we look at the
  2153       // state field of the update object.  If it's "pending", then we know
  2154       // that this is the first time we're hitting this case, so we switch
  2155       // that state to "applying" and we just wait and hope for the best.
  2156       // If it's "applying", we know that we've already been here once, so
  2157       // we really want to start from a clean state.
  2158       if (update &&
  2159           (update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) {
  2160         LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
  2161             "state for the first time");
  2162         update.state = STATE_APPLYING;
  2163         um.saveUpdates();
  2164       } else { // We get here even if we don't have an update object
  2165         LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
  2166             "state for the second time");
  2167         cleanupActiveUpdate();
  2169       return;
  2172 #ifdef MOZ_WIDGET_GONK
  2173     // The update is only applied but not selected to be installed
  2174     if (status == STATE_APPLIED && update && update.isOSUpdate) {
  2175       LOG("UpdateService:_postUpdateProcessing - update staged as applied found");
  2176       return;
  2179     if (status == STATE_APPLIED_OS && update && update.isOSUpdate) {
  2180       // In gonk, we need to check for OS update status after startup, since
  2181       // the recovery partition won't write to update.status for us
  2182       var recoveryService = Cc["@mozilla.org/recovery-service;1"].
  2183                             getService(Ci.nsIRecoveryService);
  2185       var fotaStatus = recoveryService.getFotaUpdateStatus();
  2186       switch (fotaStatus) {
  2187         case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS:
  2188           status = STATE_SUCCEEDED;
  2189           break;
  2190         case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
  2191           status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
  2192           break;
  2193         case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
  2194         default:
  2195           status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
  2196           break;
  2199 #endif
  2201     if (!update) {
  2202       if (status != STATE_SUCCEEDED) {
  2203         LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
  2204             "and no patch available");
  2205         cleanupActiveUpdate();
  2206         return;
  2208       update = new Update(null);
  2211     var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  2212                    createInstance(Ci.nsIUpdatePrompt);
  2214     update.state = status;
  2215     this._sendStatusCodeTelemetryPing(status);
  2217     if (status == STATE_SUCCEEDED) {
  2218       update.statusText = gUpdateBundle.GetStringFromName("installSuccess");
  2220       // Update the patch's metadata.
  2221       um.activeUpdate = update;
  2222       Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true);
  2223       prompter.showUpdateInstalled();
  2225       // Done with this update. Clean it up.
  2226       cleanupActiveUpdate();
  2228     else {
  2229       // If we hit an error, then the error code will be included in the status
  2230       // string following a colon and a space. If we had an I/O error, then we
  2231       // assume that the patch is not invalid, and we re-stage the patch so that
  2232       // it can be attempted again the next time we restart. This will leave a
  2233       // space at the beginning of the error code when there is a failure which
  2234       // will be removed by using parseInt below. This prevents panic which has
  2235       // occurred numerous times previously (see bug 569642 comment #9 for one
  2236       // example) when testing releases due to forgetting to include the space.
  2237       var ary = status.split(":");
  2238       update.state = ary[0];
  2239       if (update.state == STATE_FAILED && ary[1]) {
  2240         if (handleUpdateFailure(update, ary[1])) {
  2241           return;
  2245       // Something went wrong with the patch application process.
  2246       handleFallbackToCompleteUpdate(update, false);
  2248       prompter.showUpdateError(update);
  2251     // Now trash the MozUpdater folders which staged/bgupdates uses.
  2252     cleanUpMozUpdaterDirs();
  2253   },
  2255   /**
  2256    * Submit a telemetry ping with the boolean value of a pref for a histogram
  2258    * @param  pref
  2259    *         The preference to report
  2260    * @param  histogram
  2261    *         The histogram ID to report to
  2262    */
  2263   _sendBoolPrefTelemetryPing: function AUS__boolTelemetryPing(pref, histogram) {
  2264     try {
  2265       // The getPref is already wrapped in a try/catch but we never
  2266       // want telemetry pings breaking app update so we just put it
  2267       // inside the try to be safe.
  2268       let val = getPref("getBoolPref", pref, false);
  2269       Services.telemetry.getHistogramById(histogram).add(+val);
  2270     } catch(e) {
  2271       // Don't allow any exception to be propagated.
  2272       Cu.reportError(e);
  2274   },
  2276 #ifdef XP_WIN
  2277   /**
  2278    * Submit a telemetry ping with a boolean value which indicates if the service
  2279    * is installed.
  2280    * Also submits a telemetry ping with a boolean value which indicates if the
  2281    * service was at some point installed, but is now uninstalled.
  2282    */
  2283   _sendServiceInstalledTelemetryPing: function AUS__svcInstallTelemetryPing() {
  2284     let installed = isServiceInstalled(); // Is the service installed?
  2285     let attempted = 0;
  2286     try {
  2287       let wrk = Cc["@mozilla.org/windows-registry-key;1"].
  2288                 createInstance(Ci.nsIWindowsRegKey);
  2289       wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
  2290                "SOFTWARE\\Mozilla\\MaintenanceService",
  2291                wrk.ACCESS_READ | wrk.WOW64_64);
  2292       // Was the service at some point installed, but is now uninstalled?
  2293       attempted = wrk.readIntValue("Attempted");
  2294       wrk.close();
  2295     } catch(e) {
  2297     try {
  2298       let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_INSTALLED");
  2299       h.add(Number(installed));
  2300     } catch(e) {
  2301       // Don't allow any exception to be propagated.
  2302       Cu.reportError(e);
  2304     try {
  2305       let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_MANUALLY_UNINSTALLED");
  2306       h.add(!installed && attempted ? 1 : 0);
  2307     } catch(e) {
  2308       // Don't allow any exception to be propagated.
  2309       Cu.reportError(e);
  2311   },
  2312 #endif
  2314   /**
  2315    * Submit a telemetry ping with the int value of a pref for a histogram
  2317    * @param  pref
  2318    *         The preference to report
  2319    * @param histogram
  2320    *         The histogram ID to report to
  2321    */
  2322   _sendIntPrefTelemetryPing: function AUS__intTelemetryPing(pref, histogram) {
  2323     try {
  2324       // The getPref is already wrapped in a try/catch but we never
  2325       // want telemetry pings breaking app update so we just put it
  2326       // inside the try to be safe.
  2327       let val = getPref("getIntPref", pref, 0);
  2328       Services.telemetry.getHistogramById(histogram).add(val);
  2329     } catch(e) {
  2330       // Don't allow any exception to be propagated.
  2331       Cu.reportError(e);
  2333   },
  2336   /**
  2337    * Submit the results of applying the update via telemetry.
  2339    * @param  status
  2340    *         The status of the update as read from the update.status file
  2341    */
  2342   _sendStatusCodeTelemetryPing: function AUS__statusTelemetryPing(status) {
  2343     try {
  2344       let parts = status.split(":");
  2345       if ((parts.length == 1 && status != STATE_SUCCEEDED) ||
  2346           (parts.length > 1  && parts[0] != STATE_FAILED)) {
  2347         // Should also report STATE_DOWNLOAD_FAILED
  2348         return;
  2350       let result = 0; // 0 means success
  2351       if (parts.length > 1) {
  2352         result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
  2354       Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
  2355     } catch(e) {
  2356       // Don't allow any exception to be propagated.
  2357       Cu.reportError(e);
  2359   },
  2361   /**
  2362    * Submit the interval in days since the last notification for this background
  2363    * update check.
  2364    */
  2365   _sendLastNotifyIntervalPing: function AUS__notifyIntervalPing() {
  2366     if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) {
  2367       let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
  2368       let lastUpdateTimeSeconds = getPref("getIntPref",
  2369                                           PREF_APP_UPDATE_LASTUPDATETIME, 0);
  2370       if (lastUpdateTimeSeconds) {
  2371         let currentTimeSeconds = Math.round(Date.now() / 1000);
  2372         if (lastUpdateTimeSeconds > currentTimeSeconds) {
  2373           try {
  2374             Services.telemetry.
  2375               getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
  2376               add(1);
  2377           } catch(e) {
  2378             Cu.reportError(e);
  2381         else {
  2382           let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
  2383                              (60 * 60 * 24);
  2384           try {
  2385             Services.telemetry.
  2386               getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
  2387               add(0);
  2388             Services.telemetry.
  2389               getHistogramById("UPDATER_LAST_NOTIFY_INTERVAL_DAYS_" + idSuffix).
  2390               add(intervalDays);
  2391           } catch(e) {
  2392             Cu.reportError(e);
  2397   },
  2399   /**
  2400    * Submit the result for the background update check.
  2402    * @param  code
  2403    *         An integer value as defined by the PING_BGUC_* constants.
  2404    */
  2405   _backgroundUpdateCheckCodePing: function AUS__backgroundUpdateCheckCodePing(code) {
  2406     try {
  2407       let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
  2408       Services.telemetry.
  2409         getHistogramById("UPDATER_BACKGROUND_CHECK_CODE_" + idSuffix).add(code);
  2411     catch (e) {
  2412       Cu.reportError(e);
  2414   },
  2416   /**
  2417    * Register an observer when the network comes online, so we can short-circuit
  2418    * the app.update.interval when there isn't connectivity
  2419    */
  2420   _registerOnlineObserver: function AUS__registerOnlineObserver() {
  2421     if (this._registeredOnlineObserver) {
  2422       LOG("UpdateService:_registerOnlineObserver - observer already registered");
  2423       return;
  2426     LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
  2427         "be online, then forcing another check");
  2429     Services.obs.addObserver(this, "network:offline-status-changed", false);
  2430     this._registeredOnlineObserver = true;
  2431   },
  2433   /**
  2434    * Called from the network:offline-status-changed observer.
  2435    */
  2436   _offlineStatusChanged: function AUS__offlineStatusChanged(status) {
  2437     if (status !== "online") {
  2438       return;
  2441     Services.obs.removeObserver(this, "network:offline-status-changed");
  2442     this._registeredOnlineObserver = false;
  2444     LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
  2445         "another background check");
  2447     // the background checker is contained in notify
  2448     this._attemptResume();
  2449   },
  2451   onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
  2452     this._selectAndInstallUpdate(updates);
  2453   },
  2455   onError: function AUS_onError(request, update) {
  2456     LOG("UpdateService:onError - error during background update. error code: " +
  2457         update.errorCode + ", status text: " + update.statusText);
  2459     var maxErrors;
  2460     var errCount;
  2461     if (update.errorCode == NETWORK_ERROR_OFFLINE) {
  2462       // Register an online observer to try again
  2463       this._registerOnlineObserver();
  2464       this._backgroundUpdateCheckCodePing(PING_BGUC_OFFLINE);
  2465       return;
  2468     if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
  2469         update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
  2470       errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0);
  2471       errCount++;
  2472       Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
  2473       maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
  2475     else {
  2476       update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
  2477       errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
  2478       errCount++;
  2479       Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
  2480       maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
  2481                           10);
  2484     var pingCode;
  2485     if (errCount >= maxErrors) {
  2486       var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  2487                      createInstance(Ci.nsIUpdatePrompt);
  2488       prompter.showUpdateError(update);
  2490       switch (update.errorCode) {
  2491         case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
  2492           pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY;
  2493           break;
  2494         case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
  2495           pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY;
  2496           break;
  2497         default:
  2498           pingCode = PING_BGUC_GENERAL_ERROR_NOTIFY;
  2501     else {
  2502       switch (update.errorCode) {
  2503         case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
  2504           pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT;
  2505           break;
  2506         case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
  2507           pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT;
  2508           break;
  2509         default:
  2510           pingCode = PING_BGUC_GENERAL_ERROR_SILENT;
  2513     this._backgroundUpdateCheckCodePing(pingCode);
  2514   },
  2516   /**
  2517    * Called when a connection should be resumed
  2518    */
  2519   _attemptResume: function AUS_attemptResume() {
  2520     LOG("UpdateService:_attemptResume")
  2521     // If a download is in progress, then resume it.
  2522     if (this._downloader && this._downloader._patch &&
  2523         this._downloader._patch.state == STATE_DOWNLOADING &&
  2524         this._downloader._update) {
  2525       LOG("UpdateService:_attemptResume - _patch.state: " +
  2526           this._downloader._patch.state);
  2527       // Make sure downloading is the state for selectPatch to work correctly
  2528       writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
  2529       var status = this.downloadUpdate(this._downloader._update,
  2530                                        this._downloader.background);
  2531       LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
  2532       if (status == STATE_NONE) {
  2533         cleanupActiveUpdate();
  2535       return;
  2538     this.backgroundChecker.checkForUpdates(this, false);
  2539   },
  2541   /**
  2542    * Notified when a timer fires
  2543    * @param   timer
  2544    *          The timer that fired
  2545    */
  2546   notify: function AUS_notify(timer) {
  2547     // The telemetry below is specific to background notification.
  2548     this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_ENABLED,
  2549                                     "UPDATER_UPDATES_ENABLED");
  2550     this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_METRO_ENABLED,
  2551                                     "UPDATER_UPDATES_METRO_ENABLED");
  2552     this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO,
  2553                                     "UPDATER_UPDATES_AUTOMATIC");
  2554     this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
  2555                                     "UPDATER_STAGE_ENABLED");
  2557 #ifdef XP_WIN
  2558     this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED,
  2559                                     "UPDATER_SERVICE_ENABLED");
  2560     this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS,
  2561                                    "UPDATER_SERVICE_ERRORS");
  2562     this._sendServiceInstalledTelemetryPing();
  2563 #endif
  2565     this._checkForBackgroundUpdates(true);
  2566   },
  2568   /**
  2569    * See nsIUpdateService.idl
  2570    */
  2571   checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
  2572     this._checkForBackgroundUpdates(false);
  2573   },
  2575   /**
  2576    * Checks for updates in the background.
  2577    * @param   isNotify
  2578    *          Whether or not a background update check was initiated by the
  2579    *          application update timer notification.
  2580    */
  2581   _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) {
  2582     this._isNotify = isNotify;
  2583     // From this point on, the telemetry reported differentiates between a call
  2584     // to notify and a call to checkForBackgroundUpdates so they are reported
  2585     // separately.
  2586     this._sendLastNotifyIntervalPing();
  2588     // If a download is in progress or the patch has been staged do nothing.
  2589     if (this.isDownloading) {
  2590       this._backgroundUpdateCheckCodePing(PING_BGUC_IS_DOWNLOADING);
  2591       return;
  2594     if (this._downloader && this._downloader.patchIsStaged) {
  2595       this._backgroundUpdateCheckCodePing(PING_BGUC_IS_STAGED);
  2596       return;
  2599     // The following checks will return early without notification in the call
  2600     // to checkForUpdates below. To simplify the background update check ping
  2601     // their values are checked here.
  2602     try {
  2603       if (!this.backgroundChecker.getUpdateURL(false)) {
  2604         let prefs = Services.prefs;
  2605         if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE)) {
  2606           if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL)) {
  2607             this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_DEFAULT_URL);
  2609           else {
  2610             this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_CUSTOM_URL);
  2613         else {
  2614           this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_OVERRIDE_URL);
  2617       else if (!gMetroUpdatesEnabled) {
  2618         this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
  2620       else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
  2621         this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
  2623       else if (!(gCanCheckForUpdates && hasUpdateMutex())) {
  2624         this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_CHECK);
  2626       else if (!this.backgroundChecker._enabled) {
  2627         this._backgroundUpdateCheckCodePing(PING_BGUC_DISABLED_FOR_SESSION);
  2630     catch (e) {
  2631       Cu.reportError(e);
  2634     this.backgroundChecker.checkForUpdates(this, false);
  2635   },
  2637   /**
  2638    * Determine the update from the specified updates that should be offered.
  2639    * If both valid major and minor updates are available the minor update will
  2640    * be offered.
  2641    * @param   updates
  2642    *          An array of available nsIUpdate items
  2643    * @return  The nsIUpdate to offer.
  2644    */
  2645   selectUpdate: function AUS_selectUpdate(updates) {
  2646     if (updates.length == 0) {
  2647       this._backgroundUpdateCheckCodePing(PING_BGUC_NO_UPDATE_FOUND);
  2648       return null;
  2651     // The ping for unsupported is sent after the call to showPrompt.
  2652     if (updates.length == 1 && updates[0].unsupported)
  2653       return updates[0];
  2655     // Choose the newest of the available minor and major updates.
  2656     var majorUpdate = null;
  2657     var minorUpdate = null;
  2658     var vc = Services.vc;
  2659     var lastPingCode = PING_BGUC_NO_COMPAT_UPDATE_FOUND;
  2661     updates.forEach(function(aUpdate) {
  2662       // Ignore updates for older versions of the application and updates for
  2663       // the same version of the application with the same build ID.
  2664 #ifdef TOR_BROWSER_UPDATE
  2665       var compatVersion = TOR_BROWSER_VERSION;
  2666 #else
  2667       var compatVersion = Services.appinfo.version;
  2668 #endif
  2669       var rc = vc.compare(aUpdate.appVersion, compatVersion);
  2670       if (rc < 0 || ((rc == 0) &&
  2671                      (aUpdate.buildID == Services.appinfo.appBuildID))) {
  2672         LOG("UpdateService:selectUpdate - skipping update because the " +
  2673             "update's application version is less than the current " +
  2674             "application version");
  2675         lastPingCode = PING_BGUC_UPDATE_PREVIOUS_VERSION;
  2676         return;
  2679       // Skip the update if the user responded with "never" to this update's
  2680       // application version and the update specifies showNeverForVersion
  2681       // (see bug 350636).
  2682       let neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + aUpdate.appVersion;
  2683       if (aUpdate.showNeverForVersion &&
  2684           getPref("getBoolPref", neverPrefName, false)) {
  2685         LOG("UpdateService:selectUpdate - skipping update because the " +
  2686             "preference " + neverPrefName + " is true");
  2687         lastPingCode = PING_BGUC_UPDATE_NEVER_PREF;
  2688         return;
  2691       switch (aUpdate.type) {
  2692         case "major":
  2693           if (!majorUpdate)
  2694             majorUpdate = aUpdate;
  2695           else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
  2696             majorUpdate = aUpdate;
  2697           break;
  2698         case "minor":
  2699           if (!minorUpdate)
  2700             minorUpdate = aUpdate;
  2701           else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
  2702             minorUpdate = aUpdate;
  2703           break;
  2704         default:
  2705           LOG("UpdateService:selectUpdate - skipping unknown update type: " +
  2706               aUpdate.type);
  2707           lastPingCode = PING_BGUC_UPDATE_INVALID_TYPE;
  2708           break;
  2710     });
  2712     var update = minorUpdate || majorUpdate;
  2713     if (!update)
  2714       this._backgroundUpdateCheckCodePing(lastPingCode);
  2716     return update;
  2717   },
  2719   /**
  2720    * Reference to the currently selected update for when add-on compatibility
  2721    * is checked.
  2722    */
  2723   _update: null,
  2725   /**
  2726    * Determine which of the specified updates should be installed and begin the
  2727    * download/installation process or notify the user about the update.
  2728    * @param   updates
  2729    *          An array of available updates
  2730    */
  2731   _selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
  2732     // Return early if there's an active update. The user is already aware and
  2733     // is downloading or performed some user action to prevent notification.
  2734     var um = Cc["@mozilla.org/updates/update-manager;1"].
  2735              getService(Ci.nsIUpdateManager);
  2736     if (um.activeUpdate) {
  2737 #ifdef MOZ_WIDGET_GONK
  2738       // For gonk, the user isn't necessarily aware of the update, so we need
  2739       // to show the prompt to make sure.
  2740       this._showPrompt(um.activeUpdate);
  2741 #endif
  2742       this._backgroundUpdateCheckCodePing(PING_BGUC_HAS_ACTIVEUPDATE);
  2743       return;
  2746     var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
  2747     if (!updateEnabled) {
  2748       this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
  2749       LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
  2750           "update is disabled");
  2751       return;
  2754     if (!gMetroUpdatesEnabled) {
  2755       this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
  2756       return;
  2759     var update = this.selectUpdate(updates, updates.length);
  2760     if (!update) {
  2761       return;
  2764     if (update.unsupported) {
  2765       LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
  2766           "this system");
  2767       if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
  2768         LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
  2769             "update is not supported for this system");
  2770         this._showPrompt(update);
  2772       this._backgroundUpdateCheckCodePing(PING_BGUC_UNSUPPORTED);
  2773       return;
  2776     if (!(gCanApplyUpdates && hasUpdateMutex())) {
  2777       LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " +
  2778           "apply updates... prompting");
  2779       this._showPrompt(update);
  2780       this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_APPLY);
  2781       return;
  2784     /**
  2785 #      From this point on there are two possible outcomes:
  2786 #      1. download and install the update automatically
  2787 #      2. notify the user about the availability of an update
  2789 #      Notes:
  2790 #      a) if the app.update.auto preference is false then automatic download and
  2791 #         install is disabled and the user will be notified.
  2792 #      b) if the update has a showPrompt attribute the user will be notified.
  2793 #      c) Mode is determined by the value of the app.update.mode preference.
  2795 #      If the update when it is first read has an appVersion attribute the
  2796 #      following behavior implemented in bug 530872 will occur:
  2797 #      Mode   Incompatible Add-ons   Outcome
  2798 #      0      N/A                    Auto Install
  2799 #      1      Yes                    Notify
  2800 #      1      No                     Auto Install
  2802 #      If the update when it is first read does not have an appVersion attribute
  2803 #      the following deprecated behavior will occur:
  2804 #      Update Type   Mode   Incompatible Add-ons   Outcome
  2805 #      Major         all    N/A                    Notify
  2806 #      Minor         0      N/A                    Auto Install
  2807 #      Minor         1      Yes                    Notify
  2808 #      Minor         1      No                     Auto Install
  2809      */
  2810     if (update.showPrompt) {
  2811       LOG("UpdateService:_selectAndInstallUpdate - prompting because the " +
  2812           "update snippet specified showPrompt");
  2813       this._showPrompt(update);
  2814       if (!Services.metro || !Services.metro.immersive) {
  2815         this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_SNIPPET);
  2816         return;
  2820     if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
  2821       LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
  2822           "install is disabled");
  2823       this._showPrompt(update);
  2824       if (!Services.metro || !Services.metro.immersive) {
  2825         this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_PREF);
  2826         return;
  2830     if (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1) == 0) {
  2831       // Do not prompt regardless of add-on incompatibilities
  2832       LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
  2833           "check disabled by preference, just download the update");
  2834       var status = this.downloadUpdate(update, true);
  2835       if (status == STATE_NONE)
  2836         cleanupActiveUpdate();
  2837       this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_PREF_DISABLED);
  2838       return;
  2841     // Only check add-on compatibility when the version changes.
  2842 #ifdef TOR_BROWSER_UPDATE
  2843     var compatVersion = TOR_BROWSER_VERSION;
  2844 #else
  2845     var compatVersion = Services.appinfo.version;
  2846 #endif
  2847     if (update.appVersion &&
  2848         Services.vc.compare(update.appVersion, compatVersion) != 0) {
  2849       this._update = update;
  2850       this._checkAddonCompatibility();
  2852     else {
  2853       LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
  2854           "check not performed due to the update version being the same as " +
  2855           "the current application version, just download the update");
  2856       var status = this.downloadUpdate(update, true);
  2857       if (status == STATE_NONE)
  2858         cleanupActiveUpdate();
  2859       this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_SAME_APP_VER);
  2861   },
  2863   _showPrompt: function AUS__showPrompt(update) {
  2864     var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  2865                    createInstance(Ci.nsIUpdatePrompt);
  2866     prompter.showUpdateAvailable(update);
  2867   },
  2869   _checkAddonCompatibility: function AUS__checkAddonCompatibility() {
  2870     try {
  2871       var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
  2873     catch (e) { }
  2875     // Get all the installed add-ons
  2876     var self = this;
  2877     AddonManager.getAllAddons(function(addons) {
  2878 #ifdef TOR_BROWSER_UPDATE
  2879       let compatVersion = self._update.platformVersion;
  2880 #else
  2881       let compatVersion = self._update.appVersion;
  2882 #endif
  2883       self._incompatibleAddons = [];
  2884       addons.forEach(function(addon) {
  2885         // Protect against code that overrides the add-ons manager and doesn't
  2886         // implement the isCompatibleWith or the findUpdates method.
  2887         if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) {
  2888           let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
  2889                        "or the findUpdates method!";
  2890           if (addon.id)
  2891             errMsg += " Add-on ID: " + addon.id;
  2892           Cu.reportError(errMsg);
  2893           return;
  2896         // If an add-on isn't appDisabled and isn't userDisabled then it is
  2897         // either active now or the user expects it to be active after the
  2898         // restart. If that is the case and the add-on is not installed by the
  2899         // application and is not compatible with the new application version
  2900         // then the user should be warned that the add-on will become
  2901         // incompatible. If an addon's type equals plugin it is skipped since
  2902         // checking plugins compatibility information isn't supported and
  2903         // getting the scope property of a plugin breaks in some environments
  2904         // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
  2905         // block the user from upgrading.
  2906         try {
  2907           if (addon.type != "plugin" && addon.id != hotfixID &&
  2908               !addon.appDisabled && !addon.userDisabled &&
  2909               addon.scope != AddonManager.SCOPE_APPLICATION &&
  2910               addon.isCompatible &&
  2911               !addon.isCompatibleWith(compatVersion,
  2912                                       self._update.platformVersion))
  2913             self._incompatibleAddons.push(addon);
  2915         catch (e) {
  2916           Cu.reportError(e);
  2918       });
  2920       if (self._incompatibleAddons.length > 0) {
  2921       /**
  2922 #        PREF_APP_UPDATE_INCOMPATIBLE_MODE
  2923 #        Controls the mode in which we check for updates as follows.
  2925 #          PREF_APP_UPDATE_INCOMPATIBLE_MODE != 1
  2926 #          We check for VersionInfo _and_ NewerVersion updates for the
  2927 #          incompatible add-ons - i.e. if Foo 1.2 is installed and it is
  2928 #          incompatible with the update, and we find Foo 2.0 which is but has
  2929 #          not been installed, then we do NOT prompt because the user can
  2930 #          download Foo 2.0 when they restart after the update during the add-on
  2931 #          mismatch checking UI. This is the default, since it suppresses most
  2932 #          prompt dialogs.
  2934 #          PREF_APP_UPDATE_INCOMPATIBLE_MODE == 1
  2935 #          We check for VersionInfo updates for the incompatible add-ons - i.e.
  2936 #          if the situation above with Foo 1.2 and available update to 2.0
  2937 #          applies, we DO show the prompt since a download operation will be
  2938 #          required after the update. This is not the default and is supplied
  2939 #          only as a hidden option for those that want it.
  2940        */
  2941         self._updateCheckCount = self._incompatibleAddons.length;
  2942         LOG("UpdateService:_checkAddonCompatibility - checking for " +
  2943             "incompatible add-ons");
  2945         self._incompatibleAddons.forEach(function(addon) {
  2946           addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
  2947                             compatVersion, this._update.platformVersion);
  2948         }, self);
  2950       else {
  2951         LOG("UpdateService:_checkAddonCompatibility - no incompatible " +
  2952             "add-ons found, just download the update");
  2953         var status = self.downloadUpdate(self._update, true);
  2954         if (status == STATE_NONE)
  2955           cleanupActiveUpdate();
  2956         self._update = null;
  2957         this._backgroundUpdateCheckCodePing(PING_BGUC_CHECK_NO_INCOMPAT);
  2959     });
  2960   },
  2962   // AddonUpdateListener
  2963   onCompatibilityUpdateAvailable: function(addon) {
  2964     // Remove the add-on from the list of add-ons that will become incompatible
  2965     // with the new version of the application.
  2966     for (var i = 0; i < this._incompatibleAddons.length; ++i) {
  2967       if (this._incompatibleAddons[i].id == addon.id) {
  2968         LOG("UpdateService:onCompatibilityUpdateAvailable - found update for " +
  2969             "add-on ID: " + addon.id);
  2970         this._incompatibleAddons.splice(i, 1);
  2973   },
  2975   onUpdateAvailable: function(addon, install) {
  2976     if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1)
  2977       return;
  2979     // If the new version of this add-on is blocklisted for the new application
  2980     // then it isn't a valid update and the user should still be warned that
  2981     // the add-on will become incompatible.
  2982     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
  2983              getService(Ci.nsIBlocklistService);
  2984 #ifdef TOR_BROWSER_UPDATE
  2985     let compatVersion = gUpdates.update.platformVersion;
  2986 #else
  2987     let compatVersion = gUpdates.update.appVersion;
  2988 #endif
  2989     if (bs.isAddonBlocklisted(addon,
  2990                               compatVersion,
  2991                               gUpdates.update.platformVersion))
  2992       return;
  2994     // Compatibility or new version updates mean the same thing here.
  2995     this.onCompatibilityUpdateAvailable(addon);
  2996   },
  2998   onUpdateFinished: function(addon) {
  2999     if (--this._updateCheckCount > 0)
  3000       return;
  3002     if (this._incompatibleAddons.length > 0 ||
  3003         !(gCanApplyUpdates && hasUpdateMutex())) {
  3004       LOG("UpdateService:onUpdateEnded - prompting because there are " +
  3005           "incompatible add-ons");
  3006       this._showPrompt(this._update);
  3007       this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_HAVE_INCOMPAT);
  3009     else {
  3010       LOG("UpdateService:_selectAndInstallUpdate - updates for all " +
  3011           "incompatible add-ons found, just download the update");
  3012       var status = this.downloadUpdate(this._update, true);
  3013       if (status == STATE_NONE)
  3014         cleanupActiveUpdate();
  3015       this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT);
  3017     this._update = null;
  3018   },
  3020   /**
  3021    * The Checker used for background update checks.
  3022    */
  3023   _backgroundChecker: null,
  3025   /**
  3026    * See nsIUpdateService.idl
  3027    */
  3028   get backgroundChecker() {
  3029     if (!this._backgroundChecker)
  3030       this._backgroundChecker = new Checker();
  3031     return this._backgroundChecker;
  3032   },
  3034   /**
  3035    * See nsIUpdateService.idl
  3036    */
  3037   get canCheckForUpdates() {
  3038     return gCanCheckForUpdates && hasUpdateMutex();
  3039   },
  3041   /**
  3042    * See nsIUpdateService.idl
  3043    */
  3044   get canApplyUpdates() {
  3045     return gCanApplyUpdates && hasUpdateMutex();
  3046   },
  3048   /**
  3049    * See nsIUpdateService.idl
  3050    */
  3051   get canStageUpdates() {
  3052     return getCanStageUpdates();
  3053   },
  3055   /**
  3056    * See nsIUpdateService.idl
  3057    */
  3058   get isOtherInstanceHandlingUpdates() {
  3059     return !hasUpdateMutex();
  3060   },
  3063   /**
  3064    * See nsIUpdateService.idl
  3065    */
  3066   addDownloadListener: function AUS_addDownloadListener(listener) {
  3067     if (!this._downloader) {
  3068       LOG("UpdateService:addDownloadListener - no downloader!");
  3069       return;
  3071     this._downloader.addDownloadListener(listener);
  3072   },
  3074   /**
  3075    * See nsIUpdateService.idl
  3076    */
  3077   removeDownloadListener: function AUS_removeDownloadListener(listener) {
  3078     if (!this._downloader) {
  3079       LOG("UpdateService:removeDownloadListener - no downloader!");
  3080       return;
  3082     this._downloader.removeDownloadListener(listener);
  3083   },
  3085   /**
  3086    * See nsIUpdateService.idl
  3087    */
  3088   downloadUpdate: function AUS_downloadUpdate(update, background) {
  3089     if (!update)
  3090       throw Cr.NS_ERROR_NULL_POINTER;
  3092     // Don't download the update if the update's version is less than the
  3093     // current application's version or the update's version is the same as the
  3094     // application's version and the build ID is the same as the application's
  3095     // build ID.
  3096 #ifdef TOR_BROWSER_UPDATE
  3097     var compatVersion = TOR_BROWSER_VERSION;
  3098 #else
  3099     var compatVersion = Services.appinfo.version;
  3100 #endif
  3101     if (update.appVersion &&
  3102         (Services.vc.compare(update.appVersion, compatVersion) < 0 ||
  3103          update.buildID && update.buildID == Services.appinfo.appBuildID &&
  3104          update.appVersion == compatVersion)) {
  3105       LOG("UpdateService:downloadUpdate - canceling download of update since " +
  3106           "it is for an earlier or same application version and build ID.\n" +
  3107 #ifdef TOR_BROWSER_UPDATE
  3108           "current Tor Browser version: " + compatVersion + "\n" +
  3109           "update Tor Browser version : " + update.appVersion + "\n" +
  3110 #else
  3111           "current application version: " + compatVersion + "\n" +
  3112           "update application version : " + update.appVersion + "\n" +
  3113 #endif
  3114           "current build ID: " + Services.appinfo.appBuildID + "\n" +
  3115           "update build ID : " + update.buildID);
  3116       cleanupActiveUpdate();
  3117       return STATE_NONE;
  3120     // If a download request is in progress vs. a download ready to resume
  3121     if (this.isDownloading) {
  3122       if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
  3123           background == this._downloader.background) {
  3124         LOG("UpdateService:downloadUpdate - no support for downloading more " +
  3125             "than one update at a time");
  3126         return readStatusFile(getUpdatesDir());
  3128       this._downloader.cancel();
  3130 #ifdef MOZ_WIDGET_GONK
  3131     var um = Cc["@mozilla.org/updates/update-manager;1"].
  3132              getService(Ci.nsIUpdateManager);
  3133     var activeUpdate = um.activeUpdate;
  3134     if (activeUpdate &&
  3135         (activeUpdate.appVersion != update.appVersion ||
  3136          activeUpdate.buildID != update.buildID)) {
  3137       // We have an activeUpdate (which presumably was interrupted), and are
  3138       // about start downloading a new one. Make sure we remove all traces
  3139       // of the active one (otherwise we'll start appending the new update.mar
  3140       // the the one that's been partially downloaded).
  3141       LOG("UpdateService:downloadUpdate - removing stale active update.");
  3142       cleanupActiveUpdate();
  3144 #endif
  3145     // Set the previous application version prior to downloading the update.
  3146     update.previousAppVersion = compatVersion;
  3147     this._downloader = new Downloader(background, this);
  3148     return this._downloader.downloadUpdate(update);
  3149   },
  3151   /**
  3152    * See nsIUpdateService.idl
  3153    */
  3154   pauseDownload: function AUS_pauseDownload() {
  3155     if (this.isDownloading) {
  3156       this._downloader.cancel();
  3157     } else if (this._retryTimer) {
  3158       // Download status is still consider as 'downloading' during retry.
  3159       // We need to cancel both retry and download at this stage.
  3160       this._retryTimer.cancel();
  3161       this._retryTimer = null;
  3162       this._downloader.cancel();
  3164   },
  3166   /**
  3167    * See nsIUpdateService.idl
  3168    */
  3169   getUpdatesDirectory: getUpdatesDir,
  3171   /**
  3172    * See nsIUpdateService.idl
  3173    */
  3174   get isDownloading() {
  3175     return this._downloader && this._downloader.isBusy;
  3176   },
  3178   /**
  3179    * See nsIUpdateService.idl
  3180    */
  3181   applyOsUpdate: function AUS_applyOsUpdate(aUpdate) {
  3182     if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) {
  3183       aUpdate.statusText = "fota-state-error";
  3184       throw Cr.NS_ERROR_FAILURE;
  3187     let osApplyToDir;
  3188     try {
  3189       aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
  3190       osApplyToDir = aUpdate.getProperty("osApplyToDir");
  3191     } catch (e) {}
  3193     if (!osApplyToDir) {
  3194       LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
  3195           "in the nsIUpdate!");
  3196       handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
  3197       return;
  3200     let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  3201     updateFile.initWithPath(osApplyToDir + "/update.zip");
  3202     if (!updateFile.exists()) {
  3203       LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " +
  3204           updateFile.path);
  3205       handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
  3206       return;
  3209     writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
  3210     LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
  3211         "FOTA update: " + updateFile.path);
  3212     try {
  3213       let recoveryService = Cc["@mozilla.org/recovery-service;1"]
  3214                             .getService(Ci.nsIRecoveryService);
  3215       recoveryService.installFotaUpdate(updateFile.path);
  3216     } catch (e) {
  3217       LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" +
  3218           " to apply FOTA update " + updateFile.path);
  3219       writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED);
  3220       handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR);
  3222   },
  3224   classID: UPDATESERVICE_CID,
  3225   classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
  3226                                     contractID: UPDATESERVICE_CONTRACTID,
  3227                                     interfaces: [Ci.nsIApplicationUpdateService,
  3228                                                  Ci.nsITimerCallback,
  3229                                                  Ci.nsIObserver],
  3230                                     flags: Ci.nsIClassInfo.SINGLETON}),
  3232   _xpcom_factory: UpdateServiceFactory,
  3233   QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
  3234                                          Ci.nsIUpdateCheckListener,
  3235                                          Ci.nsITimerCallback,
  3236                                          Ci.nsIObserver])
  3237 };
  3239 /**
  3240  * A service to manage active and past updates.
  3241  * @constructor
  3242  */
  3243 function UpdateManager() {
  3244   // Ensure the Active Update file is loaded
  3245   var updates = this._loadXMLFileIntoArray(getUpdateFile(
  3246                   [FILE_UPDATE_ACTIVE]));
  3247   if (updates.length > 0) {
  3248     // Under some edgecases such as Windows system restore the active-update.xml
  3249     // will contain a pending update without the status file which will return
  3250     // STATE_NONE. To recover from this situation clean the updates dir and
  3251     // rewrite the active-update.xml file without the broken update.
  3252     if (readStatusFile(getUpdatesDir()) == STATE_NONE) {
  3253       cleanUpUpdatesDir();
  3254       this._writeUpdatesToXMLFile([], getUpdateFile([FILE_UPDATE_ACTIVE]));
  3256     else
  3257       this._activeUpdate = updates[0];
  3260 UpdateManager.prototype = {
  3261   /**
  3262    * All previously downloaded and installed updates, as an array of nsIUpdate
  3263    * objects.
  3264    */
  3265   _updates: null,
  3267   /**
  3268    * The current actively downloading/installing update, as a nsIUpdate object.
  3269    */
  3270   _activeUpdate: null,
  3272   /**
  3273    * Handle Observer Service notifications
  3274    * @param   subject
  3275    *          The subject of the notification
  3276    * @param   topic
  3277    *          The notification name
  3278    * @param   data
  3279    *          Additional data
  3280    */
  3281   observe: function UM_observe(subject, topic, data) {
  3282     // Hack to be able to run and cleanup tests by reloading the update data.
  3283     if (topic == "um-reload-update-data") {
  3284       this._updates = this._loadXMLFileIntoArray(getUpdateFile(
  3285                         [FILE_UPDATES_DB]));
  3286       this._activeUpdate = null;
  3287       var updates = this._loadXMLFileIntoArray(getUpdateFile(
  3288                       [FILE_UPDATE_ACTIVE]));
  3289       if (updates.length > 0)
  3290         this._activeUpdate = updates[0];
  3292   },
  3294   /**
  3295    * Loads an updates.xml formatted file into an array of nsIUpdate items.
  3296    * @param   file
  3297    *          A nsIFile for the updates.xml file
  3298    * @return  The array of nsIUpdate items held in the file.
  3299    */
  3300   _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
  3301     if (!file.exists()) {
  3302       LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
  3303       return [];
  3306     var result = [];
  3307     var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
  3308                      createInstance(Ci.nsIFileInputStream);
  3309     fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
  3310     try {
  3311       var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
  3312                    createInstance(Ci.nsIDOMParser);
  3313       var doc = parser.parseFromStream(fileStream, "UTF-8",
  3314                                        fileStream.available(), "text/xml");
  3316       const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
  3317       var updateCount = doc.documentElement.childNodes.length;
  3318       for (var i = 0; i < updateCount; ++i) {
  3319         var updateElement = doc.documentElement.childNodes.item(i);
  3320         if (updateElement.nodeType != ELEMENT_NODE ||
  3321             updateElement.localName != "update")
  3322           continue;
  3324         updateElement.QueryInterface(Ci.nsIDOMElement);
  3325         try {
  3326           var update = new Update(updateElement);
  3327         } catch (e) {
  3328           LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
  3329           continue;
  3331         result.push(update);
  3334     catch (e) {
  3335       LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
  3336           "list. Exception: " + e);
  3338     fileStream.close();
  3339     return result;
  3340   },
  3342   /**
  3343    * Load the update manager, initializing state from state files.
  3344    */
  3345   _ensureUpdates: function UM__ensureUpdates() {
  3346     if (!this._updates) {
  3347       this._updates = this._loadXMLFileIntoArray(getUpdateFile(
  3348                         [FILE_UPDATES_DB]));
  3349       var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
  3350                             [FILE_UPDATE_ACTIVE]));
  3351       if (activeUpdates.length > 0)
  3352         this._activeUpdate = activeUpdates[0];
  3354   },
  3356   /**
  3357    * See nsIUpdateService.idl
  3358    */
  3359   getUpdateAt: function UM_getUpdateAt(index) {
  3360     this._ensureUpdates();
  3361     return this._updates[index];
  3362   },
  3364   /**
  3365    * See nsIUpdateService.idl
  3366    */
  3367   get updateCount() {
  3368     this._ensureUpdates();
  3369     return this._updates.length;
  3370   },
  3372   /**
  3373    * See nsIUpdateService.idl
  3374    */
  3375   get activeUpdate() {
  3376     if (this._activeUpdate &&
  3377         this._activeUpdate.channel != UpdateChannel.get()) {
  3378       LOG("UpdateManager:get activeUpdate - channel has changed, " +
  3379           "reloading default preferences to workaround bug 802022");
  3380       // Workaround to get distribution preferences loaded (Bug 774618). This
  3381       // can be removed after bug 802022 is fixed.
  3382       let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
  3383       prefSvc.observe(null, "reload-default-prefs", null);
  3384       if (this._activeUpdate.channel != UpdateChannel.get()) {
  3385         // User switched channels, clear out any old active updates and remove
  3386         // partial downloads
  3387         this._activeUpdate = null;
  3388         this.saveUpdates();
  3390         // Destroy the updates directory, since we're done with it.
  3391         cleanUpUpdatesDir();
  3394     return this._activeUpdate;
  3395   },
  3396   set activeUpdate(activeUpdate) {
  3397     this._addUpdate(activeUpdate);
  3398     this._activeUpdate = activeUpdate;
  3399     if (!activeUpdate) {
  3400       // If |activeUpdate| is null, we have updated both lists - the active list
  3401       // and the history list, so we want to write both files.
  3402       this.saveUpdates();
  3404     else
  3405       this._writeUpdatesToXMLFile([this._activeUpdate],
  3406                                   getUpdateFile([FILE_UPDATE_ACTIVE]));
  3407     return activeUpdate;
  3408   },
  3410   /**
  3411    * Add an update to the Updates list. If the item already exists in the list,
  3412    * replace the existing value with the new value.
  3413    * @param   update
  3414    *          The nsIUpdate object to add.
  3415    */
  3416   _addUpdate: function UM__addUpdate(update) {
  3417     if (!update)
  3418       return;
  3419     this._ensureUpdates();
  3420     if (this._updates) {
  3421       for (var i = 0; i < this._updates.length; ++i) {
  3422         if (this._updates[i] &&
  3423             this._updates[i].appVersion == update.appVersion &&
  3424             this._updates[i].buildID == update.buildID) {
  3425           // Replace the existing entry with the new value, updating
  3426           // all metadata.
  3427           this._updates[i] = update;
  3428           return;
  3432     // Otherwise add it to the front of the list.
  3433     this._updates.unshift(update);
  3434   },
  3436   /**
  3437    * Serializes an array of updates to an XML file
  3438    * @param   updates
  3439    *          An array of nsIUpdate objects
  3440    * @param   file
  3441    *          The nsIFile object to serialize to
  3442    */
  3443   _writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) {
  3444     var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  3445               createInstance(Ci.nsIFileOutputStream);
  3446     var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
  3447                     FileUtils.MODE_TRUNCATE;
  3448     if (!file.exists())
  3449       file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
  3450     fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);
  3452     try {
  3453       var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
  3454                    createInstance(Ci.nsIDOMParser);
  3455       const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
  3456       var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
  3458       for (var i = 0; i < updates.length; ++i) {
  3459         if (updates[i])
  3460           doc.documentElement.appendChild(updates[i].serialize(doc));
  3463       var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
  3464                        createInstance(Ci.nsIDOMSerializer);
  3465       serializer.serializeToStream(doc.documentElement, fos, null);
  3467     catch (e) {
  3470     FileUtils.closeSafeFileOutputStream(fos);
  3471   },
  3473   /**
  3474    * See nsIUpdateService.idl
  3475    */
  3476   saveUpdates: function UM_saveUpdates() {
  3477     this._writeUpdatesToXMLFile([this._activeUpdate],
  3478                                 getUpdateFile([FILE_UPDATE_ACTIVE]));
  3479     if (this._activeUpdate)
  3480       this._addUpdate(this._activeUpdate);
  3482     this._ensureUpdates();
  3483     // Don't write updates that have a temporary state to the updates.xml file.
  3484     if (this._updates) {
  3485       let updates = this._updates.slice();
  3486       for (let i = updates.length - 1; i >= 0; --i) {
  3487         let state = updates[i].state;
  3488         if (state == STATE_NONE || state == STATE_DOWNLOADING ||
  3489             state == STATE_APPLIED || state == STATE_APPLIED_SVC ||
  3490             state == STATE_PENDING || state == STATE_PENDING_SVC) {
  3491           updates.splice(i, 1);
  3495       this._writeUpdatesToXMLFile(updates.slice(0, 10),
  3496                                   getUpdateFile([FILE_UPDATES_DB]));
  3498   },
  3500   refreshUpdateStatus: function UM_refreshUpdateStatus(update) {
  3501     var updateSucceeded = true;
  3502     var status = readStatusFile(getUpdatesDir());
  3503     var ary = status.split(":");
  3504     update.state = ary[0];
  3505     if (update.state == STATE_FAILED && ary[1]) {
  3506       updateSucceeded = false;
  3507       if (!handleUpdateFailure(update, ary[1])) {
  3508         handleFallbackToCompleteUpdate(update, true);
  3511     if (update.state == STATE_APPLIED && shouldUseService()) {
  3512       writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
  3514     var um = Cc["@mozilla.org/updates/update-manager;1"].
  3515              getService(Ci.nsIUpdateManager);
  3516     um.saveUpdates();
  3518     if (update.state != STATE_PENDING && update.state != STATE_PENDING_SVC) {
  3519       // Destroy the updates directory, since we're done with it.
  3520       // Make sure to not do this when the updater has fallen back to
  3521       // non-staged updates.
  3522       cleanUpUpdatesDir(updateSucceeded);
  3525     // Send an observer notification which the update wizard uses in
  3526     // order to update its UI.
  3527     LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
  3528         "the update was staged. state: " + update.state + ", status: " + status);
  3529     Services.obs.notifyObservers(null, "update-staged", update.state);
  3531     // Do this after *everything* else, since it will likely cause the app
  3532     // to shut down.
  3533 #ifdef MOZ_WIDGET_GONK
  3534     if (update.state == STATE_APPLIED) {
  3535       // Notify the user that an update has been staged and is ready for
  3536       // installation (i.e. that they should restart the application). We do
  3537       // not notify on failed update attempts.
  3538       var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  3539                      createInstance(Ci.nsIUpdatePrompt);
  3540       prompter.showUpdateDownloaded(update, true);
  3541     } else {
  3542       releaseSDCardMountLock();
  3544 #else
  3545     // Only prompt when the UI isn't already open.
  3546     let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
  3547     if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
  3548         windowType && Services.wm.getMostRecentWindow(windowType)) {
  3549       return;
  3552     if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC ||
  3553         update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) {
  3554       // Notify the user that an update has been staged and is ready for
  3555       // installation (i.e. that they should restart the application).
  3556       var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  3557                      createInstance(Ci.nsIUpdatePrompt);
  3558       prompter.showUpdateDownloaded(update, true);
  3560 #endif
  3561   },
  3563   classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
  3564   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
  3565 };
  3567 /**
  3568  * Checker
  3569  * Checks for new Updates
  3570  * @constructor
  3571  */
  3572 function Checker() {
  3574 Checker.prototype = {
  3575   /**
  3576    * The XMLHttpRequest object that performs the connection.
  3577    */
  3578   _request  : null,
  3580   /**
  3581    * The nsIUpdateCheckListener callback
  3582    */
  3583   _callback : null,
  3585   /**
  3586    * The URL of the update service XML file to connect to that contains details
  3587    * about available updates.
  3588    */
  3589   getUpdateURL: function UC_getUpdateURL(force) {
  3590     this._forced = force;
  3592     // Use the override URL if specified.
  3593     var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
  3595     // Otherwise, construct the update URL from component parts.
  3596     if (!url) {
  3597       try {
  3598         url = Services.prefs.getDefaultBranch(null).
  3599               getCharPref(PREF_APP_UPDATE_URL);
  3600       } catch (e) {
  3604     if (!url || url == "") {
  3605       LOG("Checker:getUpdateURL - update URL not defined");
  3606       return null;
  3609     url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
  3610 #ifdef TOR_BROWSER_UPDATE
  3611     url = url.replace(/%VERSION%/g, TOR_BROWSER_VERSION);
  3612 #else
  3613     url = url.replace(/%VERSION%/g, Services.appinfo.version);
  3614 #endif
  3615     url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
  3616     url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + gABI);
  3617     url = url.replace(/%OS_VERSION%/g, gOSVersion);
  3618     if (/%LOCALE%/.test(url))
  3619       url = url.replace(/%LOCALE%/g, getLocale());
  3620     url = url.replace(/%CHANNEL%/g, UpdateChannel.get());
  3621     url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
  3622     url = url.replace(/%DISTRIBUTION%/g,
  3623                       getDistributionPrefValue(PREF_APP_DISTRIBUTION));
  3624     url = url.replace(/%DISTRIBUTION_VERSION%/g,
  3625                       getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
  3626     url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, ""));
  3627     url = url.replace(/\+/g, "%2B");
  3629 #ifdef MOZ_WIDGET_GONK
  3630     url = url.replace(/%PRODUCT_MODEL%/g, gProductModel);
  3631     url = url.replace(/%B2G_VERSION%/g, getPref("getCharPref", PREF_APP_B2G_VERSION, null));
  3632 #endif
  3634     if (force)
  3635       url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
  3637     LOG("Checker:getUpdateURL - update URL: " + url);
  3638     return url;
  3639   },
  3641   /**
  3642    * See nsIUpdateService.idl
  3643    */
  3644   checkForUpdates: function UC_checkForUpdates(listener, force) {
  3645     LOG("Checker: checkForUpdates, force: " + force);
  3646     if (!listener)
  3647       throw Cr.NS_ERROR_NULL_POINTER;
  3649     Services.obs.notifyObservers(null, "update-check-start", null);
  3651     var url = this.getUpdateURL(force);
  3652     if (!url || (!this.enabled && !force))
  3653       return;
  3655     this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
  3656                     createInstance(Ci.nsISupports);
  3657     // This is here to let unit test code override XHR
  3658     if (this._request.wrappedJSObject) {
  3659       this._request = this._request.wrappedJSObject;
  3661     this._request.open("GET", url, true);
  3662     var allowNonBuiltIn = !getPref("getBoolPref",
  3663                                    PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
  3664     this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn);
  3665     // Prevent the request from reading from the cache.
  3666     this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  3667     // Prevent the request from writing to the cache.
  3668     this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
  3670     this._request.overrideMimeType("text/xml");
  3671     // The Cache-Control header is only interpreted by proxies and the
  3672     // final destination. It does not help if a resource is already
  3673     // cached locally.
  3674     this._request.setRequestHeader("Cache-Control", "no-cache");
  3675     // HTTP/1.0 servers might not implement Cache-Control and
  3676     // might only implement Pragma: no-cache
  3677     this._request.setRequestHeader("Pragma", "no-cache");
  3679     var self = this;
  3680     this._request.addEventListener("error", function(event) { self.onError(event); } ,false);
  3681     this._request.addEventListener("load", function(event) { self.onLoad(event); }, false);
  3683     LOG("Checker:checkForUpdates - sending request to: " + url);
  3684     this._request.send(null);
  3686     this._callback = listener;
  3687   },
  3689   /**
  3690    * Returns an array of nsIUpdate objects discovered by the update check.
  3691    * @throws if the XML document element node name is not updates.
  3692    */
  3693   get _updates() {
  3694     var updatesElement = this._request.responseXML.documentElement;
  3695     if (!updatesElement) {
  3696       LOG("Checker:_updates get - empty updates document?!");
  3697       return [];
  3700     if (updatesElement.nodeName != "updates") {
  3701       LOG("Checker:_updates get - unexpected node name!");
  3702       throw new Error("Unexpected node name, expected: updates, got: " +
  3703                       updatesElement.nodeName);
  3706     const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
  3707     var updates = [];
  3708     for (var i = 0; i < updatesElement.childNodes.length; ++i) {
  3709       var updateElement = updatesElement.childNodes.item(i);
  3710       if (updateElement.nodeType != ELEMENT_NODE ||
  3711           updateElement.localName != "update")
  3712         continue;
  3714       updateElement.QueryInterface(Ci.nsIDOMElement);
  3715       try {
  3716         var update = new Update(updateElement);
  3717       } catch (e) {
  3718         LOG("Checker:_updates get - invalid <update/>, ignoring...");
  3719         continue;
  3721       update.serviceURL = this.getUpdateURL(this._forced);
  3722       update.channel = UpdateChannel.get();
  3723       updates.push(update);
  3726     return updates;
  3727   },
  3729   /**
  3730    * Returns the status code for the XMLHttpRequest
  3731    */
  3732   _getChannelStatus: function UC__getChannelStatus(request) {
  3733     var status = 0;
  3734     try {
  3735       status = request.status;
  3737     catch (e) {
  3740     if (status == 0)
  3741       status = request.channel.QueryInterface(Ci.nsIRequest).status;
  3742     return status;
  3743   },
  3745   _isHttpStatusCode: function UC__isHttpStatusCode(status) {
  3746     return status >= 100 && status <= 599;
  3747   },
  3749   /**
  3750    * The XMLHttpRequest succeeded and the document was loaded.
  3751    * @param   event
  3752    *          The nsIDOMEvent for the load
  3753    */
  3754   onLoad: function UC_onLoad(event) {
  3755     LOG("Checker:onLoad - request completed downloading document");
  3757     var prefs = Services.prefs;
  3758     var certs = null;
  3759     if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) &&
  3760         getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true)) {
  3761       certs = gCertUtils.readCertPrefs(PREF_APP_UPDATE_CERTS_BRANCH);
  3764     try {
  3765       // Analyze the resulting DOM and determine the set of updates.
  3766       var updates = this._updates;
  3767       LOG("Checker:onLoad - number of updates available: " + updates.length);
  3768       var allowNonBuiltIn = !getPref("getBoolPref",
  3769                                      PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
  3770       gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
  3772       if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS))
  3773         Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS);
  3775       if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
  3776         Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
  3778       // Tell the callback about the updates
  3779       this._callback.onCheckComplete(event.target, updates, updates.length);
  3781     catch (e) {
  3782       LOG("Checker:onLoad - there was a problem checking for updates. " +
  3783           "Exception: " + e);
  3784       var request = event.target;
  3785       var status = this._getChannelStatus(request);
  3786       LOG("Checker:onLoad - request.status: " + status);
  3787       var update = new Update(null);
  3788       update.errorCode = status;
  3789       update.statusText = getStatusTextFromCode(status, 404);
  3791       if (this._isHttpStatusCode(status)) {
  3792         update.errorCode = HTTP_ERROR_OFFSET + status;
  3794       if (e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
  3795         update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE
  3796                                       : CERT_ATTR_CHECK_FAILED_NO_UPDATE;
  3798       this._callback.onError(request, update);
  3801     this._callback = null;
  3802     this._request = null;
  3803   },
  3805   /**
  3806    * There was an error of some kind during the XMLHttpRequest
  3807    * @param   event
  3808    *          The nsIDOMEvent for the error
  3809    */
  3810   onError: function UC_onError(event) {
  3811     var request = event.target;
  3812     var status = this._getChannelStatus(request);
  3813     LOG("Checker:onError - request.status: " + status);
  3815     // If we can't find an error string specific to this status code,
  3816     // just use the 200 message from above, which means everything
  3817     // "looks" fine but there was probably an XML error or a bogus file.
  3818     var update = new Update(null);
  3819     update.errorCode = status;
  3820     update.statusText = getStatusTextFromCode(status, 200);
  3822     if (status == Cr.NS_ERROR_OFFLINE) {
  3823       // We use a separate constant here because nsIUpdate.errorCode is signed
  3824       update.errorCode = NETWORK_ERROR_OFFLINE;
  3825     } else if (this._isHttpStatusCode(status)) {
  3826       update.errorCode = HTTP_ERROR_OFFSET + status;
  3829     this._callback.onError(request, update);
  3831     this._request = null;
  3832   },
  3834   /**
  3835    * Whether or not we are allowed to do update checking.
  3836    */
  3837   _enabled: true,
  3838   get enabled() {
  3839     if (!gMetroUpdatesEnabled) {
  3840       return false;
  3843     return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
  3844            gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
  3845   },
  3847   /**
  3848    * See nsIUpdateService.idl
  3849    */
  3850   stopChecking: function UC_stopChecking(duration) {
  3851     // Always stop the current check
  3852     if (this._request)
  3853       this._request.abort();
  3855     switch (duration) {
  3856     case Ci.nsIUpdateChecker.CURRENT_SESSION:
  3857       this._enabled = false;
  3858       break;
  3859     case Ci.nsIUpdateChecker.ANY_CHECKS:
  3860       this._enabled = false;
  3861       Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
  3862       break;
  3865     this._callback = null;
  3866   },
  3868   classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
  3869   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
  3870 };
  3872 /**
  3873  * Manages the download of updates
  3874  * @param   background
  3875  *          Whether or not this downloader is operating in background
  3876  *          update mode.
  3877  * @param   updateService
  3878  *          The update service that created this downloader.
  3879  * @constructor
  3880  */
  3881 function Downloader(background, updateService) {
  3882   LOG("Creating Downloader");
  3883   this.background = background;
  3884   this.updateService = updateService;
  3886 Downloader.prototype = {
  3887   /**
  3888    * The nsIUpdatePatch that we are downloading
  3889    */
  3890   _patch: null,
  3892   /**
  3893    * The nsIUpdate that we are downloading
  3894    */
  3895   _update: null,
  3897   /**
  3898    * The nsIIncrementalDownload object handling the download
  3899    */
  3900   _request: null,
  3902   /**
  3903    * Whether or not the update being downloaded is a complete replacement of
  3904    * the user's existing installation or a patch representing the difference
  3905    * between the new version and the previous version.
  3906    */
  3907   isCompleteUpdate: null,
  3909   /**
  3910    * Cancels the active download.
  3911    */
  3912   cancel: function Downloader_cancel(cancelError) {
  3913     LOG("Downloader: cancel");
  3914     if (cancelError === undefined) {
  3915       cancelError = Cr.NS_BINDING_ABORTED;
  3917     if (this._request && this._request instanceof Ci.nsIRequest) {
  3918       this._request.cancel(cancelError);
  3920     releaseSDCardMountLock();
  3921   },
  3923   /**
  3924    * Whether or not a patch has been downloaded and staged for installation.
  3925    */
  3926   get patchIsStaged() {
  3927     var readState = readStatusFile(getUpdatesDir());
  3928     // Note that if we decide to download and apply new updates after another
  3929     // update has been successfully applied in the background, we need to stop
  3930     // checking for the APPLIED state here.
  3931     return readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
  3932            readState == STATE_APPLIED || readState == STATE_APPLIED_SVC;
  3933   },
  3935   /**
  3936    * Verify the downloaded file.  We assume that the download is complete at
  3937    * this point.
  3938    */
  3939   _verifyDownload: function Downloader__verifyDownload() {
  3940     LOG("Downloader:_verifyDownload called");
  3941     if (!this._request)
  3942       return false;
  3944     var destination = this._request.destination;
  3946     // Ensure that the file size matches the expected file size.
  3947     if (destination.fileSize != this._patch.size) {
  3948       LOG("Downloader:_verifyDownload downloaded size != expected size.");
  3949       return false;
  3952     LOG("Downloader:_verifyDownload downloaded size == expected size.");
  3953     var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
  3954                      createInstance(Ci.nsIFileInputStream);
  3955     fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
  3957     try {
  3958       var hash = Cc["@mozilla.org/security/hash;1"].
  3959                  createInstance(Ci.nsICryptoHash);
  3960       var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
  3961       if (hashFunction == undefined)
  3962         throw Cr.NS_ERROR_UNEXPECTED;
  3963       hash.init(hashFunction);
  3964       hash.updateFromStream(fileStream, -1);
  3965       // NOTE: For now, we assume that the format of _patch.hashValue is hex
  3966       // encoded binary (such as what is typically output by programs like
  3967       // sha1sum).  In the future, this may change to base64 depending on how
  3968       // we choose to compute these hashes.
  3969       digest = binaryToHex(hash.finish(false));
  3970     } catch (e) {
  3971       LOG("Downloader:_verifyDownload - failed to compute hash of the " +
  3972           "downloaded update archive");
  3973       digest = "";
  3976     fileStream.close();
  3978     if (digest == this._patch.hashValue.toLowerCase()) {
  3979       LOG("Downloader:_verifyDownload hashes match.");
  3980       return true;
  3983     LOG("Downloader:_verifyDownload hashes do not match. ");
  3984     return false;
  3985   },
  3987   /**
  3988    * Select the patch to use given the current state of updateDir and the given
  3989    * set of update patches.
  3990    * @param   update
  3991    *          A nsIUpdate object to select a patch from
  3992    * @param   updateDir
  3993    *          A nsIFile representing the update directory
  3994    * @return  A nsIUpdatePatch object to download
  3995    */
  3996   _selectPatch: function Downloader__selectPatch(update, updateDir) {
  3997     // Given an update to download, we will always try to download the patch
  3998     // for a partial update over the patch for a full update.
  4000     /**
  4001      * Return the first UpdatePatch with the given type.
  4002      * @param   type
  4003      *          The type of the patch ("complete" or "partial")
  4004      * @return  A nsIUpdatePatch object matching the type specified
  4005      */
  4006     function getPatchOfType(type) {
  4007       for (var i = 0; i < update.patchCount; ++i) {
  4008         var patch = update.getPatchAt(i);
  4009         if (patch && patch.type == type)
  4010           return patch;
  4012       return null;
  4015     // Look to see if any of the patches in the Update object has been
  4016     // pre-selected for download, otherwise we must figure out which one
  4017     // to select ourselves.
  4018     var selectedPatch = update.selectedPatch;
  4020     var state = readStatusFile(updateDir);
  4022     // If this is a patch that we know about, then select it.  If it is a patch
  4023     // that we do not know about, then remove it and use our default logic.
  4024     var useComplete = false;
  4025     if (selectedPatch) {
  4026       LOG("Downloader:_selectPatch - found existing patch with state: " +
  4027           state);
  4028       switch (state) {
  4029       case STATE_DOWNLOADING:
  4030         LOG("Downloader:_selectPatch - resuming download");
  4031         return selectedPatch;
  4032 #ifdef MOZ_WIDGET_GONK
  4033       case STATE_PENDING:
  4034       case STATE_APPLYING:
  4035         LOG("Downloader:_selectPatch - resuming interrupted apply");
  4036         return selectedPatch;
  4037       case STATE_APPLIED:
  4038         LOG("Downloader:_selectPatch - already downloaded and staged");
  4039         return null;
  4040 #else
  4041       case STATE_PENDING_SVC:
  4042       case STATE_PENDING:
  4043         LOG("Downloader:_selectPatch - already downloaded and staged");
  4044         return null;
  4045 #endif
  4046       default:
  4047         // Something went wrong when we tried to apply the previous patch.
  4048         // Try the complete patch next time.
  4049         if (update && selectedPatch.type == "partial") {
  4050           useComplete = true;
  4051         } else {
  4052           // This is a pretty fatal error.  Just bail.
  4053           LOG("Downloader:_selectPatch - failed to apply complete patch!");
  4054           writeStatusFile(updateDir, STATE_NONE);
  4055           writeVersionFile(getUpdatesDir(), null);
  4056           return null;
  4060       selectedPatch = null;
  4063     // If we were not able to discover an update from a previous download, we
  4064     // select the best patch from the given set.
  4065     var partialPatch = getPatchOfType("partial");
  4066     if (!useComplete)
  4067       selectedPatch = partialPatch;
  4068     if (!selectedPatch) {
  4069       if (partialPatch)
  4070         partialPatch.selected = false;
  4071       selectedPatch = getPatchOfType("complete");
  4074     // Restore the updateDir since we may have deleted it.
  4075     updateDir = getUpdatesDir();
  4077     // if update only contains a partial patch, selectedPatch == null here if
  4078     // the partial patch has been attempted and fails and we're trying to get a
  4079     // complete patch
  4080     if (selectedPatch)
  4081       selectedPatch.selected = true;
  4083     update.isCompleteUpdate = useComplete;
  4085     // Reset the Active Update object on the Update Manager and flush the
  4086     // Active Update DB.
  4087     var um = Cc["@mozilla.org/updates/update-manager;1"].
  4088              getService(Ci.nsIUpdateManager);
  4089     um.activeUpdate = update;
  4091     return selectedPatch;
  4092   },
  4094   /**
  4095    * Whether or not we are currently downloading something.
  4096    */
  4097   get isBusy() {
  4098     return this._request != null;
  4099   },
  4101   /**
  4102    * Get the nsIFile to use for downloading the active update's selected patch
  4103    */
  4104   _getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
  4105     var updateArchive;
  4106 #ifdef USE_UPDATE_ARCHIVE_DIR
  4107     try {
  4108       updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
  4109     } catch (e) {
  4110       return null;
  4112 #else
  4113     updateArchive = getUpdatesDir().clone();
  4114 #endif
  4116     updateArchive.append(FILE_UPDATE_ARCHIVE);
  4117     return updateArchive;
  4118   },
  4120   /**
  4121    * Download and stage the given update.
  4122    * @param   update
  4123    *          A nsIUpdate object to download a patch for. Cannot be null.
  4124    */
  4125   downloadUpdate: function Downloader_downloadUpdate(update) {
  4126     LOG("UpdateService:_downloadUpdate");
  4127     if (!update)
  4128       throw Cr.NS_ERROR_NULL_POINTER;
  4130     var updateDir = getUpdatesDir();
  4132     this._update = update;
  4134     // This function may return null, which indicates that there are no patches
  4135     // to download.
  4136     this._patch = this._selectPatch(update, updateDir);
  4137     if (!this._patch) {
  4138       LOG("Downloader:downloadUpdate - no patch to download");
  4139       return readStatusFile(updateDir);
  4141     this.isCompleteUpdate = this._patch.type == "complete";
  4143     var patchFile = null;
  4145 #ifdef MOZ_WIDGET_GONK
  4146     let status = readStatusFile(updateDir);
  4147     if (isInterruptedUpdate(status)) {
  4148       LOG("Downloader:downloadUpdate - interruptted update");
  4149       // The update was interrupted. Try to locate the existing patch file.
  4150       // For an interrupted download, this allows a resume rather than a
  4151       // re-download.
  4152       patchFile = getFileFromUpdateLink(updateDir);
  4153       if (!patchFile) {
  4154         // No link file. We'll just assume that the update.mar is in the
  4155         // update directory.
  4156         patchFile = updateDir.clone();
  4157         patchFile.append(FILE_UPDATE_ARCHIVE);
  4159       if (patchFile.exists()) {
  4160         LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path);
  4161         if (patchFile.fileSize == this._patch.size) {
  4162           LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
  4163           // Bump the status along so that we don't try to redownload again.
  4164           status = STATE_PENDING;
  4166       } else {
  4167         LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
  4168             " doesn't exist - performing full download");
  4169         // The patchfile doesn't exist, we might as well treat this like
  4170         // a new download.
  4171         patchFile = null;
  4173       if (patchFile && (status != STATE_DOWNLOADING)) {
  4174         // It looks like the patch was downloaded, but got interrupted while it
  4175         // was being verified or applied. So we'll fake the downloading portion.
  4177         writeStatusFile(updateDir, STATE_PENDING);
  4179         // Since the code expects the onStopRequest callback to happen
  4180         // asynchronously (And you have to call AUS_addDownloadListener
  4181         // after calling AUS_downloadUpdate) we need to defer this.
  4183         this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  4184         this._downloadTimer.initWithCallback(function() {
  4185           this._downloadTimer = null;
  4186           // Send a fake onStopRequest. Filling in the destination allows
  4187           // _verifyDownload to work, and then the update will be applied.
  4188           this._request = {destination: patchFile};
  4189           this.onStopRequest(this._request, null, Cr.NS_OK);
  4190         }.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT);
  4192         // Returning STATE_DOWNLOADING makes UpdatePrompt think we're
  4193         // downloading. The onStopRequest that we spoofed above will make it
  4194         // look like the download finished.
  4195         return STATE_DOWNLOADING;
  4198 #endif
  4199     if (!patchFile) {
  4200       // Find a place to put the patchfile that we're going to download.
  4201       patchFile = this._getUpdateArchiveFile();
  4203     if (!patchFile) {
  4204       return STATE_NONE;
  4207 #ifdef MOZ_WIDGET_GONK
  4208     if (patchFile.path.indexOf(updateDir.path) != 0) {
  4209       // The patchFile is in a directory which is different from the
  4210       // updateDir, create a link file.
  4211       writeLinkFile(updateDir, patchFile);
  4213       if (!isInterruptedUpdate(status) && patchFile.exists()) {
  4214         // Remove stale patchFile
  4215         patchFile.remove(false);
  4218 #endif
  4220     var uri = Services.io.newURI(this._patch.URL, null, null);
  4222     this._request = Cc["@mozilla.org/network/incremental-download;1"].
  4223                     createInstance(Ci.nsIIncrementalDownload);
  4225     LOG("Downloader:downloadUpdate - downloading from " + uri.spec + " to " +
  4226         patchFile.path);
  4227     var interval = this.background ? getPref("getIntPref",
  4228                                              PREF_APP_UPDATE_BACKGROUND_INTERVAL,
  4229                                              DOWNLOAD_BACKGROUND_INTERVAL)
  4230                                    : DOWNLOAD_FOREGROUND_INTERVAL;
  4231     this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
  4232     this._request.start(this, null);
  4234     writeStatusFile(updateDir, STATE_DOWNLOADING);
  4235     this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
  4236     this._patch.state = STATE_DOWNLOADING;
  4237     var um = Cc["@mozilla.org/updates/update-manager;1"].
  4238              getService(Ci.nsIUpdateManager);
  4239     um.saveUpdates();
  4240     return STATE_DOWNLOADING;
  4241   },
  4243   /**
  4244    * An array of download listeners to notify when we receive
  4245    * nsIRequestObserver or nsIProgressEventSink method calls.
  4246    */
  4247   _listeners: [],
  4249   /**
  4250    * Adds a listener to the download process
  4251    * @param   listener
  4252    *          A download listener, implementing nsIRequestObserver and
  4253    *          nsIProgressEventSink
  4254    */
  4255   addDownloadListener: function Downloader_addDownloadListener(listener) {
  4256     for (var i = 0; i < this._listeners.length; ++i) {
  4257       if (this._listeners[i] == listener)
  4258         return;
  4260     this._listeners.push(listener);
  4261   },
  4263   /**
  4264    * Removes a download listener
  4265    * @param   listener
  4266    *          The listener to remove.
  4267    */
  4268   removeDownloadListener: function Downloader_removeDownloadListener(listener) {
  4269     for (var i = 0; i < this._listeners.length; ++i) {
  4270       if (this._listeners[i] == listener) {
  4271         this._listeners.splice(i, 1);
  4272         return;
  4275   },
  4277   /**
  4278    * When the async request begins
  4279    * @param   request
  4280    *          The nsIRequest object for the transfer
  4281    * @param   context
  4282    *          Additional data
  4283    */
  4284   onStartRequest: function Downloader_onStartRequest(request, context) {
  4285     if (request instanceof Ci.nsIIncrementalDownload)
  4286       LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
  4287           ", final URI spec: " + request.finalURI.spec);
  4288     // Always set finalURL in onStartRequest since it can change.
  4289     this._patch.finalURL = request.finalURI.spec;
  4290     var um = Cc["@mozilla.org/updates/update-manager;1"].
  4291              getService(Ci.nsIUpdateManager);
  4292     um.saveUpdates();
  4294     var listeners = this._listeners.concat();
  4295     var listenerCount = listeners.length;
  4296     for (var i = 0; i < listenerCount; ++i)
  4297       listeners[i].onStartRequest(request, context);
  4298   },
  4300   /**
  4301    * When new data has been downloaded
  4302    * @param   request
  4303    *          The nsIRequest object for the transfer
  4304    * @param   context
  4305    *          Additional data
  4306    * @param   progress
  4307    *          The current number of bytes transferred
  4308    * @param   maxProgress
  4309    *          The total number of bytes that must be transferred
  4310    */
  4311   onProgress: function Downloader_onProgress(request, context, progress,
  4312                                              maxProgress) {
  4313     LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
  4315     if (progress > this._patch.size) {
  4316       LOG("Downloader:onProgress - progress: " + progress +
  4317           " is higher than patch size: " + this._patch.size);
  4318       // It's important that we use a different code than
  4319       // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
  4320       // between a hash error and a wrong download error.
  4321       this.cancel(Cr.NS_ERROR_UNEXPECTED);
  4322       return;
  4325     if (maxProgress != this._patch.size) {
  4326       LOG("Downloader:onProgress - maxProgress: " + maxProgress +
  4327           " is not equal to expectd patch size: " + this._patch.size);
  4328       // It's important that we use a different code than
  4329       // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
  4330       // between a hash error and a wrong download error.
  4331       this.cancel(Cr.NS_ERROR_UNEXPECTED);
  4332       return;
  4335     var listeners = this._listeners.concat();
  4336     var listenerCount = listeners.length;
  4337     for (var i = 0; i < listenerCount; ++i) {
  4338       var listener = listeners[i];
  4339       if (listener instanceof Ci.nsIProgressEventSink)
  4340         listener.onProgress(request, context, progress, maxProgress);
  4342     this.updateService._consecutiveSocketErrors = 0;
  4343   },
  4345   /**
  4346    * When we have new status text
  4347    * @param   request
  4348    *          The nsIRequest object for the transfer
  4349    * @param   context
  4350    *          Additional data
  4351    * @param   status
  4352    *          A status code
  4353    * @param   statusText
  4354    *          Human readable version of |status|
  4355    */
  4356   onStatus: function Downloader_onStatus(request, context, status, statusText) {
  4357     LOG("Downloader:onStatus - status: " + status + ", statusText: " +
  4358         statusText);
  4360     var listeners = this._listeners.concat();
  4361     var listenerCount = listeners.length;
  4362     for (var i = 0; i < listenerCount; ++i) {
  4363       var listener = listeners[i];
  4364       if (listener instanceof Ci.nsIProgressEventSink)
  4365         listener.onStatus(request, context, status, statusText);
  4367   },
  4369   /**
  4370    * When data transfer ceases
  4371    * @param   request
  4372    *          The nsIRequest object for the transfer
  4373    * @param   context
  4374    *          Additional data
  4375    * @param   status
  4376    *          Status code containing the reason for the cessation.
  4377    */
  4378   onStopRequest: function  Downloader_onStopRequest(request, context, status) {
  4379     if (request instanceof Ci.nsIIncrementalDownload)
  4380       LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
  4381           ", final URI spec: " + request.finalURI.spec + ", status: " + status);
  4383     // XXX ehsan shouldShowPrompt should always be false here.
  4384     // But what happens when there is already a UI showing?
  4385     var state = this._patch.state;
  4386     var shouldShowPrompt = false;
  4387     var shouldRegisterOnlineObserver = false;
  4388     var shouldRetrySoon = false;
  4389     var deleteActiveUpdate = false;
  4390     var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_RETRY_TIMEOUT,
  4391                                DEFAULT_UPDATE_RETRY_TIMEOUT);
  4392     var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS,
  4393                           DEFAULT_SOCKET_MAX_ERRORS);
  4394     LOG("Downloader:onStopRequest - status: " + status + ", " +
  4395         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
  4396         "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
  4397     if (Components.isSuccessCode(status)) {
  4398       if (this._verifyDownload()) {
  4399         state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING;
  4400         if (this.background) {
  4401           shouldShowPrompt = !getCanStageUpdates();
  4404         // Tell the updater.exe we're ready to apply.
  4405         writeStatusFile(getUpdatesDir(), state);
  4406         writeVersionFile(getUpdatesDir(), this._update.appVersion);
  4407         this._update.installDate = (new Date()).getTime();
  4408         this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
  4410       else {
  4411         LOG("Downloader:onStopRequest - download verification failed");
  4412         state = STATE_DOWNLOAD_FAILED;
  4413         status = Cr.NS_ERROR_CORRUPTED_CONTENT;
  4415         // Yes, this code is a string.
  4416         const vfCode = "verification_failed";
  4417         var message = getStatusTextFromCode(vfCode, vfCode);
  4418         this._update.statusText = message;
  4420         if (this._update.isCompleteUpdate || this._update.patchCount != 2)
  4421           deleteActiveUpdate = true;
  4423         // Destroy the updates directory, since we're done with it.
  4424         cleanUpUpdatesDir();
  4426     } else if (status == Cr.NS_ERROR_OFFLINE) {
  4427       // Register an online observer to try again.
  4428       // The online observer will continue the incremental download by
  4429       // calling downloadUpdate on the active update which continues
  4430       // downloading the file from where it was.
  4431       LOG("Downloader:onStopRequest - offline, register online observer: true");
  4432       shouldRegisterOnlineObserver = true;
  4433       deleteActiveUpdate = false;
  4434     // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, and
  4435     // NS_ERROR_NET_RESET can be returned when disconnecting the internet while
  4436     // a download of a MAR is in progress.  There may be others but I have not
  4437     // encountered them during testing.
  4438     } else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
  4439                 status == Cr.NS_ERROR_CONNECTION_REFUSED ||
  4440                 status == Cr.NS_ERROR_NET_RESET) &&
  4441                this.updateService._consecutiveSocketErrors < maxFail) {
  4442       LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
  4443       shouldRetrySoon = true;
  4444       deleteActiveUpdate = false;
  4445     } else if (status != Cr.NS_BINDING_ABORTED &&
  4446                status != Cr.NS_ERROR_ABORT &&
  4447                status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
  4448       LOG("Downloader:onStopRequest - non-verification failure");
  4449       // Some sort of other failure, log this in the |statusText| property
  4450       state = STATE_DOWNLOAD_FAILED;
  4452       // XXXben - if |request| (The Incremental Download) provided a means
  4453       // for accessing the http channel we could do more here.
  4455       this._update.statusText = getStatusTextFromCode(status,
  4456                                                       Cr.NS_BINDING_FAILED);
  4458 #ifdef MOZ_WIDGET_GONK
  4459       // bug891009: On FirefoxOS, manaully retry OTA download will reuse
  4460       // the Update object. We need to remove selected patch so that download
  4461       // can be triggered again successfully.
  4462       this._update.selectedPatch.selected = false;
  4463 #endif
  4465       // Destroy the updates directory, since we're done with it.
  4466       cleanUpUpdatesDir();
  4468       deleteActiveUpdate = true;
  4470     LOG("Downloader:onStopRequest - setting state to: " + state);
  4471     this._patch.state = state;
  4472     var um = Cc["@mozilla.org/updates/update-manager;1"].
  4473              getService(Ci.nsIUpdateManager);
  4474     if (deleteActiveUpdate) {
  4475       this._update.installDate = (new Date()).getTime();
  4476       um.activeUpdate = null;
  4478     else {
  4479       if (um.activeUpdate)
  4480         um.activeUpdate.state = state;
  4482     um.saveUpdates();
  4484     // Only notify listeners about the stopped state if we
  4485     // aren't handling an internal retry.
  4486     if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
  4487       var listeners = this._listeners.concat();
  4488       var listenerCount = listeners.length;
  4489       for (var i = 0; i < listenerCount; ++i) {
  4490         listeners[i].onStopRequest(request, context, status);
  4494     this._request = null;
  4496     if (state == STATE_DOWNLOAD_FAILED) {
  4497       var allFailed = true;
  4498       // Check if there is a complete update patch that can be downloaded.
  4499       if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
  4500         LOG("Downloader:onStopRequest - verification of patch failed, " +
  4501             "downloading complete update patch");
  4502         this._update.isCompleteUpdate = true;
  4503         let updateStatus = this.downloadUpdate(this._update);
  4505         if (updateStatus == STATE_NONE) {
  4506           cleanupActiveUpdate();
  4507         } else {
  4508           allFailed = false;
  4512       if (allFailed) {
  4513         LOG("Downloader:onStopRequest - all update patch downloads failed");
  4514         // If the update UI is not open (e.g. the user closed the window while
  4515         // downloading) and if at any point this was a foreground download
  4516         // notify the user about the error. If the update was a background
  4517         // update there is no notification since the user won't be expecting it.
  4518         if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) {
  4519           try {
  4520             this._update.QueryInterface(Ci.nsIWritablePropertyBag);
  4521             var fgdl = this._update.getProperty("foregroundDownload");
  4523           catch (e) {
  4526           if (fgdl == "true") {
  4527             var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  4528                            createInstance(Ci.nsIUpdatePrompt);
  4529             prompter.showUpdateError(this._update);
  4533 #ifdef MOZ_WIDGET_GONK
  4534         // We always forward errors in B2G, since Gaia controls the update UI
  4535         var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  4536                        createInstance(Ci.nsIUpdatePrompt);
  4537         prompter.showUpdateError(this._update);
  4538 #endif
  4540         // Prevent leaking the update object (bug 454964).
  4541         this._update = null;
  4543       // A complete download has been initiated or the failure was handled.
  4544       return;
  4547     if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
  4548       if (getCanStageUpdates()) {
  4549         LOG("Downloader:onStopRequest - attempting to stage update: " +
  4550             this._update.name);
  4552         // Initiate the update in the background
  4553         try {
  4554           Cc["@mozilla.org/updates/update-processor;1"].
  4555             createInstance(Ci.nsIUpdateProcessor).
  4556             processUpdate(this._update);
  4557         } catch (e) {
  4558           // Fail gracefully in case the application does not support the update
  4559           // processor service.
  4560           LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
  4561               e);
  4562           if (this.background) {
  4563             shouldShowPrompt = true;
  4569     // Do this after *everything* else, since it will likely cause the app
  4570     // to shut down.
  4571     if (shouldShowPrompt) {
  4572       // Notify the user that an update has been downloaded and is ready for
  4573       // installation (i.e. that they should restart the application). We do
  4574       // not notify on failed update attempts.
  4575       var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
  4576                      createInstance(Ci.nsIUpdatePrompt);
  4577       prompter.showUpdateDownloaded(this._update, true);
  4580     if (shouldRegisterOnlineObserver) {
  4581       LOG("Downloader:onStopRequest - Registering online observer");
  4582       this.updateService._registerOnlineObserver();
  4583     } else if (shouldRetrySoon) {
  4584       LOG("Downloader:onStopRequest - Retrying soon");
  4585       this.updateService._consecutiveSocketErrors++;
  4586       if (this.updateService._retryTimer) {
  4587         this.updateService._retryTimer.cancel();
  4589       this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  4590       this.updateService._retryTimer.initWithCallback(function() {
  4591         this._attemptResume();
  4592       }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
  4593     } else {
  4594       // Prevent leaking the update object (bug 454964)
  4595       this._update = null;
  4597   },
  4599   /**
  4600    * See nsIInterfaceRequestor.idl
  4601    */
  4602   getInterface: function Downloader_getInterface(iid) {
  4603     // The network request may require proxy authentication, so provide the
  4604     // default nsIAuthPrompt if requested.
  4605     if (iid.equals(Ci.nsIAuthPrompt)) {
  4606       var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
  4607                    createInstance();
  4608       return prompt.QueryInterface(iid);
  4610     throw Cr.NS_NOINTERFACE;
  4611   },
  4613   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
  4614                                          Ci.nsIProgressEventSink,
  4615                                          Ci.nsIInterfaceRequestor])
  4616 };
  4618 /**
  4619  * UpdatePrompt
  4620  * An object which can prompt the user with information about updates, request
  4621  * action, etc. Embedding clients can override this component with one that
  4622  * invokes a native front end.
  4623  * @constructor
  4624  */
  4625 function UpdatePrompt() {
  4627 UpdatePrompt.prototype = {
  4628   /**
  4629    * See nsIUpdateService.idl
  4630    */
  4631   checkForUpdates: function UP_checkForUpdates() {
  4632     if (this._getAltUpdateWindow())
  4633       return;
  4635     this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
  4636                  null, null);
  4637   },
  4639   /**
  4640    * See nsIUpdateService.idl
  4641    */
  4642   showUpdateAvailable: function UP_showUpdateAvailable(update) {
  4643     if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
  4644         this._getUpdateWindow() || this._getAltUpdateWindow())
  4645       return;
  4647     var stringsPrefix = "updateAvailable_" + update.type + ".";
  4648     var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
  4649                                                    [update.name], 1);
  4650     var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
  4651     var imageUrl = "";
  4652     this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
  4653                            UPDATE_WINDOW_NAME, "updatesavailable", update,
  4654                            title, text, imageUrl);
  4655   },
  4657   /**
  4658    * See nsIUpdateService.idl
  4659    */
  4660   showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
  4661     if (this._getAltUpdateWindow())
  4662       return;
  4664     if (background) {
  4665       if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false))
  4666         return;
  4668       var stringsPrefix = "updateDownloaded_" + update.type + ".";
  4669       var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
  4670                                                      [update.name], 1);
  4671       var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
  4672       var imageUrl = "";
  4673       this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
  4674                               UPDATE_WINDOW_NAME, "finishedBackground", update,
  4675                               title, text, imageUrl);
  4676     } else {
  4677       this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
  4678                    UPDATE_WINDOW_NAME, "finishedBackground", update);
  4680   },
  4682   /**
  4683    * See nsIUpdateService.idl
  4684    */
  4685   showUpdateInstalled: function UP_showUpdateInstalled() {
  4686     if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
  4687         !getPref("getBoolPref", PREF_APP_UPDATE_SHOW_INSTALLED_UI, false) ||
  4688         this._getUpdateWindow())
  4689       return;
  4691     var page = "installed";
  4692     var win = this._getUpdateWindow();
  4693     if (win) {
  4694       if (page && "setCurrentPage" in win)
  4695         win.setCurrentPage(page);
  4696       win.focus();
  4698     else {
  4699       var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
  4700       var arg = Cc["@mozilla.org/supports-string;1"].
  4701                 createInstance(Ci.nsISupportsString);
  4702       arg.data = page;
  4703       Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
  4705   },
  4707   /**
  4708    * See nsIUpdateService.idl
  4709    */
  4710   showUpdateError: function UP_showUpdateError(update) {
  4711     if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
  4712         this._getAltUpdateWindow())
  4713       return;
  4715     // In some cases, we want to just show a simple alert dialog:
  4716     if (update.state == STATE_FAILED &&
  4717         (update.errorCode == WRITE_ERROR ||
  4718          update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
  4719          update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
  4720          update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
  4721          update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
  4722          update.errorCode == WRITE_ERROR_CALLBACK_APP ||
  4723          update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR ||
  4724          update.errorCode == FOTA_GENERAL_ERROR ||
  4725          update.errorCode == FOTA_FILE_OPERATION_ERROR ||
  4726          update.errorCode == FOTA_RECOVERY_ERROR ||
  4727          update.errorCode == FOTA_UNKNOWN_ERROR)) {
  4728       var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
  4729       var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
  4730                                                     [Services.appinfo.name,
  4731                                                      Services.appinfo.name], 2);
  4732       Services.ww.getNewPrompter(null).alert(title, text);
  4733       return;
  4736     if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
  4737         update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE ||
  4738         update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
  4739       this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null,
  4740                            UPDATE_WINDOW_NAME, null, update);
  4741       return;
  4744     this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
  4745                  "errors", update);
  4746   },
  4748   /**
  4749    * See nsIUpdateService.idl
  4750    */
  4751   showUpdateHistory: function UP_showUpdateHistory(parent) {
  4752     this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
  4753                  "Update:History", null, null);
  4754   },
  4756   /**
  4757    * Returns the update window if present.
  4758    */
  4759   _getUpdateWindow: function UP__getUpdateWindow() {
  4760     return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
  4761   },
  4763   /**
  4764    * Returns an alternative update window if present. When a window with this
  4765    * windowtype is open the application update service won't open the normal
  4766    * application update user interface window.
  4767    */
  4768   _getAltUpdateWindow: function UP__getAltUpdateWindow() {
  4769     let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
  4770     if (!windowType)
  4771       return null;
  4772     return Services.wm.getMostRecentWindow(windowType);
  4773   },
  4775   /**
  4776    * Initiate a less obtrusive UI, starting with a non-modal notification alert
  4777    * @param   parent
  4778    *          A parent window, can be null
  4779    * @param   uri
  4780    *          The URI string of the dialog to show
  4781    * @param   name
  4782    *          The Window Name of the dialog to show, in case it is already open
  4783    *          and can merely be focused
  4784    * @param   page
  4785    *          The page of the wizard to be displayed, if one is already open.
  4786    * @param   update
  4787    *          An update to pass to the UI in the window arguments.
  4788    *          Can be null
  4789    * @param   title
  4790    *          The title for the notification alert.
  4791    * @param   text
  4792    *          The contents of the notification alert.
  4793    * @param   imageUrl
  4794    *          A URL identifying the image to put in the notification alert.
  4795    */
  4796   _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
  4797                                               update, title, text, imageUrl) {
  4798     var observer = {
  4799       updatePrompt: this,
  4800       service: null,
  4801       timer: null,
  4802       notify: function () {
  4803         // the user hasn't restarted yet => prompt when idle
  4804         this.service.removeObserver(this, "quit-application");
  4805         // If the update window is already open skip showing the UI
  4806         if (this.updatePrompt._getUpdateWindow())
  4807           return;
  4808         this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
  4809       },
  4810       observe: function (aSubject, aTopic, aData) {
  4811         switch (aTopic) {
  4812           case "alertclickcallback":
  4813             this.updatePrompt._showUI(parent, uri, features, name, page, update);
  4814             // fall thru
  4815           case "quit-application":
  4816             if (this.timer)
  4817               this.timer.cancel();
  4818             this.service.removeObserver(this, "quit-application");
  4819             break;
  4822     };
  4824     // bug 534090 - show the UI for update available notifications when the
  4825     // the system has been idle for at least IDLE_TIME without displaying an
  4826     // alert notification.
  4827     if (page == "updatesavailable") {
  4828       var idleService = Cc["@mozilla.org/widget/idleservice;1"].
  4829                         getService(Ci.nsIIdleService);
  4831       const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
  4832       if (idleService.idleTime / 1000 >= IDLE_TIME) {
  4833         this._showUI(parent, uri, features, name, page, update);
  4834         return;
  4838     try {
  4839       var notifier = Cc["@mozilla.org/alerts-service;1"].
  4840                      getService(Ci.nsIAlertsService);
  4841       notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
  4843     catch (e) {
  4844       // Failed to retrieve alerts service, platform unsupported
  4845       this._showUIWhenIdle(parent, uri, features, name, page, update);
  4846       return;
  4849     observer.service = Services.obs;
  4850     observer.service.addObserver(observer, "quit-application", false);
  4852     // bug 534090 - show the UI when idle for update available notifications.
  4853     if (page == "updatesavailable") {
  4854       this._showUIWhenIdle(parent, uri, features, name, page, update);
  4855       return;
  4858     // Give the user x seconds to react before prompting as defined by
  4859     // promptWaitTime
  4860     observer.timer = Cc["@mozilla.org/timer;1"].
  4861                      createInstance(Ci.nsITimer);
  4862     observer.timer.initWithCallback(observer, update.promptWaitTime * 1000,
  4863                                     observer.timer.TYPE_ONE_SHOT);
  4864   },
  4866   /**
  4867    * Show the UI when the user was idle
  4868    * @param   parent
  4869    *          A parent window, can be null
  4870    * @param   uri
  4871    *          The URI string of the dialog to show
  4872    * @param   name
  4873    *          The Window Name of the dialog to show, in case it is already open
  4874    *          and can merely be focused
  4875    * @param   page
  4876    *          The page of the wizard to be displayed, if one is already open.
  4877    * @param   update
  4878    *          An update to pass to the UI in the window arguments.
  4879    *          Can be null
  4880    */
  4881   _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
  4882                                                page, update) {
  4883     var idleService = Cc["@mozilla.org/widget/idleservice;1"].
  4884                       getService(Ci.nsIIdleService);
  4886     const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
  4887     if (idleService.idleTime / 1000 >= IDLE_TIME) {
  4888       this._showUI(parent, uri, features, name, page, update);
  4889     } else {
  4890       var observer = {
  4891         updatePrompt: this,
  4892         observe: function (aSubject, aTopic, aData) {
  4893           switch (aTopic) {
  4894             case "idle":
  4895               // If the update window is already open skip showing the UI
  4896               if (!this.updatePrompt._getUpdateWindow())
  4897                 this.updatePrompt._showUI(parent, uri, features, name, page, update);
  4898               // fall thru
  4899             case "quit-application":
  4900               idleService.removeIdleObserver(this, IDLE_TIME);
  4901               Services.obs.removeObserver(this, "quit-application");
  4902               break;
  4905       };
  4906       idleService.addIdleObserver(observer, IDLE_TIME);
  4907       Services.obs.addObserver(observer, "quit-application", false);
  4909   },
  4911   /**
  4912    * Show the Update Checking UI
  4913    * @param   parent
  4914    *          A parent window, can be null
  4915    * @param   uri
  4916    *          The URI string of the dialog to show
  4917    * @param   name
  4918    *          The Window Name of the dialog to show, in case it is already open
  4919    *          and can merely be focused
  4920    * @param   page
  4921    *          The page of the wizard to be displayed, if one is already open.
  4922    * @param   update
  4923    *          An update to pass to the UI in the window arguments.
  4924    *          Can be null
  4925    */
  4926   _showUI: function UP__showUI(parent, uri, features, name, page, update) {
  4927     var ary = null;
  4928     if (update) {
  4929       ary = Cc["@mozilla.org/supports-array;1"].
  4930             createInstance(Ci.nsISupportsArray);
  4931       ary.AppendElement(update);
  4934     var win = this._getUpdateWindow();
  4935     if (win) {
  4936       if (page && "setCurrentPage" in win)
  4937         win.setCurrentPage(page);
  4938       win.focus();
  4940     else {
  4941       var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
  4942       if (features)
  4943         openFeatures += "," + features;
  4944       Services.ww.openWindow(parent, uri, "", openFeatures, ary);
  4946   },
  4948   classDescription: "Update Prompt",
  4949   contractID: "@mozilla.org/updates/update-prompt;1",
  4950   classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
  4951   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
  4952 };
  4954 var components = [UpdateService, Checker, UpdatePrompt, UpdateManager];
  4955 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
  4957 #if 0
  4958 /**
  4959  * Logs a message and stack trace to the console.
  4960  * @param   string
  4961  *          The string to write to the console.
  4962  */
  4963 function STACK(string) {
  4964   dump("*** " + string + "\n");
  4965   stackTrace(arguments.callee.caller.arguments, -1);
  4968 function stackTraceFunctionFormat(aFunctionName) {
  4969   var classDelimiter = aFunctionName.indexOf("_");
  4970   var className = aFunctionName.substr(0, classDelimiter);
  4971   if (!className)
  4972     className = "<global>";
  4973   var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length);
  4974   if (!functionName)
  4975     functionName = "<anonymous>";
  4976   return className + "::" + functionName;
  4979 function stackTraceArgumentsFormat(aArguments) {
  4980   arglist = "";
  4981   for (var i = 0; i < aArguments.length; i++) {
  4982     arglist += aArguments[i];
  4983     if (i < aArguments.length - 1)
  4984       arglist += ", ";
  4986   return arglist;
  4989 function stackTrace(aArguments, aMaxCount) {
  4990   dump("=[STACKTRACE]=====================================================\n");
  4991   dump("*** at: " + stackTraceFunctionFormat(aArguments.callee.name) + "(" +
  4992        stackTraceArgumentsFormat(aArguments) + ")\n");
  4993   var temp = aArguments.callee.caller;
  4994   var count = 0;
  4995   while (temp) {
  4996     dump("***     " + stackTraceFunctionFormat(temp.name) + "(" +
  4997          stackTraceArgumentsFormat(temp.arguments) + ")\n");
  4999     temp = temp.arguments.callee.caller;
  5000     if (aMaxCount > 0 && ++count == aMaxCount)
  5001       break;
  5003   dump("==================================================================\n");
  5006 function dumpFile(file) {
  5007   dump("*** file = " + file.path + ", exists = " + file.exists() + "\n");
  5009 #endif

mercurial